mirror of
https://github.com/fadden/ciderpress.git
synced 2024-12-27 06:29:21 +00:00
51b5f00f5c
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.
1384 lines
38 KiB
C
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);
|
|
}
|
|
|