mirror of
https://github.com/fadden/nulib2.git
synced 2024-09-27 15:56:12 +00:00
410 lines
9.5 KiB
C
410 lines
9.5 KiB
C
|
/*
|
||
|
* Nulib2
|
||
|
* Copyright (C) 2000 by Andy McFadden, All Rights Reserved.
|
||
|
* This is free software; you can redistribute it and/or modify it under the
|
||
|
* terms of the GNU General Public License, see the file COPYING.
|
||
|
*
|
||
|
* Main entry point and shell command argument processing.
|
||
|
*/
|
||
|
#include "Nulib2.h"
|
||
|
#include <ctype.h>
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Globals and constants.
|
||
|
*/
|
||
|
const char* gProgName = "Nulib2";
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Which modifiers are valid with which commands?
|
||
|
*/
|
||
|
typedef struct ValidCombo {
|
||
|
Command cmd;
|
||
|
Boolean okayForPipe;
|
||
|
Boolean filespecRequired;
|
||
|
const char* modifiers;
|
||
|
} ValidCombo;
|
||
|
|
||
|
static const ValidCombo gValidCombos[] = {
|
||
|
{ kCommandAdd, false, true, "ufrj0cke" },
|
||
|
{ kCommandDelete, false, true, "r" },
|
||
|
{ kCommandExtract, true, false, "ufrjclse" },
|
||
|
{ kCommandExtractToPipe, true, false, "rl" },
|
||
|
{ kCommandListShort, true, false, "" },
|
||
|
{ kCommandListVerbose, true, false, "" },
|
||
|
{ kCommandListDebug, true, false, "" },
|
||
|
{ kCommandTest, true, false, "r" },
|
||
|
};
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Find an entry in the gValidCombos table matching the specified command.
|
||
|
*
|
||
|
* Returns nil if not found.
|
||
|
*/
|
||
|
static const ValidCombo*
|
||
|
FindValidComboEntry(Command cmd)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < NELEM(gValidCombos); i++) {
|
||
|
if (gValidCombos[i].cmd == cmd)
|
||
|
return &gValidCombos[i];
|
||
|
}
|
||
|
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Determine whether the specified modifier is valid when used with the
|
||
|
* current command.
|
||
|
*/
|
||
|
static Boolean
|
||
|
IsValidModifier(Command cmd, char modifier)
|
||
|
{
|
||
|
const ValidCombo* pvc;
|
||
|
|
||
|
pvc = FindValidComboEntry(cmd);
|
||
|
if (pvc != nil) {
|
||
|
if (strchr(pvc->modifiers, modifier) == nil)
|
||
|
return false;
|
||
|
else
|
||
|
return true;
|
||
|
} else
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Determine whether the specified command can be used with stdin as input.
|
||
|
*/
|
||
|
static Boolean
|
||
|
IsValidOnPipe(Command cmd)
|
||
|
{
|
||
|
const ValidCombo* pvc;
|
||
|
|
||
|
pvc = FindValidComboEntry(cmd);
|
||
|
if (pvc != nil) {
|
||
|
return pvc->okayForPipe;
|
||
|
} else
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Determine whether the specified command can be used with stdin as input.
|
||
|
*/
|
||
|
static Boolean
|
||
|
IsFilespecRequired(Command cmd)
|
||
|
{
|
||
|
const ValidCombo* pvc;
|
||
|
|
||
|
pvc = FindValidComboEntry(cmd);
|
||
|
if (pvc != nil) {
|
||
|
return pvc->filespecRequired;
|
||
|
} else {
|
||
|
/* command not found? warn about it here... */
|
||
|
fprintf(stderr, "%s: Command %d not found in gValidCombos table\n",
|
||
|
gProgName, cmd);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Separate the program name out of argv[0].
|
||
|
*/
|
||
|
static const char*
|
||
|
GetProgName(const NulibState* pState, const char* argv0)
|
||
|
{
|
||
|
const char* result;
|
||
|
char sep;
|
||
|
|
||
|
/* use the appropriate system pathname separator */
|
||
|
sep = NState_GetSystemPathSeparator(pState);
|
||
|
|
||
|
result = strrchr(argv0, sep);
|
||
|
if (result == nil)
|
||
|
result = argv0;
|
||
|
else
|
||
|
result++; /* advance past the separator */
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Print program usage.
|
||
|
*/
|
||
|
static void
|
||
|
Usage(const NulibState* pState)
|
||
|
{
|
||
|
long majorVersion, minorVersion, bugVersion;
|
||
|
const char* nufxLibDate;
|
||
|
const char* nufxLibFlags;
|
||
|
|
||
|
(void) NuGetVersion(&majorVersion, &minorVersion, &bugVersion,
|
||
|
&nufxLibDate, &nufxLibFlags);
|
||
|
|
||
|
printf("\nNulib2 v%s, linked with NufxLib v%ld.%ld.%ld [%s]\n",
|
||
|
NState_GetProgramVersion(pState),
|
||
|
majorVersion, minorVersion, bugVersion, nufxLibFlags);
|
||
|
printf("This software is distributed under terms of the GNU General Public License.\n");
|
||
|
printf("Written by Andy McFadden, http://www.nulib.com/.\n\n");
|
||
|
printf("Usage: %s -command[modifiers] archive [filename-list]\n\n",
|
||
|
gProgName);
|
||
|
printf(
|
||
|
" -a add files, create arc if needed -x extract files\n"
|
||
|
" -t list files (short) -v list files (verbose)\n"
|
||
|
" -p extract files to pipe, no msgs -i test archive integrity\n"
|
||
|
" -d delete files from archive\n"
|
||
|
"\n"
|
||
|
" modifiers:\n"
|
||
|
" -u update files (add + keep newest) -f freshen (update, no add)\n"
|
||
|
" -r recurse into subdirs -j junk (don't record) directory names\n"
|
||
|
" -0 don't use compression -c add one-line comments\n"
|
||
|
" -l auto-convert text files -ll auto-convert ALL files\n"
|
||
|
" -s stomp existing files w/o asking -k store files as disk images\n"
|
||
|
" -e preserve ProDOS file types -ee extend preserved names\n"
|
||
|
);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Process the command-line options. The results are placed into "pState".
|
||
|
*/
|
||
|
static int
|
||
|
ProcessOptions(NulibState* pState, int argc, char* const* argv)
|
||
|
{
|
||
|
const char* cp;
|
||
|
int idx;
|
||
|
|
||
|
/*
|
||
|
* Must have at least a command letter and an archive filename.
|
||
|
*/
|
||
|
if (argc < 3) {
|
||
|
Usage(pState);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Argv[1] and any subsequent entries that have a leading hyphen
|
||
|
* are options. Anything after that is a filename. Parse until we
|
||
|
* think we've hit the filename.
|
||
|
*
|
||
|
* By UNIX convention, however, stdin is specified as a file called "-".
|
||
|
*/
|
||
|
for (idx = 1; idx < argc; idx++) {
|
||
|
cp = argv[idx];
|
||
|
|
||
|
if (idx > 1 && *cp != '-')
|
||
|
break;
|
||
|
|
||
|
if (*cp == '-')
|
||
|
cp++;
|
||
|
if (*cp == '\0') {
|
||
|
if (idx == 1) {
|
||
|
fprintf(stderr,
|
||
|
"%s: You must specify a command after the '-'\n",
|
||
|
gProgName);
|
||
|
goto fail;
|
||
|
} else {
|
||
|
/* they're using '-' for the filename */
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (idx == 1) {
|
||
|
switch (tolower(*cp)) {
|
||
|
case 'a': NState_SetCommand(pState, kCommandAdd); break;
|
||
|
case 'x': NState_SetCommand(pState, kCommandExtract); break;
|
||
|
case 'p': NState_SetCommand(pState, kCommandExtractToPipe); break;
|
||
|
case 't': NState_SetCommand(pState, kCommandListShort); break;
|
||
|
case 'v': NState_SetCommand(pState, kCommandListVerbose); break;
|
||
|
case 'z': NState_SetCommand(pState, kCommandListDebug); break;
|
||
|
case 'i': NState_SetCommand(pState, kCommandTest); break;
|
||
|
case 'd': NState_SetCommand(pState, kCommandDelete); break;
|
||
|
default:
|
||
|
fprintf(stderr, "%s: Unknown command '%c'\n", gProgName, *cp);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
cp++;
|
||
|
}
|
||
|
|
||
|
while (*cp != '\0') {
|
||
|
switch (tolower(*cp)) {
|
||
|
case 'u': NState_SetModUpdate(pState, true); break;
|
||
|
case 'f': NState_SetModFreshen(pState, true); break;
|
||
|
case 'r': NState_SetModRecurse(pState, true); break;
|
||
|
case 'j': NState_SetModJunkPaths(pState, true); break;
|
||
|
case '0': NState_SetModNoCompression(pState, true); break;
|
||
|
case 'c': NState_SetModComments(pState, true); break;
|
||
|
case 's': NState_SetModOverwriteExisting(pState, true); break;
|
||
|
case 'k': NState_SetModAddAsDisk(pState, true); break;
|
||
|
case 'e':
|
||
|
if (*(cp-1) == 'e') /* should never point at invalid */
|
||
|
NState_SetModPreserveTypeExtended(pState, true);
|
||
|
else
|
||
|
NState_SetModPreserveType(pState, true);
|
||
|
break;
|
||
|
case 'l':
|
||
|
if (*(cp-1) == 'l') /* should never point at invalid */
|
||
|
NState_SetModConvertAll(pState, true);
|
||
|
else
|
||
|
NState_SetModConvertText(pState, true);
|
||
|
break;
|
||
|
default:
|
||
|
fprintf(stderr, "%s: Unknown modifier '%c'\n", gProgName, *cp);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
if (!IsValidModifier(NState_GetCommand(pState), (char)tolower(*cp)))
|
||
|
{
|
||
|
fprintf(stderr,
|
||
|
"%s: The '%c' modifier doesn't make sense here\n",
|
||
|
gProgName, tolower(*cp));
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
cp++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* See if we have an archive name. If it's "-", see if we allow that.
|
||
|
*/
|
||
|
assert(idx < argc);
|
||
|
NState_SetArchiveFilename(pState, argv[idx]);
|
||
|
if (IsFilenameStdin(argv[idx])) {
|
||
|
if (!IsValidOnPipe(NState_GetCommand(pState))) {
|
||
|
fprintf(stderr, "%s: You can't do that with a pipe\n",
|
||
|
gProgName);
|
||
|
goto fail;
|
||
|
}
|
||
|
}
|
||
|
idx++;
|
||
|
|
||
|
/*
|
||
|
* See if we have a file specification. Some of the commands require
|
||
|
* a filespec; others just perform the requested operation on all of
|
||
|
* the records in the archive if none is provided.
|
||
|
*/
|
||
|
if (idx < argc) {
|
||
|
/* got one or more */
|
||
|
NState_SetFilespecPointer(pState, &argv[idx]);
|
||
|
NState_SetFilespecCount(pState, argc - idx);
|
||
|
} else {
|
||
|
assert(idx == argc);
|
||
|
if (IsFilespecRequired(NState_GetCommand(pState))) {
|
||
|
fprintf(stderr, "%s: This command requires a list of files\n",
|
||
|
gProgName);
|
||
|
goto fail;
|
||
|
}
|
||
|
NState_SetFilespecPointer(pState, nil);
|
||
|
NState_SetFilespecCount(pState, 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
#ifdef DEBUG_VERBOSE
|
||
|
NState_DebugDump(pState);
|
||
|
#endif
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
fail:
|
||
|
fprintf(stderr,
|
||
|
"%s: (invoke without arguments to see usage information)\n",
|
||
|
gProgName);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* We have all of the parsed command line options in "pState". Now we just
|
||
|
* have to do something useful with it.
|
||
|
*
|
||
|
* Returns 0 on success, 1 on error.
|
||
|
*/
|
||
|
int
|
||
|
DoWork(NulibState* pState)
|
||
|
{
|
||
|
NuError err;
|
||
|
|
||
|
switch (NState_GetCommand(pState)) {
|
||
|
case kCommandAdd:
|
||
|
err = DoAdd(pState);
|
||
|
break;
|
||
|
case kCommandExtract:
|
||
|
err = DoExtract(pState);
|
||
|
break;
|
||
|
case kCommandExtractToPipe:
|
||
|
err = DoExtractToPipe(pState);
|
||
|
break;
|
||
|
case kCommandTest:
|
||
|
err = DoTest(pState);
|
||
|
break;
|
||
|
case kCommandListShort:
|
||
|
err = DoListShort(pState);
|
||
|
break;
|
||
|
case kCommandListVerbose:
|
||
|
err = DoListVerbose(pState);
|
||
|
break;
|
||
|
case kCommandListDebug:
|
||
|
err = DoListDebug(pState);
|
||
|
break;
|
||
|
case kCommandDelete:
|
||
|
err = DoDelete(pState);
|
||
|
break;
|
||
|
default:
|
||
|
fprintf(stderr, "ERROR: unexpected command %d\n",
|
||
|
NState_GetCommand(pState));
|
||
|
err = kNuErrInternal;
|
||
|
assert(0);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return (err != kNuErrNone);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Entry point.
|
||
|
*/
|
||
|
int
|
||
|
main(int argc, char** argv)
|
||
|
{
|
||
|
NulibState* pState = nil;
|
||
|
int result = 0;
|
||
|
|
||
|
#if 0
|
||
|
extern NuResult ErrorMessageHandler(NuArchive* pArchive,
|
||
|
void* vErrorMessage);
|
||
|
NuSetGlobalErrorMessageHandler(ErrorMessageHandler);
|
||
|
#endif
|
||
|
|
||
|
if (NState_Init(&pState) != kNuErrNone) {
|
||
|
fprintf(stderr, "ERROR: unable to initialize globals\n");
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
gProgName = GetProgName(pState, argv[0]);
|
||
|
|
||
|
if (ProcessOptions(pState, argc, argv) < 0) {
|
||
|
result = 2;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
if (NState_ExtraInit(pState) != kNuErrNone) {
|
||
|
fprintf(stderr, "ERROR: additional initialization failed\n");
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
result = DoWork(pState);
|
||
|
if (result)
|
||
|
printf("Failed.\n");
|
||
|
|
||
|
bail:
|
||
|
NState_Free(pState);
|
||
|
exit(result);
|
||
|
}
|
||
|
|