nulib2/nufxlib/MiscUtils.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

378 lines
11 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.
*
* Miscellaneous NufxLib utility functions.
*/
#include "NufxLibPriv.h"
/*
* Big fat hairy global. Unfortunately this is unavoidable.
*/
NuCallback gNuGlobalErrorMessageHandler = NULL;
static const char* kNufxLibName = "nufxlib";
/*
* strerror() equivalent for NufxLib errors.
*/
const char* Nu_StrError(NuError err)
{
/*
* BUG: this should be set up as per-thread storage in an MT environment.
* I would be more inclined to worry about this if I was expecting
* it to be used. So long as valid values are passed in, and the
* switch statement is kept up to date, we should never have cause
* to return this.
*
* An easier solution, should this present a problem for someone, would
* be to have the function return NULL or "unknown error" when the
* error value isn't recognized. I'd recommend leaving it as-is for
* debug builds, though, as it's helpful to know *which* error is not
* recognized.
*/
static char defaultMsg[32];
switch (err) {
case kNuErrNone:
return "(no error)";
case kNuErrGeneric:
return "NufxLib generic error";
case kNuErrInternal:
return "NufxLib internal error";
case kNuErrUsage:
return "NufxLib usage error";
case kNuErrSyntax:
return "NufxLib syntax error";
case kNuErrMalloc:
return "NufxLib malloc error";
case kNuErrInvalidArg:
return "Invalid arguments to NufxLib";
case kNuErrBadStruct:
return "Bad NuArchive structure passed to NufxLib";
case kNuErrBusy:
return "Attempted invalid reentrant call";
case kNuErrSkipped:
return "Skipped by user";
case kNuErrAborted:
return "Processing aborted";
case kNuErrRename:
return "User wants to rename file";
case kNuErrFile:
return "NufxLib trouble with a file";
case kNuErrFileOpen:
return "NufxLib unable to open file";
case kNuErrFileClose:
return "NufxLib unable to close file";
case kNuErrFileRead:
return "NufxLib unable to read file";
case kNuErrFileWrite:
return "NufxLib unable to write file";
case kNuErrFileSeek:
return "NufxLib unable to seek file";
case kNuErrFileExists:
return "File already exists";
case kNuErrFileNotFound:
return "No such file or directory";
case kNuErrFileStat:
return "Couldn't get file info";
case kNuErrFileNotReadable:
return "Read access denied";
case kNuErrDirExists:
return "Directory already exists";
case kNuErrNotDir:
return "Not a directory";
case kNuErrNotRegularFile:
return "Not a regular file";
case kNuErrDirCreate:
return "Unable to create directory";
case kNuErrOpenDir:
return "Unable to open directory";
case kNuErrReadDir:
return "Unable to read directory";
case kNuErrFileSetDate:
return "Unable to set file date";
case kNuErrFileSetAccess:
return "Unable to set file access";
case kNuErrFileAccessDenied:
return "Access denied";
case kNuErrNotNuFX:
return "Input is not a NuFX archive";
case kNuErrBadMHVersion:
return "Unrecognized Master Header version";
case kNuErrRecHdrNotFound:
return "Next record not found";
case kNuErrNoRecords:
return "No records in archive";
case kNuErrBadRecord:
return "Bad data in record";
case kNuErrBadMHCRC:
return "Bad Master Header CRC";
case kNuErrBadRHCRC:
return "Bad Record header CRC";
case kNuErrBadThreadCRC:
return "Bad Thread header CRC";
case kNuErrBadDataCRC:
return "Data CRC mismatch";
case kNuErrBadFormat:
return "Thread compression format unsupported";
case kNuErrBadData:
return "Bad data found";
case kNuErrBufferOverrun:
return "Buffer overrun";
case kNuErrBufferUnderrun:
return "Buffer underrun";
case kNuErrOutMax:
return "Output limit exceeded";
case kNuErrNotFound:
return "Not found";
case kNuErrRecordNotFound:
return "Record not found";
case kNuErrRecIdxNotFound:
return "RecordIdx not found";
case kNuErrThreadIdxNotFound:
return "ThreadIdx not found";
case kNuErrThreadIDNotFound:
return "ThreadID not found";
case kNuErrRecNameNotFound:
return "Record name not found";
case kNuErrRecordExists:
return "Record already exists";
case kNuErrAllDeleted:
return "Tried to delete all files";
case kNuErrArchiveRO:
return "Archive is in read-only mode";
case kNuErrModRecChange:
return "Attempt to alter a modified record";
case kNuErrModThreadChange:
return "Attempt to alter a modified thread";
case kNuErrThreadAdd:
return "Can't add conflicting threadID";
case kNuErrNotPreSized:
return "Operation only permitted on pre-sized threads";
case kNuErrPreSizeOverflow:
return "Data exceeds pre-sized thread size";
case kNuErrInvalidFilename:
return "Invalid filename";
case kNuErrLeadingFssep:
return "Storage name started with fssep char";
case kNuErrNotNewer:
return "New item wasn't newer than existing";
case kNuErrDuplicateNotFound:
return "Can only update an existing item";
case kNuErrDamaged:
return "Original archive may have been damaged";
case kNuErrIsBinary2:
return "This is a Binary II archive";
case kNuErrUnknownFeature:
return "Unknown feature";
case kNuErrUnsupFeature:
return "Feature not supported";
default:
sprintf(defaultMsg, "(error=%d)", err);
return defaultMsg;
}
}
#define kNuHeftyBufSize 256 /* all error messages should fit in this */
#define kNuExtraGoodies 8 /* leave room for "\0" and other trivial chars*/
/*
* Similar to perror(), but takes the error as an argument, and knows
* about NufxLib errors as well as system errors.
*
* Depending on the compiler, "file", "line", and "function" may be NULL/zero.
*
* Calling here with "pArchive"==NULL is allowed, but should only be done
* if the archive is inaccessible (perhaps because it failed to open). We
* can't invoke the error message callback if the pointer is NULL.
*/
void Nu_ReportError(NuArchive* pArchive, const char* file, int line,
const char* function, Boolean isDebug, NuError err,
const UNICHAR* format, ...)
{
NuErrorMessage errorMessage;
const char* msg;
va_list args;
char buf[kNuHeftyBufSize];
int count;
#if !defined(HAVE_SNPRINTF) && defined(SPRINTF_RETURNS_INT)
int cc;
#endif
Assert(format != NULL);
va_start(args, format);
#if defined(HAVE_VSNPRINTF) && defined(VSNPRINTF_DECLARED)
count = vsnprintf(buf, sizeof(buf)-kNuExtraGoodies, format, args);
#else
#ifdef SPRINTF_RETURNS_INT
count = vsprintf(buf, format, args);
#else
vsprintf(buf, format, args);
count = strlen(buf);
#endif
#endif
va_end(args);
Assert(count > 0);
if (count < 0)
goto bail;
/* print the error code data, if any */
if (err != kNuErrNone) {
/* we know we have room for ": ", because of kNuExtraGoodies */
strcpy(buf+count, ": ");
count += 2;
msg = NULL;
if (err >= 0)
msg = strerror(err);
if (msg == NULL)
msg = Nu_StrError(err);
#if defined(HAVE_SNPRINTF) && defined(SNPRINTF_DECLARED)
if (msg == NULL)
snprintf(buf+count, sizeof(buf) - count,
"(unknown err=%d)", err);
else
snprintf(buf+count, sizeof(buf) - count, "%s", msg);
#else
#ifdef SPRINTF_RETURNS_INT
if (msg == NULL)
cc = sprintf(buf+count, "(unknown err=%d)", err);
else
cc = sprintf(buf+count, "%s", msg);
Assert(cc > 0);
count += cc;
#else
if (msg == NULL)
sprintf(buf+count, "(unknown err=%d)", err);
else
sprintf(buf+count, "%s", msg);
count += strlen(buf + count);
#endif
#endif
}
#if !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF) || \
!defined(SNPRINTF_DELCARED) || !defined(VSNPRINTF_DECLARED)
/* couldn't do it right, so check for overflow */
Assert(count <= kNuHeftyBufSize);
#endif
if ((pArchive != NULL && pArchive->messageHandlerFunc == NULL) ||
(pArchive == NULL && gNuGlobalErrorMessageHandler == NULL))
{
if (isDebug) {
fprintf(stderr, "%s: [%s:%d %s] %s\n", kNufxLibName,
file, line, function, buf);
} else {
fprintf(stderr, "%s: ERROR: %s\n", kNufxLibName, buf);
}
} else {
errorMessage.message = buf;
errorMessage.err = err;
errorMessage.isDebug = isDebug;
errorMessage.file = file;
errorMessage.line = line;
errorMessage.function = function;
if (pArchive == NULL)
(void) (*gNuGlobalErrorMessageHandler)(pArchive, &errorMessage);
else
(void) (*pArchive->messageHandlerFunc)(pArchive, &errorMessage);
}
bail:
return;
}
/*
* Memory allocation wrappers.
*
* Under gcc these would be macros, but not all compilers can handle that.
*
* [ It should be possible to use mmalloc instead of malloc. Just tuck the
* mmalloc descriptor into the NuArchive struct. ]
*/
#ifndef USE_DMALLOC
void* Nu_Malloc(NuArchive* pArchive, size_t size)
{
void* _result;
Assert(size > 0);
_result = malloc(size);
if (_result == NULL) {
Nu_ReportError(NU_BLOB, kNuErrMalloc,
"malloc(%u) failed", (unsigned int) size);
DebugAbort(); /* leave a core dump if we're built for it */
}
DebugFill(_result, size);
return _result;
}
void* Nu_Calloc(NuArchive* pArchive, size_t size)
{
void* _cresult = Nu_Malloc(pArchive, size);
memset(_cresult, 0, size);
return _cresult;
}
void* Nu_Realloc(NuArchive* pArchive, void* ptr, size_t size)
{
void* _result;
Assert(ptr != NULL); /* disallow this usage */
Assert(size > 0); /* disallow this usage */
_result = realloc(ptr, size);
if (_result == NULL) {
Nu_ReportError(NU_BLOB, kNuErrMalloc,
"realloc(%u) failed", (unsigned int) size);
DebugAbort(); /* leave a core dump if we're built for it */
}
return _result;
}
void Nu_Free(NuArchive* pArchive, void* ptr)
{
if (ptr != NULL)
free(ptr);
}
#endif
/*
* If somebody internal wants to set doClose on a buffer DataSource
* (looks like "Rename" does), we need to supply a "free" callback.
*/
NuResult Nu_InternalFreeCallback(NuArchive* pArchive, void* args)
{
DBUG(("+++ internal free callback 0x%08lx\n", (long) args));
Nu_Free(NULL, args);
return kNuOK;
}