ciderpress/nufxlib/samples/Exerciser.c

1366 lines
38 KiB
C

/*
* NuFX archive manipulation library
* Copyright (C) 2000-2007 by Andy McFadden, All Rights Reserved.
* This is free software; you can redistribute it and/or modify it under the
* terms of the BSD License, see the file COPYING.LIB.
*
* NufxLib exerciser. Most library functions can be invoked directly from
* the exerciser command line.
*
* This was written in C++ to evaluate the interaction between NufxLib and
* the C++ language, i.e. to make sure that all type definitions and
* function calls can be used without giving the compiler fits. This
* file will compile as either "Exerciser.c" or "Exerciser.cpp".
*/
#include "NufxLib.h"
#include "Common.h"
#include <ctype.h>
/* not portable to other OSs, but not all that important anyway */
static const char kFssep = PATH_SEP;
/* ProDOS access permissions */
#define kUnlocked 0xe3
#define kTempFile "exer-temp"
#ifndef HAVE_STRCASECMP
static int strcasecmp(const char *str1, const char *str2)
{
while (*str1 && *str2 && toupper(*str1) == toupper(*str2))
str1++, str2++;
return (toupper(*str1) - toupper(*str2));
}
#endif
/*
* ===========================================================================
* ExerciserState object
* ===========================================================================
*/
/*
* Exerciser state.
*
* In case it isn't immediately apparent, this was written in C++ and
* then converted back to C.
*/
typedef struct ExerciserState {
NuArchive* pArchive;
char* archivePath;
const char* archiveFile;
} ExerciserState;
ExerciserState* ExerciserState_New(void)
{
ExerciserState* pExerState;
pExerState = (ExerciserState*) malloc(sizeof(*pExerState));
if (pExerState == NULL)
return NULL;
pExerState->pArchive = NULL;
pExerState->archivePath = NULL;
pExerState->archiveFile = NULL;
return pExerState;
}
void ExerciserState_Free(ExerciserState* pExerState)
{
if (pExerState == NULL)
return;
if (pExerState->pArchive != NULL) {
printf("Exerciser: aborting open archive\n");
(void) NuAbort(pExerState->pArchive);
(void) NuClose(pExerState->pArchive);
}
if (pExerState->archivePath != NULL)
free(pExerState->archivePath);
free(pExerState);
}
inline NuArchive* ExerciserState_GetNuArchive(const ExerciserState* pExerState)
{
return pExerState->pArchive;
}
inline void ExerciserState_SetNuArchive(ExerciserState* pExerState,
NuArchive* newArchive)
{
pExerState->pArchive = newArchive;
}
inline char* ExerciserState_GetArchivePath(const ExerciserState* pExerState)
{
return pExerState->archivePath;
}
inline void ExerciserState_SetArchivePath(ExerciserState* pExerState,
char* newPath)
{
if (pExerState->archivePath != NULL)
free(pExerState->archivePath);
if (newPath == NULL) {
pExerState->archivePath = NULL;
pExerState->archiveFile = NULL;
} else {
pExerState->archivePath = strdup(newPath);
pExerState->archiveFile = strrchr(newPath, kFssep);
if (pExerState->archiveFile != NULL)
pExerState->archiveFile++;
if (pExerState->archiveFile == NULL || *pExerState->archiveFile == '\0')
pExerState->archiveFile = pExerState->archivePath;
}
}
inline const char* ExerciserState_GetArchiveFile(const ExerciserState* pExerState)
{
if (pExerState->archiveFile == NULL)
return "[no archive open]";
else
return pExerState->archiveFile;
}
/*
* ===========================================================================
* Utility functions
* ===========================================================================
*/
/*
* NuContents callback function. Print the contents of an individual record.
*/
NuResult PrintEntry(NuArchive* pArchive, void* vpRecord)
{
const NuRecord* pRecord = (const NuRecord*) vpRecord;
int idx;
(void)pArchive; /* shut up, gcc */
printf("RecordIdx %u: '%s'\n",
pRecord->recordIdx, pRecord->filenameMOR);
for (idx = 0; idx < (int) pRecord->recTotalThreads; idx++) {
const NuThread* pThread;
NuThreadID threadID;
const char* threadLabel;
pThread = NuGetThread(pRecord, idx);
assert(pThread != NULL);
threadID = NuGetThreadID(pThread);
switch (NuThreadIDGetClass(threadID)) {
case kNuThreadClassMessage:
threadLabel = "message class";
break;
case kNuThreadClassControl:
threadLabel = "control class";
break;
case kNuThreadClassData:
threadLabel = "data class";
break;
case kNuThreadClassFilename:
threadLabel = "filename class";
break;
default:
threadLabel = "(unknown class)";
break;
}
switch (threadID) {
case kNuThreadIDComment:
threadLabel = "comment";
break;
case kNuThreadIDIcon:
threadLabel = "icon";
break;
case kNuThreadIDMkdir:
threadLabel = "mkdir";
break;
case kNuThreadIDDataFork:
threadLabel = "data fork";
break;
case kNuThreadIDDiskImage:
threadLabel = "disk image";
break;
case kNuThreadIDRsrcFork:
threadLabel = "rsrc fork";
break;
case kNuThreadIDFilename:
threadLabel = "filename";
break;
default:
break;
}
printf(" ThreadIdx %u - 0x%08x (%s)\n", pThread->threadIdx,
threadID, threadLabel);
}
return kNuOK;
}
#define kNiceLineLen 256
/*
* Get a line of input, stripping the '\n' off the end.
*/
static NuError GetLine(const char* prompt, char* buffer, int bufferSize)
{
printf("%s> ", prompt);
fflush(stdout);
if (fgets(buffer, bufferSize, stdin) == NULL)
return kNuErrGeneric;
if (buffer[strlen(buffer)-1] == '\n')
buffer[strlen(buffer)-1] = '\0';
return kNuErrNone;
}
/*
* Selection filter for mass "extract" and "delete" operations.
*/
NuResult SelectionFilter(NuArchive* pArchive, void* vselFilt)
{
const NuSelectionProposal* selProposal = (NuSelectionProposal*) vselFilt;
char buffer[8];
printf("%s (N/y)? ", selProposal->pRecord->filenameMOR);
fflush(stdout);
if (fgets(buffer, sizeof(buffer), stdin) == NULL)
return kNuAbort;
if (tolower(buffer[0]) == 'y')
return kNuOK;
else
return kNuSkip;
}
/*
* General-purpose error handler.
*/
NuResult ErrorHandler(NuArchive* pArchive, void* vErrorStatus)
{
const NuErrorStatus* pErrorStatus = (const NuErrorStatus*) vErrorStatus;
char buffer[8];
NuResult result = kNuSkip;
printf("Exerciser: error handler op=%d err=%d sysErr=%d message='%s'\n"
"\tfilename='%s' '%c'(0x%02x)\n",
pErrorStatus->operation, pErrorStatus->err, pErrorStatus->sysErr,
pErrorStatus->message == NULL ? "(NULL)" : pErrorStatus->message,
pErrorStatus->pathnameUNI, pErrorStatus->filenameSeparator,
pErrorStatus->filenameSeparator);
printf("\tValid options are:");
if (pErrorStatus->canAbort)
printf(" a)bort");
if (pErrorStatus->canRetry)
printf(" r)etry");
if (pErrorStatus->canIgnore)
printf(" i)gnore");
if (pErrorStatus->canSkip)
printf(" s)kip");
if (pErrorStatus->canRename)
printf(" re)name");
if (pErrorStatus->canOverwrite)
printf(" o)verwrite");
putc('\n', stdout);
printf("Return what (a/r/i/s/e/o)? ");
fflush(stdout);
if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
printf("Returning kNuSkip\n");
} else switch (buffer[0]) {
case 'a': result = kNuAbort; break;
case 'r': result = kNuRetry; break;
case 'i': result = kNuIgnore; break;
case 's': result = kNuSkip; break;
case 'e': result = kNuRename; break;
case 'o': result = kNuOverwrite; break;
default:
printf("Unknown value '%c', returning kNuSkip\n", buffer[0]);
break;
}
return result;
}
/*
* This gets called when a buffer DataSource is no longer needed.
*/
NuResult FreeCallback(NuArchive* pArchive, void* args)
{
free(args);
return kNuOK;
}
/*
* ===========================================================================
* Command handlers
* ===========================================================================
*/
typedef NuError (*CommandFunc)(ExerciserState* pState, int argc,
char** argv);
static NuError HelpFunc(ExerciserState* pState, int argc, char** argv);
#if 0
static NuError
GenericFunc(ExerciserState* pState, int argc, char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
printf("Generic! argc=%d\n", argc);
return kNuErrNone;
}
#endif
/*
* Do nothing. Useful when the user just hits <return> on a blank line.
*/
static NuError NothingFunc(ExerciserState* pState, int argc, char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
return kNuErrNone;
}
/*
* q - quit
*
* Do nothing. This is used as a trigger for quitting the program. In
* practice, we catch this earlier, and won't actually call here.
*/
static NuError QuitFunc(ExerciserState* pState, int argc, char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(0);
return kNuErrNone;
}
/*
* ab - abort current changes
*/
static NuError AbortFunc(ExerciserState* pState, int argc, char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 1);
return NuAbort(ExerciserState_GetNuArchive(pState));
}
/*
* af - add file to archive
*/
static NuError AddFileFunc(ExerciserState* pState, int argc, char** argv)
{
NuFileDetails nuFileDetails;
int fromRsrc = false;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 3);
if (strcasecmp(argv[2], "true") == 0) {
fromRsrc = true;
} else if (strcasecmp(argv[2], "false") != 0) {
fprintf(stderr, "WARNING: fromRsrc should be 'true' or 'false'\n");
/* ignore */
}
memset(&nuFileDetails, 0, sizeof(nuFileDetails));
if (fromRsrc) {
nuFileDetails.threadID = kNuThreadIDRsrcFork;
} else {
nuFileDetails.threadID = kNuThreadIDDataFork;
}
nuFileDetails.storageNameMOR = argv[1];
nuFileDetails.fileSysID = kNuFileSysUnknown;
nuFileDetails.fileSysInfo = (short) kFssep;
nuFileDetails.access = kUnlocked;
/* fileType, extraType, storageType, dates */
return NuAddFile(ExerciserState_GetNuArchive(pState), argv[1],
&nuFileDetails, fromRsrc, NULL);
}
/*
* ar - add an empty record
*/
static NuError AddRecordFunc(ExerciserState* pState, int argc, char** argv)
{
NuError err;
NuRecordIdx recordIdx;
NuFileDetails nuFileDetails;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 2);
memset(&nuFileDetails, 0, sizeof(nuFileDetails));
nuFileDetails.threadID = 0; /* irrelevant */
nuFileDetails.storageNameMOR = argv[1];
nuFileDetails.fileSysID = kNuFileSysUnknown;
nuFileDetails.fileSysInfo = (short) kFssep;
nuFileDetails.access = kUnlocked;
/* fileType, extraType, storageType, dates */
err = NuAddRecord(ExerciserState_GetNuArchive(pState),
&nuFileDetails, &recordIdx);
if (err == kNuErrNone)
printf("Exerciser: success, new recordIdx=%u\n", recordIdx);
return err;
}
/*
* at - add thread to record
*/
static NuError AddThreadFunc(ExerciserState* pState, int argc, char** argv)
{
NuError err;
NuDataSource* pDataSource = NULL;
char* lineBuf = NULL;
long ourLen, maxLen;
NuThreadID threadID;
NuThreadIdx threadIdx;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 3);
lineBuf = (char*)malloc(kNiceLineLen);
assert(lineBuf != NULL);
threadID = strtol(argv[2], NULL, 0);
if (NuThreadIDGetClass(threadID) == kNuThreadClassData) {
/* load data from a file on disk */
maxLen = 0;
err = GetLine("Enter filename", lineBuf, kNiceLineLen);
if (err != kNuErrNone)
goto bail;
if (!lineBuf[0]) {
fprintf(stderr, "Invalid filename\n");
err = kNuErrInvalidArg;
goto bail;
}
err = NuCreateDataSourceForFile(kNuThreadFormatUncompressed,
0, lineBuf, false, &pDataSource);
if (err != kNuErrNone) {
fprintf(stderr,
"Exerciser: file data source create failed (err=%d)\n", err);
goto bail;
}
} else {
if (threadID == kNuThreadIDFilename || threadID == kNuThreadIDComment) {
/* select the buffer pre-size */
err = GetLine("Enter max buffer size", lineBuf, kNiceLineLen);
if (err != kNuErrNone)
goto bail;
maxLen = strtol(lineBuf, NULL, 0);
if (maxLen <= 0) {
fprintf(stderr, "Bad length\n");
err = kNuErrInvalidArg;
goto bail;
}
} else {
maxLen = 0;
}
err = GetLine("Enter the thread contents", lineBuf, kNiceLineLen);
if (err != kNuErrNone)
goto bail;
ourLen = strlen(lineBuf);
/* create a data source from the buffer */
err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed,
maxLen, (uint8_t*)lineBuf, 0, ourLen, FreeCallback,
&pDataSource);
if (err != kNuErrNone) {
fprintf(stderr,
"Exerciser: buffer data source create failed (err=%d)\n", err);
goto bail;
}
lineBuf = NULL; /* now owned by the library */
}
err = NuAddThread(ExerciserState_GetNuArchive(pState),
strtol(argv[1], NULL, 0), threadID, pDataSource, &threadIdx);
if (err == kNuErrNone) {
pDataSource = NULL; /* library owns it now */
printf("Exerciser: success; function returned threadIdx=%u\n",
threadIdx);
}
bail:
NuFreeDataSource(pDataSource);
if (lineBuf != NULL)
free(lineBuf);
return err;
}
/*
* cl - close archive
*/
static NuError CloseFunc(ExerciserState* pState, int argc, char** argv)
{
NuError err;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 1);
err = NuClose(ExerciserState_GetNuArchive(pState));
if (err == kNuErrNone) {
ExerciserState_SetNuArchive(pState, NULL);
ExerciserState_SetArchivePath(pState, NULL);
}
return err;
}
/*
* d - delete all records (selection-filtered)
*/
static NuError DeleteFunc(ExerciserState* pState, int argc, char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 1);
NuSetSelectionFilter(ExerciserState_GetNuArchive(pState), SelectionFilter);
return NuDelete(ExerciserState_GetNuArchive(pState));
}
/*
* dr - delete record
*/
static NuError DeleteRecordFunc(ExerciserState* pState, int argc, char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 2);
return NuDeleteRecord(ExerciserState_GetNuArchive(pState),
strtol(argv[1], NULL, 0));
}
/*
* dt - delete thread
*/
static NuError DeleteThreadFunc(ExerciserState* pState, int argc, char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 2);
return NuDeleteThread(ExerciserState_GetNuArchive(pState),
strtol(argv[1], NULL, 0));
}
/*
* e - extract all files (selection-filtered)
*/
static NuError ExtractFunc(ExerciserState* pState, int argc, char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 1);
NuSetSelectionFilter(ExerciserState_GetNuArchive(pState), SelectionFilter);
return NuExtract(ExerciserState_GetNuArchive(pState));
}
/*
* er - extract record
*/
static NuError ExtractRecordFunc(ExerciserState* pState, int argc, char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 2);
return NuExtractRecord(ExerciserState_GetNuArchive(pState),
strtol(argv[1], NULL, 0));
}
/*
* et - extract thread
*/
static NuError ExtractThreadFunc(ExerciserState* pState, int argc, char** argv)
{
NuError err;
NuDataSink* pDataSink = NULL;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 3);
err = NuCreateDataSinkForFile(true, kNuConvertOff, argv[2], kFssep,
&pDataSink);
if (err != kNuErrNone) {
fprintf(stderr, "Exerciser: data sink create failed\n");
goto bail;
}
err = NuExtractThread(ExerciserState_GetNuArchive(pState),
strtol(argv[1], NULL, 0), pDataSink);
/* fall through with err */
bail:
NuFreeDataSink(pDataSink);
return err;
}
/*
* fl - flush changes to archive
*/
static NuError FlushFunc(ExerciserState* pState, int argc, char** argv)
{
NuError err;
uint32_t flushStatus;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 1);
err = NuFlush(ExerciserState_GetNuArchive(pState), &flushStatus);
if (err != kNuErrNone)
printf("Exerciser: flush failed, status flags=0x%04x\n", flushStatus);
return err;
}
/*
* gev - get value
*
* Currently takes numeric arguments. We could be nice and accept the
* things like "IgnoreCRC" for kNuValueIgnoreCRC, but not yet.
*/
static NuError GetValueFunc(ExerciserState* pState, int argc, char** argv)
{
NuError err;
NuValue value;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 2);
err = NuGetValue(ExerciserState_GetNuArchive(pState),
(NuValueID) strtol(argv[1], NULL, 0), &value);
if (err == kNuErrNone)
printf(" --> %u\n", value);
return err;
}
/*
* gmh - get master header
*/
static NuError GetMasterHeaderFunc(ExerciserState* pState, int argc,
char** argv)
{
NuError err;
const NuMasterHeader* pMasterHeader;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 1);
err = NuGetMasterHeader(ExerciserState_GetNuArchive(pState),
&pMasterHeader);
if (err == kNuErrNone) {
printf("Exerciser: success (version=%u, totalRecords=%u, EOF=%u)\n",
pMasterHeader->mhMasterVersion, pMasterHeader->mhTotalRecords,
pMasterHeader->mhMasterEOF);
}
return err;
}
/*
* gr - get record attributes
*/
static NuError GetRecordFunc(ExerciserState* pState, int argc, char** argv)
{
NuError err;
const NuRecord* pRecord;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 2);
err = NuGetRecord(ExerciserState_GetNuArchive(pState),
strtol(argv[1], NULL, 0), &pRecord);
if (err == kNuErrNone) {
printf("Exerciser: success, call returned:\n");
printf("\tfileSysID : %d\n", pRecord->recFileSysID);
printf("\tfileSysInfo : 0x%04x ('%c')\n", pRecord->recFileSysInfo,
NuGetSepFromSysInfo(pRecord->recFileSysInfo));
printf("\taccess : 0x%02x\n", pRecord->recAccess);
printf("\tfileType : 0x%04x\n", pRecord->recFileType);
printf("\textraType : 0x%04x\n", pRecord->recExtraType);
printf("\tcreateWhen : ...\n");
printf("\tmodWhen : ...\n"); /* too lazy */
printf("\tarchiveWhen : ...\n");
}
return err;
}
/*
* grin - get record idx by name
*/
static NuError GetRecordIdxByNameFunc(ExerciserState* pState, int argc,
char** argv)
{
NuError err;
NuRecordIdx recIdx;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 2);
err = NuGetRecordIdxByName(ExerciserState_GetNuArchive(pState),
argv[1], &recIdx);
if (err == kNuErrNone)
printf("Exerciser: success, returned recordIdx=%u\n", recIdx);
return err;
}
/*
* grip - get record idx by position
*/
static NuError GetRecordIdxByPositionFunc(ExerciserState* pState, int argc,
char** argv)
{
NuError err;
NuRecordIdx recIdx;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 2);
err = NuGetRecordIdxByPosition(ExerciserState_GetNuArchive(pState),
strtol(argv[1], NULL, 0), &recIdx);
if (err == kNuErrNone)
printf("Exerciser: success, returned recordIdx=%u\n", recIdx);
return err;
}
/*
* ocrw - open/create read-write
*/
static NuError OpenCreateReadWriteFunc(ExerciserState* pState, int argc,
char** argv)
{
NuError err;
NuArchive* pArchive;
assert(ExerciserState_GetNuArchive(pState) == NULL);
assert(argc == 2);
err = NuOpenRW(argv[1], kTempFile, kNuOpenCreat|kNuOpenExcl, &pArchive);
if (err == kNuErrNone) {
ExerciserState_SetNuArchive(pState, pArchive);
ExerciserState_SetArchivePath(pState, argv[1]);
}
return err;
}
/*
* oro - open read-only
*/
static NuError OpenReadOnlyFunc(ExerciserState* pState, int argc, char** argv)
{
NuError err;
NuArchive* pArchive;
assert(ExerciserState_GetNuArchive(pState) == NULL);
assert(argc == 2);
err = NuOpenRO(argv[1], &pArchive);
if (err == kNuErrNone) {
ExerciserState_SetNuArchive(pState, pArchive);
ExerciserState_SetArchivePath(pState, argv[1]);
}
return err;
}
/*
* ors - open streaming read-only
*/
static NuError OpenStreamingReadOnlyFunc(ExerciserState* pState, int argc,
char** argv)
{
NuError err;
NuArchive* pArchive;
FILE* fp = NULL;
assert(ExerciserState_GetNuArchive(pState) == NULL);
assert(argc == 2);
if ((fp = fopen(argv[1], kNuFileOpenReadOnly)) == NULL) {
err = errno ? (NuError)errno : kNuErrGeneric;
fprintf(stderr, "Exerciser: unable to open '%s'\n", argv[1]);
} else {
err = NuStreamOpenRO(fp, &pArchive);
if (err == kNuErrNone) {
ExerciserState_SetNuArchive(pState, pArchive);
ExerciserState_SetArchivePath(pState, argv[1]);
fp = NULL;
}
}
if (fp != NULL)
fclose(fp);
return err;
}
/*
* orw - open read-write
*/
static NuError OpenReadWriteFunc(ExerciserState* pState, int argc, char** argv)
{
NuError err;
NuArchive* pArchive;
assert(ExerciserState_GetNuArchive(pState) == NULL);
assert(argc == 2);
err = NuOpenRW(argv[1], kTempFile, 0, &pArchive);
if (err == kNuErrNone) {
ExerciserState_SetNuArchive(pState, pArchive);
ExerciserState_SetArchivePath(pState, argv[1]);
}
return err;
}
/*
* p - print
*/
static NuError PrintFunc(ExerciserState* pState, int argc, char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 1);
return NuContents(ExerciserState_GetNuArchive(pState), PrintEntry);
}
/*
* pd - print debug
*/
static NuError PrintDebugFunc(ExerciserState* pState, int argc, char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 1);
return NuDebugDumpArchive(ExerciserState_GetNuArchive(pState));
}
/*
* re - rename record
*/
static NuError RenameFunc(ExerciserState* pState, int argc, char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 4);
return NuRename(ExerciserState_GetNuArchive(pState),
strtol(argv[1], NULL, 0), argv[2], argv[3][0]);
}
/*
* sec - set error callback
*
* Use an error handler callback.
*/
static NuError SetErrorCallbackFunc(ExerciserState* pState, int argc,
char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 1);
NuSetErrorHandler(ExerciserState_GetNuArchive(pState), ErrorHandler);
return kNuErrNone;
}
/*
* sev - set value
*
* Currently takes numeric arguments.
*/
static NuError SetValueFunc(ExerciserState* pState, int argc, char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 3);
return NuSetValue(ExerciserState_GetNuArchive(pState),
(NuValueID) strtol(argv[1], NULL, 0), strtol(argv[2], NULL, 0));
}
/*
* sra - set record attributes
*
* Right now I'm only allowing changes to file type and aux type. This
* could be adapted to do more easily, but the command handler has a
* rigid notion of how many arguments each function should have, so
* you'd need to list all of them every time.
*/
static NuError SetRecordAttrFunc(ExerciserState* pState, int argc, char** argv)
{
NuError err;
const NuRecord* pRecord;
NuRecordAttr recordAttr;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 4);
err = NuGetRecord(ExerciserState_GetNuArchive(pState),
strtol(argv[1], NULL, 0), &pRecord);
if (err != kNuErrNone)
return err;
printf("Exerciser: NuGetRecord succeeded, calling NuSetRecordAttr\n");
NuRecordCopyAttr(&recordAttr, pRecord);
recordAttr.fileType = strtol(argv[2], NULL, 0);
recordAttr.extraType = strtol(argv[3], NULL, 0);
/*recordAttr.fileSysInfo = ':';*/
return NuSetRecordAttr(ExerciserState_GetNuArchive(pState),
strtol(argv[1], NULL, 0), &recordAttr);
}
/*
* t - test archive
*/
static NuError TestFunc(ExerciserState* pState, int argc, char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 1);
return NuTest(ExerciserState_GetNuArchive(pState));
}
/*
* tr - test record
*/
static NuError TestRecordFunc(ExerciserState* pState, int argc, char** argv)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 2);
return NuTestRecord(ExerciserState_GetNuArchive(pState),
strtol(argv[1], NULL, 0));
}
/*
* upt - update pre-sized thread
*/
static NuError UpdatePresizedThreadFunc(ExerciserState* pState, int argc,
char** argv)
{
NuError err;
NuDataSource* pDataSource = NULL;
char* lineBuf = NULL;
long ourLen;
int32_t maxLen;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != NULL);
assert(argc == 2);
lineBuf = (char*)malloc(kNiceLineLen);
assert(lineBuf != NULL);
err = GetLine("Enter data for thread", lineBuf, kNiceLineLen);
if (err != kNuErrNone)
goto bail;
ourLen = strlen(lineBuf);
/* use "ourLen" for both buffer len and data len */
err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed,
ourLen, (uint8_t*)lineBuf, 0, ourLen, FreeCallback,
&pDataSource);
if (err != kNuErrNone) {
fprintf(stderr, "Exerciser: data source create failed (err=%d)\n",
err);
goto bail;
}
lineBuf = NULL; /* now owned by the library */
err = NuUpdatePresizedThread(ExerciserState_GetNuArchive(pState),
strtol(argv[1], NULL, 0), pDataSource, &maxLen);
if (err == kNuErrNone)
printf("Exerciser: success; function returned maxLen=%d\n", maxLen);
bail:
NuFreeDataSource(pDataSource);
if (lineBuf != NULL)
free(lineBuf);
return err;
}
/*
* Command table. This drives the user interface.
*/
/* flags for the CommandTable */
#define kFlagArchiveReq (1L) /* must have archive open */
#define kFlagNoArchiveReq (1L<<1) /* must NOT have archive open */
/* command set */
static const struct {
const char* commandStr;
CommandFunc func;
int expectedArgCount;
const char* argumentList;
uint32_t flags;
const char* description;
} gCommandTable[] = {
{ "--- exerciser commands ---", HelpFunc, 0, "", 0,
"" },
{ "?", HelpFunc, 0, "", 0,
"Show help" },
{ "h", HelpFunc, 0, "", 0,
"Show help" },
{ "q", QuitFunc, 0, "", 0,
"Quit program (will abort un-flushed changes)" },
{ "--- archive commands ---", HelpFunc, 0, "", 0,
"" },
{ "ab", AbortFunc, 0, "", kFlagArchiveReq,
"Abort current changes" },
{ "af", AddFileFunc, 2, "filename fromRsrc", kFlagArchiveReq,
"Add file" },
{ "ar", AddRecordFunc, 1, "storageName", kFlagArchiveReq,
"Add record" },
{ "at", AddThreadFunc, 2, "recordIdx threadID", kFlagArchiveReq,
"Add thread to record" },
{ "cl", CloseFunc, 0, "", kFlagArchiveReq,
"Close archive after flushing any changes" },
{ "d", DeleteFunc, 0, "", kFlagArchiveReq,
"Delete all records" },
{ "dr", DeleteRecordFunc, 1, "recordIdx", kFlagArchiveReq,
"Delete record" },
{ "dt", DeleteThreadFunc, 1, "threadIdx", kFlagArchiveReq,
"Delete thread" },
{ "e", ExtractFunc, 0, "", kFlagArchiveReq,
"Extract all files" },
{ "er", ExtractRecordFunc, 1, "recordIdx", kFlagArchiveReq,
"Extract record" },
{ "et", ExtractThreadFunc, 2, "threadIdx filename", kFlagArchiveReq,
"Extract thread" },
{ "fl", FlushFunc, 0, "", kFlagArchiveReq,
"Flush changes" },
{ "gev", GetValueFunc, 1, "ident", kFlagArchiveReq,
"Get value" },
{ "gmh", GetMasterHeaderFunc, 0, "", kFlagArchiveReq,
"Get master header" },
{ "gr", GetRecordFunc, 1, "recordIdx", kFlagArchiveReq,
"Get record" },
{ "grin", GetRecordIdxByNameFunc, 1, "name", kFlagArchiveReq,
"Get recordIdx by name" },
{ "grip", GetRecordIdxByPositionFunc, 1, "position", kFlagArchiveReq,
"Get recordIdx by position" },
{ "ocrw", OpenCreateReadWriteFunc, 1, "filename", kFlagNoArchiveReq,
"Open/create archive read-write" },
{ "oro", OpenReadOnlyFunc, 1, "filename", kFlagNoArchiveReq,
"Open archive read-only" },
{ "ors", OpenStreamingReadOnlyFunc, 1, "filename", kFlagNoArchiveReq,
"Open archive streaming read-only" },
{ "orw", OpenReadWriteFunc, 1, "filename", kFlagNoArchiveReq,
"Open archive read-write" },
{ "p", PrintFunc, 0, "", kFlagArchiveReq,
"Print archive contents" },
{ "pd", PrintDebugFunc, 0, "", kFlagArchiveReq,
"Print debugging output (if available)" },
{ "re", RenameFunc, 3, "recordIdx name sep", kFlagArchiveReq,
"Rename record" },
{ "sec", SetErrorCallbackFunc, 0, "", kFlagArchiveReq,
"Set error callback" },
{ "sev", SetValueFunc, 2, "ident value", kFlagArchiveReq,
"Set value" },
{ "sra", SetRecordAttrFunc, 3, "recordIdx type aux", kFlagArchiveReq,
"Set record attributes" },
{ "t", TestFunc, 0, "", kFlagArchiveReq,
"Test archive" },
{ "tr", TestRecordFunc, 1, "recordIdx", kFlagArchiveReq,
"Test record" },
{ "upt", UpdatePresizedThreadFunc, 1, "threadIdx", kFlagArchiveReq,
"Update pre-sized thread" },
};
#define kMaxArgs 4
/*
* Display a summary of available commands.
*/
static NuError HelpFunc(ExerciserState* pState, int argc, char** argv)
{
int i;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
printf("\nAvailable commands:\n");
for (i = 0; i < (int)NELEM(gCommandTable); i++) {
printf(" %-4s %-21s %s\n",
gCommandTable[i].commandStr,
gCommandTable[i].argumentList,
gCommandTable[i].description);
}
return kNuErrNone;
}
/*
* ===========================================================================
* Control
* ===========================================================================
*/
static const char* kWhitespace = " \t\n";
/*
* Parse a command from the user.
*
* "lineBuf" will be mangled. On success, "pFunc", "pArgc", and "pArgv"
* will receive the results.
*/
static NuError ParseLine(char* lineBuf, ExerciserState* pState,
CommandFunc* pFunc, int* pArgc, char*** pArgv)
{
NuError err = kNuErrSyntax;
char* command;
char* cp;
int i;
/*
* Parse the strings.
*/
command = strtok(lineBuf, kWhitespace);
if (command == NULL) {
/* no command; the user probably just hit "enter" on a blank line */
*pFunc = NothingFunc;
*pArgc = 0;
*pArgv = NULL;
err = kNuErrNone;
goto bail;
}
/* no real need to be flexible; add 1 for command and one for NULL */
*pArgv = (char**) malloc(sizeof(char*) * (kMaxArgs+2));
(*pArgv)[0] = command;
*pArgc = 1;
cp = strtok(NULL, kWhitespace);
while (cp != NULL) {
if (*pArgc >= kMaxArgs+1) {
printf("ERROR: too many arguments\n");
goto bail;
}
(*pArgv)[*pArgc] = cp;
(*pArgc)++;
cp = strtok(NULL, kWhitespace);
}
assert(*pArgc < kMaxArgs+2);
(*pArgv)[*pArgc] = NULL;
/*
* Look up the command.
*/
for (i = 0; i < (int)NELEM(gCommandTable); i++) {
if (strcmp(command, gCommandTable[i].commandStr) == 0)
break;
}
if (i == NELEM(gCommandTable)) {
printf("ERROR: unrecognized command\n");
goto bail;
}
*pFunc = gCommandTable[i].func;
/*
* Check arguments and flags.
*/
if (*pArgc -1 != gCommandTable[i].expectedArgCount) {
printf("ERROR: expected %d args, found %d\n",
gCommandTable[i].expectedArgCount, *pArgc -1);
goto bail;
}
if (gCommandTable[i].flags & kFlagArchiveReq) {
if (ExerciserState_GetNuArchive(pState) == NULL) {
printf("ERROR: must have an archive open\n");
goto bail;
}
}
if (gCommandTable[i].flags & kFlagNoArchiveReq) {
if (ExerciserState_GetNuArchive(pState) != NULL) {
printf("ERROR: an archive is already open\n");
goto bail;
}
}
/*
* Looks good!
*/
err = kNuErrNone;
bail:
return err;
}
/*
* Interpret commands, do clever things.
*/
static NuError CommandLoop(void)
{
NuError err = kNuErrNone;
ExerciserState* pState = ExerciserState_New();
CommandFunc func;
char lineBuf[128];
int argc;
char** argv = NULL;
while (1) {
printf("\nEnter command (%s)> ", ExerciserState_GetArchiveFile(pState));
fflush(stdout);
if (fgets(lineBuf, sizeof(lineBuf), stdin) == NULL) {
printf("\n");
break;
}
if (argv != NULL) {
free(argv);
argv = NULL;
}
func = NULL; /* sanity check */
err = ParseLine(lineBuf, pState, &func, &argc, &argv);
if (err != kNuErrNone)
continue;
assert(func != NULL);
if (func == QuitFunc)
break;
err = (*func)(pState, argc, argv);
if (err < 0)
printf("Exerciser: received error %d (%s)\n", err, NuStrError(err));
else if (err > 0)
printf("Exerciser: received error %d\n", err);
if (argv != NULL) {
free(argv);
argv = NULL;
}
}
if (ExerciserState_GetNuArchive(pState) != NULL) {
/* ought to query the archive before saying something like this... */
printf("Exerciser: aborting any un-flushed changes in archive %s\n",
ExerciserState_GetArchivePath(pState));
(void) NuAbort(ExerciserState_GetNuArchive(pState));
err = NuClose(ExerciserState_GetNuArchive(pState));
if (err != kNuErrNone)
printf("Exerciser: got error %d closing archive\n", err);
ExerciserState_SetNuArchive(pState, NULL);
}
if (pState != NULL)
ExerciserState_Free(pState);
if (argv != NULL)
free(argv);
return kNuErrNone;
}
/*
* Main entry point.
*
* We don't currently take any arguments, so this is pretty straightforward.
*/
int main(void)
{
NuError result;
int32_t majorVersion, minorVersion, bugVersion;
const char* nufxLibDate;
const char* nufxLibFlags;
(void) NuGetVersion(&majorVersion, &minorVersion, &bugVersion,
&nufxLibDate, &nufxLibFlags);
printf("NufxLib exerciser, linked with NufxLib v%d.%d.%d [%s]\n\n",
majorVersion, minorVersion, bugVersion, nufxLibFlags);
printf("Use 'h' or '?' for help, 'q' to quit.\n");
/* stuff useful when debugging lots */
if (unlink(kTempFile) == 0)
fprintf(stderr, "NOTE: whacked exer-temp\n");
if (unlink("new.shk") == 0)
fprintf(stderr, "NOTE: whacked new.shk\n");
#if defined(HAS_MALLOC_CHECK_) && !defined(USE_DMALLOC)
/*
* This is really nice to have on Linux and any other system that
* uses the GNU libc/malloc stuff. It slows things down, but it
* tells you when you do something dumb with malloc/realloc/free.
* (Solaris 2.7 has a similar feature that is enabled by setting the
* environment variable LD_PRELOAD to include watchmalloc.so. Other
* OSs and 3rd-party malloc packages may have similar features.)
*
* This environment variable must be set when the program is launched.
* Tweaking the environment within the program has no effect.
*
* Now that the Linux world has "valgrind", this is probably
* unnecessary.
*/
{
char* debugSet = getenv("MALLOC_CHECK_");
if (debugSet == NULL)
printf("WARNING: MALLOC_CHECK_ not enabled\n\n");
}
#endif
result = CommandLoop();
exit(result != kNuErrNone);
}