nulib2/nulib2/ArcUtils.c
Andy McFadden e2088e64d3 Distinguish Unicode and Mac OS Roman strings
NufxLib has historically made no effort to distinguish between
the character set used for filenames on the local disk, and for
filenames stored within the archive.  Now all Unicode filename
strings use the UNICHAR type and have "UNI" in the name, and all
Mac OS Roman strings have "MOR" in the name.  (The naming
convention makes it obvious when you're assigning the wrong thing;
on Linux both formats are char*, so the compiler won't tell you
if you get it wrong.)

The distinction is necessary because filesystems generally support
Unicode these days, but on Windows you need to use a separate
set of wide-character file I/O functions.  (On Linux it all works
with "narrow" strings, and the UTF-8 encoding is interpreted by
applications.)  The character set used for NuFX archive filenames
is MOR, matching what GS/OS + HFS supported, and we want to be able
to convert back and forth between MOR and a Unicode representation.

This change updates the various character types and string names,
adds conversion functions, and updates NuLib2 for proper execution
on Linux.  It does not include the (probably extensive) changes
required for Windows UTF-16 support.  Instead, the conversion
functions are no-ops, which should result in NuLib2 for Windows
continuing to behave in the same slightly broken way.

This adds "test-names", which exercises Unicode filenames a bit.
It will not pass on Win32.

Also, tweaked the Linux makefiles to have explicit dependencies,
rather than empty space and an expectation that "makedepend" exists.

Also, minor source code cleanups.

While this probably doesn't affect binary compatibility -- it's
mainly a matter of naming and string interpretation -- there's
enough going on that it should be considered an API revision, so
this updates the version to 3.0.0.
2015-01-02 17:14:34 -08:00

977 lines
28 KiB
C

/*
* NuLib2
* 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.
*
* Common archive-related utility functions.
*/
#include "NuLib2.h"
/*
* ===========================================================================
* Output pathnames
* ===========================================================================
*/
/*
* General-purpose output pathname filter, invoked by nufxlib via a
* callback. Normalizes the pathname to match the current OS requirements.
*
* If we're extracting to stdout, this fills in the "newFp" field instead.
*
* The buffer we return to the archive library will be overwritten the
* next time this function gets called. This is expected.
*/
static NuResult OutputPathnameFilter(NuArchive* pArchive, void* vproposal)
{
NuPathnameProposal* pathProposal = vproposal;
NulibState* pState;
const UNICHAR* newPathnameUNI;
NuRecordIdx renameFromIdx;
UNICHAR* renameToStrUNI;
UNICHAR* resultBufUNI;
Assert(pArchive != NULL);
(void) NuGetExtraData(pArchive, (void**) &pState);
Assert(pState != NULL);
/* handle extract-to-pipe */
if (NState_GetCommand(pState) == kCommandExtractToPipe) {
pathProposal->newDataSink = NState_GetPipeSink(pState);
NState_SetSuppressOutput(pState, true);
return kNuOK;
}
/*
* If they're trying to rename this file, do so now. We don't run
* the output through the normalizer; if the user typed it, assume
* that it's okay (and let the OS tell them if it isn't).
*/
renameToStrUNI = NState_GetRenameToStr(pState);
if (renameToStrUNI != NULL) {
renameFromIdx = NState_GetRenameFromIdx(pState);
if (renameFromIdx == pathProposal->pRecord->recordIdx) {
/* right source file, proceed with the rename */
NState_SetTempPathnameLen(pState, strlen(renameToStrUNI) +1);
resultBufUNI = NState_GetTempPathnameBuf(pState);
Assert(resultBufUNI != NULL);
strcpy(resultBufUNI, renameToStrUNI);
pathProposal->newPathnameUNI = resultBufUNI;
}
/* free up renameToStrUNI */
NState_SetRenameToStr(pState, NULL);
goto bail;
}
/*
* Convert the pathname into something suitable for the current OS.
*/
newPathnameUNI = NormalizePath(pState, pathProposal);
if (newPathnameUNI == NULL) {
ReportError(kNuErrNone, "unable to convert pathname");
return kNuAbort;
}
pathProposal->newPathnameUNI = newPathnameUNI;
bail:
return kNuOK;
}
/*
* ===========================================================================
* User input
* ===========================================================================
*/
/*
* Get a single character from the input.
*
* For portability, I'm just getting a line of input and keeping the
* first character. A fancier version would play with line disciplines
* so you wouldn't have to hit "return".
*/
static char GetReplyChar(char defaultReply)
{
char tmpBuf[32];
if (fgets(tmpBuf, sizeof(tmpBuf), stdin) == NULL)
return defaultReply;
if (tmpBuf[0] == '\n' || tmpBuf[0] == '\r')
return defaultReply;
return tmpBuf[0];
}
#define kMaxInputLen 128
/*
* Get a string back from the user.
*
* String returned should be freed by the caller.
*/
static char* GetReplyString(const char* prompt)
{
char buf[kMaxInputLen];
char* result;
int len;
printf("%s", prompt);
fflush(stdout);
result = fgets(buf, sizeof(buf), stdin);
if (result == NULL || feof(stdin) || ferror(stdin) || buf[0] == '\0' ||
buf[0] == '\n')
{
return NULL;
}
/* nuke the terminating '\n', which is lots of fun in filenames */
len = strlen(buf);
if (buf[len-1] == '\n')
buf[len-1] = '\0';
return strdup(buf);
}
/*
* Get a one-line comment from the user, of at most "maxLen" bytes.
*
* If the user enters a blank line, return "NULL".
*
* A pointer to a newly-allocated buffer is returned.
*/
char* GetSimpleComment(NulibState* pState, const char* pathname, int maxLen)
{
char* buf = NULL;
char* result;
int len;
buf = Malloc(maxLen);
if (buf == NULL)
return NULL;
printf("Enter one-line comment for '%s'\n: ", pathname);
fflush(stdout);
result = fgets(buf, maxLen, stdin);
if (result == NULL || feof(stdin) || ferror(stdin) || buf[0] == '\0' ||
buf[0] == '\n')
{
Free(buf);
return NULL;
}
/* nuke the terminating '\n', which we don't need */
len = strlen(buf);
if (buf[len-1] == '\n')
buf[len-1] = '\0';
return buf;
}
/*
* ===========================================================================
* Callbacks (progress updates, error handling, record selection)
* ===========================================================================
*/
#define kMaxDisplayLen 60
/*
* Returns "true" if the filespec in "spec" matches what's in "pRecord".
*
* (Someday "spec" might be a regexp.)
*/
static Boolean SpecMatchesRecord(NulibState* pState, const char* spec,
const NuRecord* pRecord)
{
#ifdef NU_CASE_SENSITIVE
if (NState_GetModRecurse(pState))
return (strncmp(spec, pRecord->filename, strlen(spec)) == 0);
else
return (strcmp(spec, pRecord->filename) == 0);
#else
if (NState_GetModRecurse(pState))
return (strncasecmp(spec, pRecord->filenameMOR, strlen(spec)) == 0);
else
return (strcasecmp(spec, pRecord->filenameMOR) == 0);
#endif
}
/*
* Determine whether the current record we're examining is described by
* the file specification given on the command line.
*
* If no filespec was provided, then all records are "specified".
*
* We pass the entire NuRecord in because we may want to allow
* extraction by criteria other than name, e.g. all text files or all
* files archived before a certain date.
*/
Boolean IsSpecified(NulibState* pState, const NuRecord* pRecord)
{
char* const* pSpec;
int i;
if (!NState_GetFilespecCount(pState))
return true;
pSpec = NState_GetFilespecPointer(pState);
for (i = NState_GetFilespecCount(pState); i > 0; i--, pSpec++) {
if (SpecMatchesRecord(pState, *pSpec, pRecord))
return true;
}
return false;
}
/*
* General-purpose selection filter, invoked as a callback. Compares the
* selection proposal with the filenames in "filespec".
*/
NuResult SelectionFilter(NuArchive* pArchive, void* vproposal)
{
const NuSelectionProposal* selProposal = vproposal;
NulibState* pState;
Assert(pArchive != NULL);
(void) NuGetExtraData(pArchive, (void**) &pState);
Assert(pState != NULL);
if (IsSpecified(pState, selProposal->pRecord)) {
NState_IncMatchCount(pState);
/* we don't get progress notifications for delete, so do it here */
if (NState_GetCommand(pState) == kCommandDelete) {
printf("Deleting %s\n", selProposal->pRecord->filenameMOR);
}
return kNuOK;
} else {
return kNuSkip;
}
}
/*
* Print a three-digit progress percentage; range is 0% to 100%.
*/
void PrintPercentage(uint32_t total, uint32_t progress)
{
uint32_t perc;
if (!total) {
/*printf(" %%");*/
printf(" ");
return;
}
if (total < 21474836) {
perc = (progress * 100 + 50) / total;
if (perc > 100)
perc = 100;
} else {
perc = progress / (total / 100);
if (perc > 100)
perc = 100;
}
printf("%3d%%", perc);
}
/*
* Show our progress, unless we're expanding to a pipe. Invoked as a
* callback by nufxlib.
*/
NuResult ProgressUpdater(NuArchive* pArchive, void* vProgress)
{
const NuProgressData* pProgress = vProgress;
NulibState* pState;
const char* percStr;
const char* actionStr;
char nameBuf[kMaxDisplayLen+1];
Boolean showName, eolConv;
Assert(pArchive != NULL);
(void) NuGetExtraData(pArchive, (void**) &pState);
Assert(pState != NULL);
if (NState_GetSuppressOutput(pState))
return kNuOK;
percStr = NULL;
showName = false;
eolConv = false;
switch (pProgress->operation) {
case kNuOpAdd:
switch (pProgress->state) {
case kNuProgressPreparing:
case kNuProgressOpening:
actionStr = "adding ";
showName = true;
break;
case kNuProgressAnalyzing:
actionStr = "analyzing ";
break;
case kNuProgressCompressing:
actionStr = "compressing";
break;
case kNuProgressStoring:
actionStr = "storing ";
break;
default:
actionStr = "?????? ";
break;
}
break;
case kNuOpExtract:
switch (pProgress->state) {
case kNuProgressPreparing:
case kNuProgressOpening:
actionStr = "extracting";
showName = true;
break;
case kNuProgressExpanding:
actionStr = "expanding ";
break;
case kNuProgressCopying:
actionStr = "extracting";
break;
default:
actionStr = "?????? ";
break;
}
if (pProgress->expand.convertEOL == kNuConvertOn)
eolConv = true;
break;
case kNuOpTest:
switch (pProgress->state) {
case kNuProgressPreparing:
case kNuProgressOpening:
showName = true;
/* no break */
case kNuProgressExpanding:
case kNuProgressCopying:
actionStr = "verifying";
break;
default:
actionStr = "?????? ";
break;
}
break;
default:
Assert(0);
actionStr = "????";
}
switch (pProgress->state) {
case kNuProgressDone:
actionStr = NULL;
percStr = "DONE\n";
break;
case kNuProgressSkipped:
actionStr = NULL;
percStr = "SKIP\n";
break;
case kNuProgressAborted: /* not currently possible in NuLib2 */
actionStr = NULL;
percStr = "CNCL\n";
break;
case kNuProgressFailed:
actionStr = NULL;
percStr = "FAIL\n";
break;
default:
break;
}
if (showName) {
/*
* Use "pathname" (whole thing) rather than "filename" (file part).
* Could also use "origPathname", but I like to show what they're
* getting instead of what they're giving.
*/
int len = strlen(pProgress->pathnameUNI);
if (len < sizeof(nameBuf)) {
strcpy(nameBuf, pProgress->pathnameUNI);
} else {
nameBuf[0] = nameBuf[1] = '.';
strncpy(nameBuf+2,
pProgress->pathnameUNI + len - (sizeof(nameBuf)-3),
sizeof(nameBuf)-3);
nameBuf[sizeof(nameBuf)-1] = '\0';
}
}
if (actionStr == NULL && percStr != NULL) {
printf("\r%s", percStr);
} else if (actionStr != NULL && percStr == NULL) {
if (percStr == NULL) {
putc('\r', stdout);
PrintPercentage(pProgress->uncompressedLength,
pProgress->uncompressedProgress);
if (showName)
printf(" %s%c %s", actionStr, eolConv ? '+' : ' ', nameBuf);
else
printf(" %s%c", actionStr, eolConv ? '+' : ' ');
}
} else {
Assert(0);
printf("????\n");
}
fflush(stdout);
/*printf(" %ld \n", pProgress->uncompressedProgress);*/
/*usleep(250000);*/
return kNuOK;
}
/*
* Decide whether or not to replace an existing file (during extract)
* or record (during add).
*/
static NuResult HandleReplaceExisting(NulibState* pState, NuArchive* pArchive,
const NuErrorStatus* pErrorStatus)
{
NuResult result = kNuOK;
char* renameName;
char reply;
Assert(pState != NULL);
Assert(pErrorStatus != NULL);
Assert(pErrorStatus->pathnameUNI != NULL);
Assert(pErrorStatus->canOverwrite);
Assert(pErrorStatus->canSkip);
Assert(pErrorStatus->canAbort);
if (NState_GetInputUnavailable(pState)) {
putc('\n', stdout);
ReportError(pErrorStatus->err, "Giving up");
result = kNuAbort;
goto bail;
}
while (1) {
printf("\n Replace %s? [y]es, [n]o, [A]ll, [N]one",
pErrorStatus->pathnameUNI);
if (pErrorStatus->canRename) /* renaming record adds not allowed */
printf(", [r]ename: ");
else
printf(": ");
fflush(stdout);
reply = GetReplyChar('n');
switch (reply) {
case 'y':
result = kNuOverwrite;
goto bail;
case 'n':
result = kNuSkip;
goto bail;
case 'A':
(void) NuSetValue(pArchive, kNuValueHandleExisting,
kNuAlwaysOverwrite);
result = kNuOverwrite;
goto bail;
case 'N':
(void) NuSetValue(pArchive, kNuValueHandleExisting,
kNuNeverOverwrite);
result = kNuSkip;
goto bail;
case 'r':
if (!pErrorStatus->canRename) {
printf("Response not acceptable\n");
break; /* continue in "while" loop */
}
renameName = GetReplyString("New name: ");
if (renameName == NULL)
break; /* continue in "while" loop */
if (pErrorStatus->pRecord == NULL) {
ReportError(kNuErrNone, "Unexpected NULL record");
break; /* continue in "while" loop */
}
NState_SetRenameFromIdx(pState,
pErrorStatus->pRecord->recordIdx);
NState_SetRenameToStr(pState, renameName);
result = kNuRename;
goto bail;
case 'q': /* stealth option to quit */
case 'Q':
result = kNuAbort;
goto bail;
default:
printf("Response not understood -- please use y/n/A/N/r\n");
break; /* continue in "while" loop */
}
}
bail:
return result;
}
/*
* Found a bad CRC... should we press onward?
*
* Note pErrorStatus->pathname may be NULL if the error was found in the
* master header or in the record header.
*/
static NuResult HandleBadCRC(NulibState* pState, NuArchive* pArchive,
const NuErrorStatus* pErrorStatus)
{
NuResult result = kNuOK;
char reply;
Assert(pState != NULL);
Assert(pErrorStatus != NULL);
if (NState_GetInputUnavailable(pState)) {
putc('\n', stderr);
ReportError(pErrorStatus->err, "Giving up");
result = kNuAbort;
goto bail;
}
while (1) {
if (pErrorStatus->pathnameUNI != NULL)
fprintf(stderr, "\n Found a bad CRC in %s\n",
pErrorStatus->pathnameUNI);
else
fprintf(stderr, "\n Found a bad CRC in the archive\n");
fprintf(stderr,
" Archive may be damaged, continue anyway? [y]es, [n]o: ");
fflush(stderr);
reply = GetReplyChar('n');
switch (reply) {
case 'y':
result = kNuIgnore;
goto bail;
case 'n':
case 'N':
result = kNuAbort;
goto bail;
default:
fprintf(stderr, "Response not understood -- please use y/n\n");
break; /* continue in "while" loop */
}
}
bail:
return result;
}
/*
* Tried to add a nonexistent file; continue?
*
* To make this happen, you need to:
* - nulib2 aer lots-of-files another-file
* - remove another-file while nulib2 is still processing lots-of-files
*
* The trick is to delete a file explicitly mentioned on the command line
* after NuFlush starts to do its thing. Otherwise, we're just using
* the system equivalent of readdir to scan a directory, so deleting a
* file just means it won't get added.
*/
static NuResult HandleAddNotFound(NulibState* pState, NuArchive* pArchive,
const NuErrorStatus* pErrorStatus)
{
NuResult result = kNuOK;
char reply;
Assert(pState != NULL);
Assert(pErrorStatus != NULL);
Assert(pErrorStatus->pathnameUNI != NULL);
if (NState_GetInputUnavailable(pState)) {
putc('\n', stdout);
ReportError(pErrorStatus->err, "Giving up");
result = kNuAbort;
goto bail;
}
while (1) {
fprintf(stderr, "\n Couldn't find %s, continue? [y]es, [n]o: ",
pErrorStatus->pathnameUNI);
fflush(stderr);
reply = GetReplyChar('n');
switch (reply) {
case 'y':
result = kNuSkip;
goto bail;
case 'n':
case 'N':
result = kNuAbort;
goto bail;
default:
fprintf(stderr, "Response not understood -- please use y/n\n");
break; /* continue in "while" loop */
}
}
bail:
return result;
}
/*
* Something failed, and the user may want to choose how to handle it.
* Invoked as a callback.
*/
NuResult ErrorHandler(NuArchive* pArchive, void* vErrorStatus)
{
const NuErrorStatus* pErrorStatus = vErrorStatus;
NulibState* pState;
NuResult result;
Assert(pArchive != NULL);
(void) NuGetExtraData(pArchive, (void**) &pState);
Assert(pState != NULL);
/* default action is to abort the current operation */
result = kNuAbort;
/*
* When extracting, the error handler callback gets invoked for several
* different problems because we might want to rename the file. Also,
* because extractions are done with "bulk" calls, returning an
* individual error message would be meaningless.
*
* When adding files, the NuAddFile and NuAddRecord calls can return
* immediate, specific results for a single add. The only reasons for
* calling here are to decide if an existing record should be replaced
* or not (without even an option to rename), or to decide what to do
* when the NuFlush call runs into a problem while adding a file.
*/
if (pErrorStatus->operation == kNuOpExtract) {
if (pErrorStatus->err == kNuErrFileExists) {
result = HandleReplaceExisting(pState, pArchive, pErrorStatus);
} else if (pErrorStatus->err == kNuErrNotNewer) {
/* if we were expecting this, it's okay */
if (NState_GetModFreshen(pState) || NState_GetModUpdate(pState)) {
printf("\rSKIP\n");
result = kNuSkip;
} else {
DBUG(("WEIRD one\n"));
}
} else if (pErrorStatus->err == kNuErrDuplicateNotFound) {
/* if we were expecting this, it's okay */
if (NState_GetModFreshen(pState)) {
printf("\rSKIP\n");
result = kNuSkip;
} else {
DBUG(("WEIRD two\n"));
}
}
} else if (pErrorStatus->operation == kNuOpAdd) {
if (pErrorStatus->err == kNuErrRecordExists) {
/* if they want to update or freshen, don't hassle them */
if (NState_GetModFreshen(pState) || NState_GetModUpdate(pState))
result = kNuOverwrite;
else
result = HandleReplaceExisting(pState, pArchive, pErrorStatus);
} else if (pErrorStatus->err == kNuErrFileNotFound) {
/* file was specified with NuAdd but removed during NuFlush */
result = HandleAddNotFound(pState, pArchive, pErrorStatus);
}
} else if (pErrorStatus->operation == kNuOpTest) {
if (pErrorStatus->err == kNuErrBadMHCRC ||
pErrorStatus->err == kNuErrBadRHCRC ||
pErrorStatus->err == kNuErrBadThreadCRC ||
pErrorStatus->err == kNuErrBadDataCRC)
{
result = HandleBadCRC(pState, pArchive, pErrorStatus);
}
}
return result;
}
#if 0
/*
* Display an error message.
*
* (This was just a test to see if it worked... NufxLib's default behavior
* is fine for NuLib2.)
*/
NuResult ErrorMessageHandler(NuArchive* pArchive, void* vErrorMessage)
{
const NuErrorMessage* pErrorMessage = (const NuErrorMessage*) vErrorMessage;
fprintf(stderr, "%s%d %3d %s:%d %s %s\n",
pArchive == NULL ? "(GLOBAL)" : "",
pErrorMessage->isDebug, pErrorMessage->err, pErrorMessage->file,
pErrorMessage->line, pErrorMessage->function, pErrorMessage->message);
return kNuOK;
}
#endif
/*
* ===========================================================================
* Open an archive
* ===========================================================================
*/
/* an archive name of "-" indicates we want to use stdin */
static const char* kStdinArchive = "-";
/*
* Determine whether the access bits on the record make it a read-only
* file or not.
*
* Uses a simplified view of the access flags.
*/
Boolean IsRecordReadOnly(const NuRecord* pRecord)
{
if (pRecord->recAccess == 0x21L || pRecord->recAccess == 0x01L)
return true;
else
return false;
}
/*
* Returns "true" if "archiveName" is the name we use to represent stdin.
*/
Boolean IsFilenameStdin(const char* archiveName)
{
Assert(archiveName != NULL);
return (strcmp(archiveName, kStdinArchive) == 0);
}
#define BailError(err) { if (err != kNuErrNone) goto bail; }
/*
* Open the archive in read-only mode. We use "file mode" for a file, or
* "streaming mode" for stdin.
*/
NuError OpenArchiveReadOnly(NulibState* pState)
{
NuError err;
NuArchive* pArchive = NULL;
Assert(pState != NULL);
if (IsFilenameStdin(NState_GetArchiveFilename(pState))) {
err = NuStreamOpenRO(stdin, &pArchive);
if (err != kNuErrNone) {
ReportError(err, "unable to open stdin archive");
if (err == kNuErrIsBinary2)
err = kNuErrNotNuFX; /* we can't seek back, so forget BNY */
goto bail;
}
/*
* Since the archive is on stdin, we can't ask the user questions.
* On a UNIX system we could open /dev/tty, but that's not portable,
* and I don't think archives on stdin are going to be popular
* enough to make this worth doing.
*/
NState_SetInputUnavailable(pState, true);
} else {
err = NuOpenRO(NState_GetArchiveFilename(pState), &pArchive);
if (err != kNuErrNone) {
if (err != kNuErrIsBinary2) {
ReportError(err, "unable to open '%s'",
NState_GetArchiveFilename(pState));
}
goto bail;
}
}
/* introduce them */
NState_SetNuArchive(pState, pArchive);
err = NuSetExtraData(pArchive, pState);
NuSetSelectionFilter(pArchive, SelectionFilter);
NuSetOutputPathnameFilter(pArchive, OutputPathnameFilter);
NuSetProgressUpdater(pArchive, ProgressUpdater);
NuSetErrorHandler(pArchive, ErrorHandler);
/*NuSetErrorMessageHandler(pArchive, ErrorMessageHandler);*/
/* set the EOL conversion */
if (NState_GetModConvertAll(pState))
err = NuSetValue(pArchive, kNuValueConvertExtractedEOL, kNuConvertOn);
else if (NState_GetModConvertText(pState))
err = NuSetValue(pArchive, kNuValueConvertExtractedEOL, kNuConvertAuto);
else
err = NuSetValue(pArchive, kNuValueConvertExtractedEOL, kNuConvertOff);
BailError(err);
/* if we're converting EOL, we probably ought to do this too */
err = NuSetValue(pArchive, kNuValueStripHighASCII, true);
BailError(err);
/* handle "-s" flag */
if (NState_GetModOverwriteExisting(pState)) {
err = NuSetValue(pArchive, kNuValueHandleExisting, kNuAlwaysOverwrite);
BailError(err);
}
/* handle "-f" and "-u" flags (this overrides "-s" during extraction) */
if (NState_GetModFreshen(pState) || NState_GetModUpdate(pState)) {
err = NuSetValue(pArchive, kNuValueOnlyUpdateOlder, true);
BailError(err);
}
if (NState_GetModFreshen(pState)) {
err = NuSetValue(pArchive, kNuValueHandleExisting, kNuMustOverwrite);
BailError(err);
}
DBUG(("--- enabling ShrinkIt compatibility mode\n"));
err = NuSetValue(pArchive, kNuValueMimicSHK, true);
BailError(err);
/* handy for some malformed archives */
err = NuSetValue(pArchive, kNuValueHandleBadMac, true);
BailError(err);
/*
DBUG(("--- enabling 'mask dataless' mode\n"));
err = NuSetValue(pArchive, kNuValueMaskDataless, true);
BailError(err);
*/
if (strcmp(SYSTEM_DEFAULT_EOL, "\r") == 0)
err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCR);
else if (strcmp(SYSTEM_DEFAULT_EOL, "\n") == 0)
err = NuSetValue(pArchive, kNuValueEOL, kNuEOLLF);
else if (strcmp(SYSTEM_DEFAULT_EOL, "\r\n") == 0)
err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCRLF);
else {
Assert(0);
err = kNuErrInternal;
ReportError(err, "Unknown SYSTEM_DEFAULT_EOL '%s'", SYSTEM_DEFAULT_EOL);
goto bail;
}
BailError(err);
bail:
if (err != kNuErrNone && pArchive != NULL) {
/* clean up */
(void) NuClose(pArchive);
NState_SetNuArchive(pState, NULL);
}
return err;
}
/*
* Open the archive in read-write mode, for purposes of adding, deleting,
* or updating files. We don't plan on extracting anything with this.
*
* "Streaming mode" isn't allowed.
*/
NuError OpenArchiveReadWrite(NulibState* pState)
{
NuError err = kNuErrNone;
NuArchive* pArchive = NULL;
char* tempName = NULL;
Assert(pState != NULL);
Assert(IsFilenameStdin(NState_GetArchiveFilename(pState)) == false);
tempName = MakeTempArchiveName(pState);
if (tempName == NULL)
goto bail;
DBUG(("TEMP NAME = '%s'\n", tempName));
err = NuOpenRW(NState_GetArchiveFilename(pState), tempName,
kNuOpenCreat, &pArchive);
if (err != kNuErrNone) {
ReportError(err, "unable to open '%s'",
NState_GetArchiveFilename(pState));
goto bail;
}
/* introduce them */
NState_SetNuArchive(pState, pArchive);
err = NuSetExtraData(pArchive, pState);
BailError(err);
NuSetSelectionFilter(pArchive, SelectionFilter);
NuSetProgressUpdater(pArchive, ProgressUpdater);
NuSetErrorHandler(pArchive, ErrorHandler);
/*NuSetErrorMessageHandler(pArchive, ErrorMessageHandler);*/
/* handle "-0" flag */
if (NState_GetModNoCompression(pState)) {
err = NuSetValue(pArchive, kNuValueDataCompression, kNuCompressNone);
BailError(err);
}
/* handle "-z" flag */
if (NState_GetModCompressDeflate(pState)) {
err = NuSetValue(pArchive, kNuValueDataCompression, kNuCompressDeflate);
BailError(err);
}
/* handle "-zz" flag */
if (NState_GetModCompressBzip2(pState)) {
err = NuSetValue(pArchive, kNuValueDataCompression, kNuCompressBzip2);
BailError(err);
}
/* handle "-f" and "-u" flags */
/* (BUG: if "-f" is set, creating a new archive is impossible) */
if (NState_GetModFreshen(pState) || NState_GetModUpdate(pState)) {
err = NuSetValue(pArchive, kNuValueOnlyUpdateOlder, true);
BailError(err);
}
if (NState_GetModFreshen(pState)) {
err = NuSetValue(pArchive, kNuValueHandleExisting, kNuMustOverwrite);
BailError(err);
}
DBUG(("--- enabling ShrinkIt compatibility mode\n"));
err = NuSetValue(pArchive, kNuValueMimicSHK, true);
BailError(err);
/* this probably isn't needed here, but set it anyway */
if (strcmp(SYSTEM_DEFAULT_EOL, "\r") == 0)
err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCR);
else if (strcmp(SYSTEM_DEFAULT_EOL, "\n") == 0)
err = NuSetValue(pArchive, kNuValueEOL, kNuEOLLF);
else if (strcmp(SYSTEM_DEFAULT_EOL, "\r\n") == 0)
err = NuSetValue(pArchive, kNuValueEOL, kNuEOLCRLF);
else {
Assert(0);
err = kNuErrInternal;
ReportError(err, "Unknown SYSTEM_DEFAULT_EOL '%s'", SYSTEM_DEFAULT_EOL);
goto bail;
}
BailError(err);
/*(void) NuSetValue(pArchive, kNuValueAllowDuplicates, true);*/
bail:
Free(tempName);
if (err != kNuErrNone && pArchive != NULL) {
/* clean up */
NuAbort(pArchive);
(void) NuClose(pArchive);
NState_SetNuArchive(pState, NULL);
}
return err;
}