nulib2/nufxlib-0/samples/Exerciser.c
Andy McFadden 1169554de3 Changed the DataSource API to take resource release callback pointers
instead of a "doClose" argument.  NufxLib should no longer try to free
anything allocated by the application (or vice-versa).

The DataSource "copy" function now does refcounting instead of copying.
This was done as part of cleaning up some memory leaks in the DataSource
code.

The samples were all updated with the changes to the API (and the
occasional minor valgrind-inspired bug fix).
2003-02-09 01:53:51 +00:00

1372 lines
37 KiB
C

/*
* NuFX archive manipulation library
* Copyright (C) 2000-2003 by Andy McFadden, All Rights Reserved.
* This is free software; you can redistribute it and/or modify it under the
* terms of the GNU Library General Public 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"
/*
* ===========================================================================
* 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 == nil)
return nil;
pExerState->pArchive = nil;
pExerState->archivePath = nil;
pExerState->archiveFile = nil;
return pExerState;
}
void
ExerciserState_Free(ExerciserState* pExerState)
{
if (pExerState == nil)
return;
if (pExerState->pArchive != nil) {
printf("Exerciser: aborting open archive\n");
(void) NuAbort(pExerState->pArchive);
(void) NuClose(pExerState->pArchive);
}
if (pExerState->archivePath != nil)
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 != nil)
free(pExerState->archivePath);
if (newPath == nil) {
pExerState->archivePath = nil;
pExerState->archiveFile = nil;
} else {
pExerState->archivePath = strdup(newPath);
pExerState->archiveFile = strrchr(newPath, kFssep);
if (pExerState->archiveFile != nil)
pExerState->archiveFile++;
if (pExerState->archiveFile == nil || *pExerState->archiveFile == '\0')
pExerState->archiveFile = pExerState->archivePath;
}
}
inline const char*
ExerciserState_GetArchiveFile(const ExerciserState* pExerState)
{
if (pExerState->archiveFile == nil)
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 %ld: '%s'\n",
pRecord->recordIdx, pRecord->filename);
for (idx = 0; idx < (int) pRecord->recTotalThreads; idx++) {
const NuThread* pThread;
NuThreadID threadID;
const char* threadLabel;
pThread = NuGetThread(pRecord, idx);
assert(pThread != nil);
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 %ld - 0x%08lx (%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) == nil)
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->filename);
fflush(stdout);
if (fgets(buffer, sizeof(buffer), stdin) == nil)
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 == nil ? "(nil)" : pErrorStatus->message,
pErrorStatus->pathname, 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) == nil) {
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);
}
/*
* ===========================================================================
* 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) != nil);
assert(argc == 1);
return NuAbort(ExerciserState_GetNuArchive(pState));
}
/*
* af - add file to archive
*/
static NuError
AddFileFunc(ExerciserState* pState, int argc, char** argv)
{
NuFileDetails nuFileDetails;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != nil);
assert(argc == 2);
memset(&nuFileDetails, 0, sizeof(nuFileDetails));
nuFileDetails.threadID = kNuThreadIDDataFork;
nuFileDetails.storageName = argv[1];
nuFileDetails.fileSysID = kNuFileSysUnknown;
nuFileDetails.fileSysInfo = (short) kFssep;
nuFileDetails.access = kUnlocked;
/* fileType, extraType, storageType, dates */
return NuAddFile(ExerciserState_GetNuArchive(pState), argv[1],
&nuFileDetails, false, nil);
}
/*
* 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) != nil);
assert(argc == 2);
memset(&nuFileDetails, 0, sizeof(nuFileDetails));
nuFileDetails.threadID = 0; /* irrelevant */
nuFileDetails.storageName = 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=%ld\n", recordIdx);
return err;
}
/*
* at - add thread to record
*/
static NuError
AddThreadFunc(ExerciserState* pState, int argc, char** argv)
{
NuError err;
NuDataSource* pDataSource = nil;
char* lineBuf = nil;
long ourLen, maxLen;
NuThreadID threadID;
NuThreadIdx threadIdx;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != nil);
assert(argc == 3);
lineBuf = (char*)malloc(kNiceLineLen);
assert(lineBuf != nil);
threadID = strtol(argv[2], nil, 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, nil, 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, (unsigned char*)lineBuf, 0, ourLen, FreeCallback,
&pDataSource);
if (err != kNuErrNone) {
fprintf(stderr,
"Exerciser: buffer data source create failed (err=%d)\n", err);
goto bail;
}
lineBuf = nil; /* now owned by the library */
}
err = NuAddThread(ExerciserState_GetNuArchive(pState),
strtol(argv[1], nil, 0), threadID, pDataSource, &threadIdx);
if (err == kNuErrNone) {
pDataSource = nil; /* library owns it now */
printf("Exerciser: success; function returned threadIdx=%ld\n",
threadIdx);
}
bail:
NuFreeDataSource(pDataSource);
if (lineBuf != nil)
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) != nil);
assert(argc == 1);
err = NuClose(ExerciserState_GetNuArchive(pState));
if (err == kNuErrNone) {
ExerciserState_SetNuArchive(pState, nil);
ExerciserState_SetArchivePath(pState, nil);
}
return err;
}
/*
* d - delete all records (selection-filtered)
*/
static NuError
DeleteFunc(ExerciserState* pState, int argc, char** argv)
{
NuError err;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != nil);
assert(argc == 1);
err = NuSetSelectionFilter(ExerciserState_GetNuArchive(pState),
SelectionFilter);
if (err != kNuErrNone)
return err;
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) != nil);
assert(argc == 2);
return NuDeleteRecord(ExerciserState_GetNuArchive(pState),
strtol(argv[1], nil, 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) != nil);
assert(argc == 2);
return NuDeleteThread(ExerciserState_GetNuArchive(pState),
strtol(argv[1], nil, 0));
}
/*
* e - extract all files (selection-filtered)
*/
static NuError
ExtractFunc(ExerciserState* pState, int argc, char** argv)
{
NuError err;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != nil);
assert(argc == 1);
err = NuSetSelectionFilter(ExerciserState_GetNuArchive(pState),
SelectionFilter);
if (err != kNuErrNone)
return err;
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) != nil);
assert(argc == 2);
return NuExtractRecord(ExerciserState_GetNuArchive(pState),
strtol(argv[1], nil, 0));
}
/*
* et - extract thread
*/
static NuError
ExtractThreadFunc(ExerciserState* pState, int argc, char** argv)
{
NuError err;
NuDataSink* pDataSink = nil;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != nil);
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], nil, 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;
long flushStatus;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != nil);
assert(argc == 1);
err = NuFlush(ExerciserState_GetNuArchive(pState), &flushStatus);
if (err != kNuErrNone)
printf("Exerciser: flush failed, status flags=0x%04lx\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) != nil);
assert(argc == 2);
err = NuGetValue(ExerciserState_GetNuArchive(pState),
(NuValueID) strtol(argv[1], nil, 0), &value);
if (err == kNuErrNone)
printf(" --> %ld\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) != nil);
assert(argc == 1);
err = NuGetMasterHeader(ExerciserState_GetNuArchive(pState),
&pMasterHeader);
if (err == kNuErrNone) {
printf("Exerciser: success (version=%u, totalRecords=%lu, EOF=%lu)\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) != nil);
assert(argc == 2);
err = NuGetRecord(ExerciserState_GetNuArchive(pState),
strtol(argv[1], nil, 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%02lx\n", pRecord->recAccess);
printf("\tfileType : 0x%04lx\n", pRecord->recFileType);
printf("\textraType : 0x%04lx\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) != nil);
assert(argc == 2);
err = NuGetRecordIdxByName(ExerciserState_GetNuArchive(pState),
argv[1], &recIdx);
if (err == kNuErrNone)
printf("Exerciser: success, returned recordIdx=%ld\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) != nil);
assert(argc == 2);
err = NuGetRecordIdxByPosition(ExerciserState_GetNuArchive(pState),
strtol(argv[1], nil, 0), &recIdx);
if (err == kNuErrNone)
printf("Exerciser: success, returned recordIdx=%ld\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) == nil);
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) == nil);
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 = nil;
assert(ExerciserState_GetNuArchive(pState) == nil);
assert(argc == 2);
if ((fp = fopen(argv[1], kNuFileOpenReadOnly)) == nil) {
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 = nil;
}
}
if (fp != nil)
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) == nil);
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) != nil);
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) != nil);
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) != nil);
assert(argc == 4);
return NuRename(ExerciserState_GetNuArchive(pState),
strtol(argv[1], nil, 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) != nil);
assert(argc == 1);
return NuSetErrorHandler(ExerciserState_GetNuArchive(pState), ErrorHandler);
}
/*
* 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) != nil);
assert(argc == 3);
return NuSetValue(ExerciserState_GetNuArchive(pState),
(NuValueID) strtol(argv[1], nil, 0), strtol(argv[2], nil, 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) != nil);
assert(argc == 4);
err = NuGetRecord(ExerciserState_GetNuArchive(pState),
strtol(argv[1], nil, 0), &pRecord);
if (err != kNuErrNone)
return err;
printf("Exerciser: NuGetRecord succeeded, calling NuSetRecordAttr\n");
NuRecordCopyAttr(&recordAttr, pRecord);
recordAttr.fileType = strtol(argv[2], nil, 0);
recordAttr.extraType = strtol(argv[3], nil, 0);
/*recordAttr.fileSysInfo = ':';*/
return NuSetRecordAttr(ExerciserState_GetNuArchive(pState),
strtol(argv[1], nil, 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) != nil);
assert(argc == 1);
return NuTest(ExerciserState_GetNuArchive(pState));
}
/*
* upt - update pre-sized thread
*/
static NuError
UpdatePresizedThreadFunc(ExerciserState* pState, int argc, char** argv)
{
NuError err;
NuDataSource* pDataSource = nil;
char* lineBuf = nil;
long ourLen, maxLen;
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != nil);
assert(argc == 2);
lineBuf = (char*)malloc(kNiceLineLen);
assert(lineBuf != nil);
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, (unsigned char*)lineBuf, 0, ourLen, FreeCallback,
&pDataSource);
if (err != kNuErrNone) {
fprintf(stderr, "Exerciser: data source create failed (err=%d)\n",
err);
goto bail;
}
lineBuf = nil; /* now owned by the library */
err = NuUpdatePresizedThread(ExerciserState_GetNuArchive(pState),
strtol(argv[1], nil, 0), pDataSource, &maxLen);
if (err == kNuErrNone)
printf("Exerciser: success; function returned maxLen=%ld\n", maxLen);
bail:
NuFreeDataSource(pDataSource);
if (lineBuf != nil)
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;
unsigned long 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, 1, "filename", 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" },
{ "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 == nil) {
/* no command; the user probably just hit "enter" on a blank line */
*pFunc = NothingFunc;
*pArgc = 0;
*pArgv = nil;
err = kNuErrNone;
goto bail;
}
/* no real need to be flexible; add 1 for command and one for nil */
*pArgv = (char**) malloc(sizeof(char*) * (kMaxArgs+2));
(*pArgv)[0] = command;
*pArgc = 1;
cp = strtok(nil, kWhitespace);
while (cp != nil) {
if (*pArgc >= kMaxArgs+1) {
printf("ERROR: too many arguments\n");
goto bail;
}
(*pArgv)[*pArgc] = cp;
(*pArgc)++;
cp = strtok(nil, kWhitespace);
}
assert(*pArgc < kMaxArgs+2);
(*pArgv)[*pArgc] = nil;
/*
* 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) == nil) {
printf("ERROR: must have an archive open\n");
goto bail;
}
}
if (gCommandTable[i].flags & kFlagNoArchiveReq) {
if (ExerciserState_GetNuArchive(pState) != nil) {
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 = nil;
while (1) {
printf("\nEnter command (%s)> ", ExerciserState_GetArchiveFile(pState));
fflush(stdout);
if (fgets(lineBuf, sizeof(lineBuf), stdin) == nil) {
printf("\n");
break;
}
if (argv != nil) {
free(argv);
argv = nil;
}
func = nil; /* sanity check */
err = ParseLine(lineBuf, pState, &func, &argc, &argv);
if (err != kNuErrNone)
continue;
assert(func != nil);
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 != nil) {
free(argv);
argv = nil;
}
}
if (ExerciserState_GetNuArchive(pState) != nil) {
/* 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, nil);
}
if (pState != nil)
ExerciserState_Free(pState);
if (argv != nil)
free(argv);
return kNuErrNone;
}
/*
* Main entry point.
*
* We don't currently take any arguments, so this is pretty straightforward.
*/
int
main(void)
{
NuError result;
long majorVersion, minorVersion, bugVersion;
const char* nufxLibDate;
const char* nufxLibFlags;
(void) NuGetVersion(&majorVersion, &minorVersion, &bugVersion,
&nufxLibDate, &nufxLibFlags);
printf("NufxLib exerciser, linked with NufxLib v%ld.%ld.%ld [%s]\n\n",
majorVersion, minorVersion, bugVersion, nufxLibFlags);
/* 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.
*/
{
char* debugSet = getenv("MALLOC_CHECK_");
if (debugSet == nil)
printf("WARNING: MALLOC_CHECK_ not enabled\n\n");
}
#endif
result = CommandLoop();
exit(result != kNuErrNone);
}