/* * 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; }