/* * 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. * * System-dependent utility functions. */ #include "NuLib2.h" #ifdef HAVE_WINDOWS_H # include #endif #ifdef MAC_LIKE # include #endif /* get a grip on this opendir/readdir stuff */ #if defined(UNIX_LIKE) # if defined(HAVE_DIRENT_H) # include # define DIR_NAME_LEN(dirent) ((int)strlen((dirent)->d_name)) typedef struct dirent DIR_TYPE; # elif defined(HAVE_SYS_DIR_H) # include # define DIR_NAME_LEN(direct) ((direct)->d_namlen) typedef struct direct DIR_TYPE; # elif defined(HAVE_NDIR_H) # include # define DIR_NAME_LEN(direct) ((direct)->d_namlen) typedef struct direct DIR_TYPE; # else # error "Port this?" # endif #endif /* * For systems (e.g. Visual C++ 6.0) that don't have these standard values. */ #ifndef S_IRUSR # define S_IRUSR 0400 # define S_IWUSR 0200 # define S_IXUSR 0100 # define S_IRWXU (S_IRUSR|S_IWUSR|S_IXUSR) # define S_IRGRP (S_IRUSR >> 3) # define S_IWGRP (S_IWUSR >> 3) # define S_IXGRP (S_IXUSR >> 3) # define S_IRWXG (S_IRWXU >> 3) # define S_IROTH (S_IRGRP >> 3) # define S_IWOTH (S_IWGRP >> 3) # define S_IXOTH (S_IXGRP >> 3) # define S_IRWXO (S_IRWXG >> 3) #endif #ifndef S_ISREG # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif /* * =========================================================================== * System-specific filename stuff * =========================================================================== */ // 20150103: this makes me nervous #define kTempFileNameLen 20 #if defined(UNIX_LIKE) /* * Filename normalization for typical UNIX filesystems. Only '/' is * forbidden. Maximum filename length is large enough that we might * as well just let the filesystem truncate if it gets too long, rather * than worry about truncating it cleverly. */ static NuError UNIXNormalizeFileName(NulibState* pState, const char* srcp, long srcLen, char fssep, char** pDstp, long dstLen) { char* dstp = *pDstp; while (srcLen--) { /* don't go until null found! */ Assert(*srcp != '\0'); if (*srcp == kForeignIndic) { /* change '%' to "%%" */ if (NState_GetModPreserveType(pState)) *dstp++ = *srcp; *dstp++ = *srcp++; } else if (*srcp == '/') { /* change '/' to "%2f" */ if (NState_GetModPreserveType(pState)) { *dstp++ = kForeignIndic; *dstp++ = HexConv(*srcp >> 4 & 0x0f); *dstp++ = HexConv(*srcp & 0x0f); } else { *dstp++ = '_'; } srcp++; } else { /* no need to fiddle with it */ *dstp++ = *srcp++; } } *dstp = '\0'; /* end the string, but don't advance past the null */ Assert(*pDstp - dstp <= dstLen); /* make sure we didn't overflow */ *pDstp = dstp; return kNuErrNone; } #elif defined(WINDOWS_LIKE) /* * You can't create files or directories with these names on a FAT filesystem, * because they're MS-DOS "device special files". So, we either prepend * a '_' (if we're not preserving filenames) or "%00" (if we are). The * "%00" sequence gets stripped off during denormalization. * * For some reason FAT is actually even more picky than that, insisting * that files like "CON.FOO.TXT" are also illegal. * * The list comes from the Linux kernel's fs/msdos/namei.c. */ static const char* fatReservedNames3[] = { "CON", "PRN", "NUL", "AUX", NULL }; static const char* fatReservedNames4[] = { "LPT1", "LPT2", "LPT3", "LPT4", "COM1", "COM2", "COM3", "COM4", NULL }; /* * Filename normalization for Win32 filesystems. You can't use [ \/:*?"<>| ]. */ static NuError Win32NormalizeFileName(NulibState* pState, const char* srcp, long srcLen, char fssep, char** pDstp, long dstLen) { char* dstp = *pDstp; const char* startp = srcp; static const char* kInvalid = "\\/:*?\"<>|"; /* look for a match on "aux" or "aux\..*" */ if (srcLen >= 3) { const char** ppcch; for (ppcch = fatReservedNames3; *ppcch != NULL; ppcch++) { if (strncasecmp(srcp, *ppcch, 3) == 0 && srcp[3] == '.' || srcLen == 3) { DBUG(("--- fixing '%s'\n", *ppcch)); if (NState_GetModPreserveType(pState)) { *dstp++ = kForeignIndic; *dstp++ = '0'; *dstp++ = '0'; } else *dstp++ = '_'; break; } } } if (srcLen >= 4) { const char** ppcch; for (ppcch = fatReservedNames4; *ppcch != NULL; ppcch++) { if (strncasecmp(srcp, *ppcch, 4) == 0 && srcp[4] == '.' || srcLen == 4) { DBUG(("--- fixing '%s'\n", *ppcch)); if (NState_GetModPreserveType(pState)) { *dstp++ = kForeignIndic; *dstp++ = '0'; *dstp++ = '0'; } else *dstp++ = '_'; break; } } } while (srcLen--) { /* don't go until null found! */ Assert(*srcp != '\0'); if (*srcp == kForeignIndic) { /* change '%' to "%%" */ if (NState_GetModPreserveType(pState)) *dstp++ = *srcp; *dstp++ = *srcp++; } else if (strchr(kInvalid, *srcp) != NULL) { /* change invalid char to "%2f" or '_' */ if (NState_GetModPreserveType(pState)) { *dstp++ = kForeignIndic; *dstp++ = HexConv(*srcp >> 4 & 0x0f); *dstp++ = HexConv(*srcp & 0x0f); } else { *dstp++ = '_'; } srcp++; } else { /* no need to fiddle with it */ *dstp++ = *srcp++; } } *dstp = '\0'; /* end the string, but don't advance past the null */ Assert(*pDstp - dstp <= dstLen); /* make sure we didn't overflow */ *pDstp = dstp; return kNuErrNone; } #endif /* * Normalize a file name to local filesystem conventions. The input * is quite possibly *NOT* null-terminated, since it may represent a * substring of a full pathname. Use "srcLen". * * The output filename is copied to *pDstp, which is advanced forward. * * The output buffer must be able to hold 3x the original string length. */ NuError NormalizeFileName(NulibState* pState, const char* srcp, long srcLen, char fssep, char** pDstp, long dstLen) { NuError err; Assert(srcp != NULL); Assert(srcLen > 0); Assert(dstLen > srcLen); Assert(pDstp != NULL); Assert(*pDstp != NULL); Assert(fssep > ' ' && fssep < 0x7f); #if defined(UNIX_LIKE) err = UNIXNormalizeFileName(pState, srcp, srcLen, fssep, pDstp, dstLen); #elif defined(WINDOWS_LIKE) err = Win32NormalizeFileName(pState, srcp, srcLen, fssep, pDstp, dstLen); #else #error "port this" #endif return err; } /* * Normalize a directory name to local filesystem conventions. */ NuError NormalizeDirectoryName(NulibState* pState, const char* srcp, long srcLen, char fssep, char** pDstp, long dstLen) { /* in general, directories and filenames are the same */ return NormalizeFileName(pState, srcp, srcLen, fssep, pDstp, dstLen); } /* * Given the archive filename and the file system separator, strip off the * archive filename and replace it with the name of a nonexistent file * in the same directory. * * Under UNIX we just need the file to be on the same filesystem, but * under GS/OS it has to be in the same directory. Not sure what Mac OS * or Windows requires, so it's safest to just put it in the same dir. */ char* MakeTempArchiveName(NulibState* pState) { const char* archivePathname; char fssep; const char* nameStart; char* newName = NULL; char* namePtr; char* resultName = NULL; long len; archivePathname = NState_GetArchiveFilename(pState); Assert(archivePathname != NULL); fssep = NState_GetSystemPathSeparator(pState); Assert(fssep != 0); /* we'll get confused if the archive pathname looks like "/foo/bar/" */ len = strlen(archivePathname); if (len < 1) goto bail; if (archivePathname[len-1] == fssep) { ReportError(kNuErrNone, "archive pathname can't end in '%c'", fssep); goto bail; } /* figure out where the filename ends */ nameStart = strrchr(archivePathname, fssep); if (nameStart == NULL) { /* nothing but a filename */ newName = Malloc(kTempFileNameLen +1); namePtr = newName; } else { nameStart++; /* advance past the fssep */ newName = Malloc((nameStart - archivePathname) + kTempFileNameLen +1); strcpy(newName, archivePathname); namePtr = newName + (nameStart - archivePathname); } if (newName == NULL) goto bail; /* * Create a new name with a mktemp-style template. */ strcpy(namePtr, "nulibtmpXXXXXX"); resultName = newName; bail: if (resultName == NULL) Free(newName); return resultName; } /* * =========================================================================== * Add a set of files * =========================================================================== */ /* * AddFile() and supporting functions. * * When adding one or more files, we need to add the file's attributes too, * including file type and access permissions. We may want to recurse * into subdirectories. * * Because UNIX and GS/OS have rather different schemes for scanning * directories, I'm defining the whole thing as system-dependent instead * of trying to put an OS-dependent callback inside an OS-independent * wrapper. The GS/OS directory scanning mechanism does everything stat() * does, plus picks up file types, so AddDirectory will want to pass a * lot more stuff into AddFile than the UNIX version. And the UNIX and * Windows versions need to make filetype assumptions based on filename * extensions. * * We could force GS/OS to do an opendir/readdir/stat sort of thing, and * pass around some file type info that doesn't really get set under * UNIX or Windows, but that would be slower and more clumsy. */ #if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) /* * Check a file's status. * * [ Someday we may want to modify this to handle symbolic links. ] */ NuError CheckFileStatus(const char* pathname, struct stat* psb, Boolean* pExists, Boolean* pIsReadable, Boolean* pIsDir) { NuError err = kNuErrNone; int cc; Assert(pathname != NULL); Assert(pExists != NULL); Assert(pIsReadable != NULL); Assert(pIsDir != NULL); *pExists = true; *pIsReadable = true; *pIsDir = false; cc = stat(pathname, psb); if (cc) { if (errno == ENOENT) *pExists = false; else err = kNuErrFileStat; goto bail; } if (S_ISDIR(psb->st_mode)) *pIsDir = true; /* * Test if we can read this file. How do we do that? The easy but slow * way is to call access(2), the harder way is to figure out * what user/group we are and compare the appropriate file mode. */ if (access(pathname, R_OK) < 0) *pIsReadable = false; bail: return err; } #endif #if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) /* * Convert from time in seconds to DateTime format. */ static void UNIXTimeToDateTime(const time_t* pWhen, NuDateTime *pDateTime) { struct tm* ptm; Assert(pWhen != NULL); Assert(pDateTime != NULL); ptm = localtime(pWhen); pDateTime->second = ptm->tm_sec; pDateTime->minute = ptm->tm_min; pDateTime->hour = ptm->tm_hour; pDateTime->day = ptm->tm_mday -1; pDateTime->month = ptm->tm_mon; pDateTime->year = ptm->tm_year; pDateTime->extra = 0; pDateTime->weekDay = ptm->tm_wday +1; } #endif #if defined(MAC_LIKE) /* * Due to historical reasons, the XATTR_FINDERINFO_NAME (defined to be * ``com.apple.FinderInfo'') extended attribute must be 32 bytes; see the * ATTR_CMN_FNDRINFO section in getattrlist(2). * * The FinderInfo block is the concatenation of a FileInfo structure * and an ExtendedFileInfo (or ExtendedFolderInfo) structure -- see * ATTR_CMN_FNDRINFO in getattrlist(2). * * All we're really interested in is the file type and creator code, * which are stored big-endian in the first 8 bytes. */ static const int kFinderInfoSize = 32; /* * Obtains the creator and file type from the Finder info block, if any, * and converts the types to ProDOS equivalents. * * If the attribute doesn't exist, this returns an error without modifying * the output args. */ static NuError GetTypesFromFinder(const char* pathnameUNI, uint32_t* pFileType, uint32_t* pAuxType) { uint8_t fiBuf[kFinderInfoSize]; size_t actual = getxattr(pathnameUNI, XATTR_FINDERINFO_NAME, fiBuf, sizeof(fiBuf), 0, 0); if (actual != kFinderInfoSize) { return kNuErrNotFound; } uint32_t fileType, creator; fileType = (fiBuf[0] << 24) | (fiBuf[1] << 16) | (fiBuf[2] << 8) | fiBuf[3]; creator = (fiBuf[4] << 24) | (fiBuf[5] << 16) | (fiBuf[6] << 8) | fiBuf[7]; Boolean found = false; uint8_t proType; uint16_t proAux; /* * Convert to ProDOS file/aux type. */ if (creator == 'pdos') { /* * TODO: handle conversion from 'pdos'/'XY ' to $XY/$0000. * I think this conversion was deprecated and not widely used; * the inability to retain the aux type renders it inapplicable * to many files. */ if (fileType == 'PSYS') { proType = 0xFF; // SYS proAux = 0x0000; found = true; } else if (fileType == 'PS16') { proType = 0xB3; // S16 proAux = 0x0000; found = true; } else { if (((fileType >> 24) & 0xFF) == 'p') { proType = (fileType >> 16) & 0xFF; proAux = (uint16_t) fileType; found = true; } } } else if (creator == 'dCpy') { if (fileType == 'dImg') { proType = 0xE0; // LBR proAux = 0x0005; found = true; } } if (!found) { switch (fileType) { case 'BINA': proType = 0x06; // BIN proAux = 0x0000; break; case 'TEXT': proType = 0x04; // TXT proAux = 0x0000; break; case 'MIDI': proType = 0xD7; // MDI proAux = 0x0000; break; case 'AIFF': proType = 0xD8; // SND proAux = 0x0000; break; case 'AIFC': proType = 0xD8; // SND proAux = 0x0001; break; default: proType = 0x00; // NON proAux = 0x0000; break; } } *pFileType = proType; *pAuxType = proAux; return kNuErrNone; } /* * Set the file type and creator type, based on the ProDOS file type * and aux type. * * This is a clone of the function in NufxLib; it exists for the * benefit of the Binary ][ code. */ NuError SetFinderInfo(int fd, uint8_t proType, uint16_t proAux) { uint8_t fiBuf[kFinderInfoSize]; size_t actual = fgetxattr(fd, XATTR_FINDERINFO_NAME, fiBuf, sizeof(fiBuf), 0, 0); if (actual == (size_t) -1 && errno == ENOATTR) { // doesn't yet have Finder info memset(fiBuf, 0, sizeof(fiBuf)); } else if (actual != kFinderInfoSize) { return kNuErrFile; } /* * Attempt to use one of the convenience types. If nothing matches, * use the generic pdos/pXYZ approach. Note that PSYS/PS16 will * lose the file's aux type. * * I'm told this is from page 336 of _Programmer's Reference for * System 6.0_. */ uint8_t* fileTypeBuf = fiBuf; uint8_t* creatorBuf = fiBuf + 4; memcpy(creatorBuf, "pdos", 4); if (proType == 0x00 && proAux == 0x0000) { memcpy(fileTypeBuf, "BINA", 4); } else if (proType == 0x04 && proAux == 0x0000) { memcpy(fileTypeBuf, "TEXT", 4); } else if (proType == 0xff) { memcpy(fileTypeBuf, "PSYS", 4); } else if (proType == 0xb3 && (proAux & 0xff00) != 0xdb00) { memcpy(fileTypeBuf, "PS16", 4); } else if (proType == 0xd7 && proAux == 0x0000) { memcpy(fileTypeBuf, "MIDI", 4); } else if (proType == 0xd8 && proAux == 0x0000) { memcpy(fileTypeBuf, "AIFF", 4); } else if (proType == 0xd8 && proAux == 0x0001) { memcpy(fileTypeBuf, "AIFC", 4); } else if (proType == 0xe0 && proAux == 0x0005) { memcpy(creatorBuf, "dCpy", 4); memcpy(fileTypeBuf, "dImg", 4); } else { fileTypeBuf[0] = 'p'; fileTypeBuf[1] = proType; fileTypeBuf[2] = (uint8_t) (proAux >> 8); fileTypeBuf[3] = (uint8_t) proAux; } if (fsetxattr(fd, XATTR_FINDERINFO_NAME, fiBuf, sizeof(fiBuf), 0, 0) != 0) { return kNuErrFile; } return kNuErrNone; } #endif /*MAC_LIKE*/ #if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) /* * Replace "oldc" with "newc". If we find an instance of "newc" already * in the string, replace it with "newSubst". */ static void ReplaceFssep(char* str, char oldc, char newc, char newSubst) { while (*str != '\0') { if (*str == oldc) *str = newc; else if (*str == newc) *str = newSubst; str++; } } /* * Set the contents of a NuFileDetails structure, based on the pathname * and characteristics of the file. */ static NuError GetFileDetails(NulibState* pState, const char* pathnameMOR, struct stat* psb, NuFileDetails* pDetails) { Boolean wasPreserved; Boolean doJunk = false; Boolean adjusted; char* livePathStr; char slashDotDotSlash[5] = "_.._"; time_t now; Assert(pState != NULL); Assert(pathnameMOR != NULL); Assert(pDetails != NULL); /* set up the pathname buffer; note pDetails->storageName is const */ NState_SetTempPathnameLen(pState, strlen(pathnameMOR) +1); livePathStr = NState_GetTempPathnameBuf(pState); Assert(livePathStr != NULL); strcpy(livePathStr, pathnameMOR); /* under Win32, both '/' and '\' work... we want to settle on one */ if (NState_GetAltSystemPathSeparator(pState) != '\0') { ReplaceFssep(livePathStr, NState_GetAltSystemPathSeparator(pState), NState_GetSystemPathSeparator(pState), NState_GetSystemPathSeparator(pState)); } /* init to defaults */ memset(pDetails, 0, sizeof(*pDetails)); pDetails->threadID = kNuThreadIDDataFork; pDetails->storageNameMOR = livePathStr; /* point at temp buffer */ pDetails->origName = NULL; pDetails->fileSysID = kNuFileSysUnknown; pDetails->fileSysInfo = kStorageFssep; pDetails->fileType = 0; pDetails->extraType = 0; pDetails->storageType = kNuStorageUnknown; /* let NufxLib worry about it */ if (psb->st_mode & S_IWUSR) pDetails->access = kNuAccessUnlocked; else pDetails->access = kNuAccessLocked; /* if this is a disk image, fill in disk-specific fields */ if (NState_GetModAddAsDisk(pState)) { if ((psb->st_size & 0x1ff) != 0) { /* reject anything whose size isn't a multiple of 512 bytes */ printf("NOT storing odd-sized (%ld) file as disk image: %s\n", (long)psb->st_size, livePathStr); } else { /* set fields; note the "preserve" stuff will override this */ pDetails->threadID = kNuThreadIDDiskImage; pDetails->storageType = 512; pDetails->extraType = psb->st_size / 512; } } now = time(NULL); UNIXTimeToDateTime(&now, &pDetails->archiveWhen); UNIXTimeToDateTime(&psb->st_mtime, &pDetails->modWhen); UNIXTimeToDateTime(&psb->st_mtime, &pDetails->createWhen); #ifdef MAC_LIKE /* * Retrieve the file/aux type from the Finder info. We want the * type-preservation string to take priority, so get this first. */ (void) GetTypesFromFinder(livePathStr, &pDetails->fileType, &pDetails->extraType); #endif /* * Check for file type preservation info in the filename. If present, * set the file type values and truncate the filename. */ wasPreserved = false; if (NState_GetModPreserveType(pState)) { wasPreserved = ExtractPreservationString(pState, livePathStr, &pDetails->fileType, &pDetails->extraType, &pDetails->threadID); } /* * Do a "denormalization" pass, where we convert invalid chars (such * as '/') from percent-codes back to 8-bit characters. The filename * will always be the same size or smaller, so we can do it in place. */ if (wasPreserved) DenormalizePath(pState, livePathStr); /* * If we're in "extended" mode, and the file wasn't preserved, take a * guess at what the file type should be based on the file extension. */ if (!wasPreserved && NState_GetModPreserveTypeExtended(pState)) { InterpretExtension(pState, livePathStr, &pDetails->fileType, &pDetails->extraType); } /* * Strip bad chars off the front of the pathname. Every time we * remove one thing we potentially expose another, so we have to * loop until it's sanitized. * * The outer loop isn't really necessary under Win32, because you'd * need to do something like ".\\foo", which isn't allowed. UNIX * silently allows ".//foo", so this is a problem there. (We could * probably do away with the inner loops, but those were already * written when I saw the larger problem.) */ do { adjusted = false; /* * Check for other unpleasantness, such as a leading fssep. */ Assert(NState_GetSystemPathSeparator(pState) != '\0'); while (livePathStr[0] == NState_GetSystemPathSeparator(pState)) { /* slide it down, len is (strlen +1), -1 (dropping first char)*/ memmove(livePathStr, livePathStr+1, strlen(livePathStr)); adjusted = true; } /* * Remove leading "./". */ while (livePathStr[0] == '.' && livePathStr[1] == NState_GetSystemPathSeparator(pState)) { /* slide it down, len is (strlen +1) -2 (dropping two chars) */ memmove(livePathStr, livePathStr+2, strlen(livePathStr)-1); adjusted = true; } } while (adjusted); /* * If there's a "/../" present anywhere in the name, junk everything * but the filename. * * This won't catch "foo/bar/..", but that should've been caught as * a directory anyway. */ slashDotDotSlash[0] = NState_GetSystemPathSeparator(pState); slashDotDotSlash[3] = NState_GetSystemPathSeparator(pState); if ((livePathStr[0] == '.' && livePathStr[1] == '.') || (strstr(livePathStr, slashDotDotSlash) != NULL)) { DBUG(("Found dot dot in '%s', keeping only filename\n", livePathStr)); doJunk = true; } /* * Scan for and remove "/./" and trailing "/.". They're filesystem * no-ops that work just fine under Win32 and UNIX but could confuse * a IIgs. (Of course, the user could just omit them from the pathname.) */ /* TO DO 20030208 */ /* * If "junk paths" is set, drop everything before the last fssep char. */ if (NState_GetModJunkPaths(pState) || doJunk) { char* lastFssep; lastFssep = strrchr(livePathStr, NState_GetSystemPathSeparator(pState)); if (lastFssep != NULL) { Assert(*(lastFssep+1) != '\0'); /* should already have been caught*/ memmove(livePathStr, lastFssep+1, strlen(lastFssep+1)+1); } } /* * Finally, substitute our generally-accepted path separator in place of * the local one, stomping on anything with a ':' in it as we do. The * goal is to avoid having "subdir:foo/bar" turn into "subdir/foo/bar". * Were we a general-purpose archiver, this might be a mistake, but * we're not. NuFX doesn't really give us a choice. */ ReplaceFssep(livePathStr, NState_GetSystemPathSeparator(pState), kStorageFssep, 'X'); /*bail:*/ return kNuErrNone; } #endif /* * Do the system-independent part of the file add, including things like * adding comments. */ static NuError DoAddFile(NulibState* pState, NuArchive* pArchive, const char* pathname, const NuFileDetails* pDetails) { NuError err; NuRecordIdx recordIdx = 0; err = NuAddFile(pArchive, pathname, pDetails, false, &recordIdx); if (err == kNuErrNone) { NState_IncMatchCount(pState); } else if (err == kNuErrSkipped) { /* "maybe overwrite" UI causes this if user declines */ err = kNuErrNone; goto bail; } else if (err == kNuErrNotNewer) { /* if we were expecting this, it's okay */ if (NState_GetModFreshen(pState) || NState_GetModUpdate(pState)) { printf("SKIP older file: %s\n", pathname); err = kNuErrNone; goto bail; } } else if (err == kNuErrDuplicateNotFound) { /* if we were expecting this, it's okay */ if (NState_GetModFreshen(pState)) { printf("SKIP file not in archive: %s\n", pathname); err = kNuErrNone; goto bail; } } else if (err == kNuErrRecordExists) { printf("FAIL same filename added twice: '%s'\n", NState_GetTempPathnameBuf(pState)); goto bail_quiet; } if (err != kNuErrNone) goto bail; /* add a one-line comment if requested */ if (NState_GetModComments(pState)) { char* comment; DBUG(("Preparing comment for recordIdx=%ld\n", recordIdx)); Assert(recordIdx != 0); comment = GetSimpleComment(pState, pathname, kDefaultCommentLen); if (comment != NULL) { NuDataSource* pDataSource; err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, kDefaultCommentLen, (uint8_t*)comment, 0, strlen(comment), FreeCallback, &pDataSource); if (err != kNuErrNone) { ReportError(err, "comment buffer create failed"); Free(comment); err = kNuErrNone; /* oh well */ } else { comment = NULL; /* now owned by the data source */ err = NuAddThread(pArchive, recordIdx, kNuThreadIDComment, pDataSource, NULL); if (err != kNuErrNone) { ReportError(err, "comment thread add failed"); NuFreeDataSource(pDataSource); err = kNuErrNone; /* oh well */ } else { pDataSource = NULL; /* now owned by NufxLib */ } } } } bail: if (err != kNuErrNone) ReportError(err, "unable to add file"); bail_quiet: return err; } #if defined(UNIX_LIKE) /* ---- UNIX --------------------------------------- */ static NuError UNIXAddFile(NulibState* pState, NuArchive* pArchive, const char* pathname); /* * UNIX-style recursive directory descent. Scan the contents of a directory. * If a subdirectory is found, follow it; otherwise, call UNIXAddFile to * add the file. */ static NuError UNIXAddDirectory(NulibState* pState, NuArchive* pArchive, const char* dirName) { NuError err = kNuErrNone; DIR* dirp = NULL; DIR_TYPE* entry; char nbuf[MAX_PATH_LEN]; /* malloc might be better; this soaks stack */ char fssep; int len; Assert(pState != NULL); Assert(pArchive != NULL); Assert(dirName != NULL); DBUG(("+++ DESCEND: '%s'\n", dirName)); dirp = opendir(dirName); if (dirp == NULL) { if (errno == ENOTDIR) err = kNuErrNotDir; else err = errno ? errno : kNuErrOpenDir; ReportError(err, "failed on '%s'", dirName); goto bail; } fssep = NState_GetSystemPathSeparator(pState); /* could use readdir_r, but we don't care about reentrancy here */ while ((entry = readdir(dirp)) != NULL) { /* skip the dotsies */ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; len = strlen(dirName); if (len + DIR_NAME_LEN(entry) +2 > MAX_PATH_LEN) { err = kNuErrInternal; ReportError(err, "Filename exceeds %d bytes: %s%c%s", MAX_PATH_LEN, dirName, fssep, entry->d_name); goto bail; } /* form the new name, inserting an fssep if needed */ strcpy(nbuf, dirName); if (dirName[len-1] != fssep) nbuf[len++] = fssep; strcpy(nbuf+len, entry->d_name); err = UNIXAddFile(pState, pArchive, nbuf); if (err != kNuErrNone) goto bail; } bail: if (dirp != NULL) (void)closedir(dirp); return err; } /* * Add a file to the list we're adding to the archive. * * If the file is a directory, and we allow recursing into subdirectories, * this calls UNIXAddDirectory. If we don't allow recursion, this just * returns without an error. * * Returns with an error if the file doesn't exist or isn't readable. */ static NuError UNIXAddFile(NulibState* pState, NuArchive* pArchive, const char* pathname) { NuError err = kNuErrNone; Boolean exists, isDir, isReadable; NuFileDetails details; struct stat sb; Assert(pState != NULL); Assert(pArchive != NULL); Assert(pathname != NULL); err = CheckFileStatus(pathname, &sb, &exists, &isReadable, &isDir); if (err != kNuErrNone) { ReportError(err, "unexpected error while examining '%s'", pathname); goto bail; } if (!exists) { err = kNuErrFileNotFound; ReportError(err, "couldn't find '%s'", pathname); goto bail; } if (!isReadable) { ReportError(kNuErrNone, "file '%s' isn't readable", pathname); err = kNuErrFileNotReadable; goto bail; } if (isDir) { if (NState_GetModRecurse(pState)) err = UNIXAddDirectory(pState, pArchive, pathname); goto bail_quiet; } /* * We've found a file that we want to add. We need to decide what * filetype and auxtype it has, and whether or not it's actually the * resource fork of another file. */ DBUG(("+++ ADD '%s'\n", pathname)); err = GetFileDetails(pState, pathname, &sb, &details); if (err != kNuErrNone) goto bail; err = DoAddFile(pState, pArchive, pathname, &details); if (err != kNuErrNone) goto bail_quiet; bail: if (err != kNuErrNone) ReportError(err, "unable to add file"); bail_quiet: return err; } #elif defined(WINDOWS_LIKE) /* ---- Windows -------------------------------- */ /* * Directory structure and functions, based on zDIR in Info-Zip sources. */ typedef struct Win32dirent { char d_attr; char d_name[MAX_PATH_LEN]; int d_first; HANDLE d_hFindFile; } Win32dirent; static const char* kWildMatchAll = "*.*"; /* * Prepare a directory for reading. */ static Win32dirent* OpenDir(const char* name) { Win32dirent* dir = NULL; char* tmpStr = NULL; char* cp; WIN32_FIND_DATA fnd; dir = Malloc(sizeof(*dir)); tmpStr = Malloc(strlen(name) + (2 + sizeof(kWildMatchAll))); if (dir == NULL || tmpStr == NULL) goto failed; strcpy(tmpStr, name); cp = tmpStr + strlen(tmpStr); /* don't end in a colon (e.g. "C:") */ if ((cp - tmpStr) > 0 && strrchr(tmpStr, ':') == (cp - 1)) *cp++ = '.'; /* must end in a slash */ if ((cp - tmpStr) > 0 && strrchr(tmpStr, PATH_SEP) != (cp - 1)) *cp++ = PATH_SEP; strcpy(cp, kWildMatchAll); dir->d_hFindFile = FindFirstFile(tmpStr, &fnd); if (dir->d_hFindFile == INVALID_HANDLE_VALUE) goto failed; strcpy(dir->d_name, fnd.cFileName); dir->d_attr = (uint8_t) fnd.dwFileAttributes; dir->d_first = 1; bail: Free(tmpStr); return dir; failed: Free(dir); dir = NULL; goto bail; } /* * Get an entry from an open directory. * * Returns a NULL pointer after the last entry has been read. */ static Win32dirent* ReadDir(Win32dirent* dir) { if (dir->d_first) dir->d_first = 0; else { WIN32_FIND_DATA fnd; if (!FindNextFile(dir->d_hFindFile, &fnd)) return NULL; strcpy(dir->d_name, fnd.cFileName); dir->d_attr = (uint8_t) fnd.dwFileAttributes; } return dir; } /* * Close a directory. */ static void CloseDir(Win32dirent* dir) { if (dir == NULL) return; FindClose(dir->d_hFindFile); Free(dir); } /* might as well blend in with the UNIX version */ #define DIR_NAME_LEN(dirent) ((int)strlen((dirent)->d_name)) static NuError Win32AddFile(NulibState* pState, NuArchive* pArchive, const char* pathname); /* * Win32 recursive directory descent. Scan the contents of a directory. * If a subdirectory is found, follow it; otherwise, call Win32AddFile to * add the file. */ static NuError Win32AddDirectory(NulibState* pState, NuArchive* pArchive, const char* dirName) { NuError err = kNuErrNone; Win32dirent* dirp = NULL; Win32dirent* entry; char nbuf[MAX_PATH_LEN]; /* malloc might be better; this soaks stack */ char fssep; int len; Assert(pState != NULL); Assert(pArchive != NULL); Assert(dirName != NULL); DBUG(("+++ DESCEND: '%s'\n", dirName)); dirp = OpenDir(dirName); if (dirp == NULL) { if (errno == ENOTDIR) err = kNuErrNotDir; else err = errno ? errno : kNuErrOpenDir; ReportError(err, "failed on '%s'", dirName); goto bail; } fssep = NState_GetSystemPathSeparator(pState); /* could use readdir_r, but we don't care about reentrancy here */ while ((entry = ReadDir(dirp)) != NULL) { /* skip the dotsies */ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; len = strlen(dirName); if (len + DIR_NAME_LEN(entry) +2 > MAX_PATH_LEN) { err = kNuErrInternal; ReportError(err, "Filename exceeds %d bytes: %s%c%s", MAX_PATH_LEN, dirName, fssep, entry->d_name); goto bail; } /* form the new name, inserting an fssep if needed */ strcpy(nbuf, dirName); if (dirName[len-1] != fssep) nbuf[len++] = fssep; strcpy(nbuf+len, entry->d_name); err = Win32AddFile(pState, pArchive, nbuf); if (err != kNuErrNone) goto bail; } bail: if (dirp != NULL) (void)CloseDir(dirp); return err; } /* * Add a file to the list we're adding to the archive. If it's a directory, * and the recursive descent feature is enabled, call Win32AddDirectory to * add the contents of the dir. * * Returns with an error if the file doesn't exist or isn't readable. */ static NuError Win32AddFile(NulibState* pState, NuArchive* pArchive, const char* pathname) { NuError err = kNuErrNone; Boolean exists, isDir, isReadable; NuFileDetails details; struct stat sb; Assert(pState != NULL); Assert(pArchive != NULL); Assert(pathname != NULL); err = CheckFileStatus(pathname, &sb, &exists, &isReadable, &isDir); if (err != kNuErrNone) { ReportError(err, "unexpected error while examining '%s'", pathname); goto bail; } if (!exists) { err = kNuErrFileNotFound; ReportError(err, "couldn't find '%s'", pathname); goto bail; } if (!isReadable) { ReportError(kNuErrNone, "file '%s' isn't readable", pathname); err = kNuErrFileNotReadable; goto bail; } if (isDir) { if (NState_GetModRecurse(pState)) err = Win32AddDirectory(pState, pArchive, pathname); goto bail_quiet; } /* * We've found a file that we want to add. We need to decide what * filetype and auxtype it has, and whether or not it's actually the * resource fork of another file. */ DBUG(("+++ ADD '%s'\n", pathname)); err = GetFileDetails(pState, pathname, &sb, &details); if (err != kNuErrNone) goto bail; err = DoAddFile(pState, pArchive, pathname, &details); if (err != kNuErrNone) goto bail_quiet; bail: if (err != kNuErrNone) ReportError(err, "unable to add file"); bail_quiet: return err; } #else /* ---- unknown ----------------------------------------------------- */ # error "Port this (AddFile/AddDirectory)" #endif /* * External entry point; just calls the system-specific version. * * [ I figure the GS/OS version will want to pass a copy of the file * info from the GSOSAddDirectory function back into GSOSAddFile, so we'd * want to call it from here with a NULL pointer indicating that we * don't yet have the file info. That way we can get the file info * from the directory read call and won't have to check it again in * GSOSAddFile. ] */ NuError AddFile(NulibState* pState, NuArchive* pArchive, const char* pathname) { #if defined(UNIX_LIKE) return UNIXAddFile(pState, pArchive, pathname); #elif defined(WINDOWS_LIKE) return Win32AddFile(pState, pArchive, pathname); #else #error "Port this" #endif } /* * Invoke the system-dependent directory creation function. * * Currently only used by Binary2.c. */ NuError Mkdir(const char* dir) { NuError err = kNuErrNone; Assert(dir != NULL); #if defined(UNIX_LIKE) if (mkdir(dir, S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH) < 0) { err = errno ? errno : kNuErrDirCreate; goto bail; } #elif defined(WINDOWS_LIKE) if (mkdir(dir) < 0) { err = errno ? errno : kNuErrDirCreate; goto bail; } #else #error "Port this" #endif bail: return err; } /* * Test for the existence of a file. * * Currently only used by Binary2.c. */ NuError TestFileExistence(const char* fileName, Boolean* pIsDir) { NuError err = kNuErrNone; Assert(fileName != NULL); Assert(pIsDir != NULL); #if defined(UNIX_LIKE) || defined(WINDOWS_LIKE) { struct stat sbuf; int cc; cc = stat(fileName, &sbuf); if (cc) { if (errno == ENOENT) err = kNuErrFileNotFound; else err = kNuErrFileStat; goto bail; } if (S_ISDIR(sbuf.st_mode)) *pIsDir = true; else *pIsDir = false; } #else #error "Port this" #endif bail: return err; }