ciderpress/nufxlib/samples/Exerciser.c
Andy McFadden 51b5f00f5c Large set of changes to restore CiderPress build.
CiderPress and MDC now compile, and execute far enough to open
their respective "about" boxes, but I doubt they'll do much
more than that.

* Switch from MBCS to UNICODE APIs

Microsoft switched to UTF-16 (by way of UCS-2) a long time ago,
and the support for MBCS seems to be getting phased out.  So it's
time to switch to wide strings.

This is a bit awkward for CiderPress because it works with disk
and file archives with 8-bit filenames, and I want NufxLib and
DiskImgLib to continue to work on Linux (which has largely taken
the UTF-8 approach to Unicode).  The libraries will continue to
work with 8-bit filenames, with CiderPress/MDC doing the
conversion at the appropriate point.

There were a couple of places where strings from a structure
handed back by one of the libraries were used directly in the UI,
or vice-versa, which is a problem because we have nowhere to
store the result of the conversion.  These currently have fixed
place-holder "xyzzy" strings.

All UI strings are now wide.

Various format strings now use "%ls" and "%hs" to explicitly
specify wide and narrow.  This doesn't play well with gcc, so
only the Windows-specific parts use those.

* Various updates to vcxproj files

The project-file conversion had some cruft that is now largely
gone.  The build now has a common output directory for the EXEs
and libraries, avoiding the old post-build copy steps.

* Added zlib 1.2.8 and nufxlib 2.2.2 source snapshots

The old "prebuilts" directory is now gone.  The libraries are now
built as part of building the apps.

I added a minimal set of files for zlib, and a full set for nufxlib.
The Linux-specific nufxlib goodies are included for the benefit of
the Linux utilities, which are currently broken (don't build).

* Replace symbols used for include guards

Symbols with a leading "__" are reserved.
2014-11-16 21:01:53 -08:00

1384 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"
/*
* ===========================================================================
* 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);
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) != 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)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != nil);
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) != 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)
{
(void) pState, (void) argc, (void) argv; /* shut up, gcc */
assert(ExerciserState_GetNuArchive(pState) != nil);
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) != 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);
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) != 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));
}
/*
* 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) != nil);
assert(argc == 2);
return NuTestRecord(ExerciserState_GetNuArchive(pState),
strtol(argv[1], nil, 0));
}
/*
* 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" },
{ "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 == 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);
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 == nil)
printf("WARNING: MALLOC_CHECK_ not enabled\n\n");
}
#endif
result = CommandLoop();
exit(result != kNuErrNone);
}