mirror of
https://github.com/fadden/nulib2.git
synced 2024-06-18 05:29:32 +00:00
1164 lines
33 KiB
C
1164 lines
33 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.
|
|
*
|
|
* System-dependent utility functions.
|
|
*/
|
|
#include "Nulib2.h"
|
|
|
|
#ifdef HAVE_WINDOWS_H
|
|
# include <windows.h>
|
|
#endif
|
|
|
|
/* get a grip on this opendir/readdir stuff */
|
|
#if defined(UNIX_LIKE)
|
|
# if defined(HAVE_DIRENT_H)
|
|
# include <dirent.h>
|
|
# define DIR_NAME_LEN(dirent) ((int)strlen((dirent)->d_name))
|
|
typedef struct dirent DIR_TYPE;
|
|
# elif defined(HAVE_SYS_DIR_H)
|
|
# include <sys/dir.h>
|
|
# define DIR_NAME_LEN(direct) ((direct)->d_namlen)
|
|
typedef struct direct DIR_TYPE;
|
|
# elif defined(HAVE_NDIR_H)
|
|
# include <sys/ndir.h>
|
|
# 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
|
|
* ===========================================================================
|
|
*/
|
|
|
|
#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", nil
|
|
};
|
|
static const char* fatReservedNames4[] = {
|
|
"LPT1", "LPT2", "LPT3", "LPT4", "COM1", "COM2", "COM3", "COM4", nil
|
|
};
|
|
|
|
/*
|
|
* 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 != nil; 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 != nil; 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) != nil) {
|
|
/* 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 != nil);
|
|
Assert(srcLen > 0);
|
|
Assert(dstLen > srcLen);
|
|
Assert(pDstp != nil);
|
|
Assert(*pDstp != nil);
|
|
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 = nil;
|
|
char* namePtr;
|
|
char* resultName = nil;
|
|
long len;
|
|
|
|
archivePathname = NState_GetArchiveFilename(pState);
|
|
Assert(archivePathname != nil);
|
|
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 == nil) {
|
|
/* 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 == nil)
|
|
goto bail;
|
|
|
|
/*
|
|
* Create a new name with a mktemp-style template.
|
|
*/
|
|
strcpy(namePtr, "nulibtmpXXXXXX");
|
|
|
|
resultName = newName;
|
|
|
|
bail:
|
|
if (resultName == nil)
|
|
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 != nil);
|
|
Assert(pExists != nil);
|
|
Assert(pIsReadable != nil);
|
|
Assert(pIsDir != nil);
|
|
|
|
*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 != nil);
|
|
Assert(pDateTime != nil);
|
|
|
|
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(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* pathname, struct stat* psb,
|
|
NuFileDetails* pDetails)
|
|
{
|
|
Boolean wasPreserved;
|
|
Boolean doJunk = false;
|
|
Boolean adjusted;
|
|
char* livePathStr;
|
|
char slashDotDotSlash[5] = "_.._";
|
|
time_t now;
|
|
|
|
Assert(pState != nil);
|
|
Assert(pathname != nil);
|
|
Assert(pDetails != nil);
|
|
|
|
/* set up the pathname buffer; note pDetails->storageName is const */
|
|
NState_SetTempPathnameLen(pState, strlen(pathname) +1);
|
|
livePathStr = NState_GetTempPathnameBuf(pState);
|
|
Assert(livePathStr != nil);
|
|
strcpy(livePathStr, pathname);
|
|
|
|
/* 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->storageName = livePathStr; /* point at temp buffer */
|
|
pDetails->origName = nil;
|
|
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(nil);
|
|
UNIXTimeToDateTime(&now, &pDetails->archiveWhen);
|
|
UNIXTimeToDateTime(&psb->st_mtime, &pDetails->modWhen);
|
|
UNIXTimeToDateTime(&psb->st_mtime, &pDetails->createWhen);
|
|
|
|
/*
|
|
* 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) != nil))
|
|
{
|
|
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 != nil) {
|
|
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.
|
|
*/
|
|
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 != nil) {
|
|
NuDataSource* pDataSource;
|
|
|
|
err = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed,
|
|
kDefaultCommentLen, (unsigned char*)comment, 0,
|
|
strlen(comment), FreeCallback, &pDataSource);
|
|
if (err != kNuErrNone) {
|
|
ReportError(err, "comment buffer create failed");
|
|
Free(comment);
|
|
err = kNuErrNone; /* oh well */
|
|
} else {
|
|
comment = nil; /* now owned by the data source */
|
|
err = NuAddThread(pArchive, recordIdx, kNuThreadIDComment,
|
|
pDataSource, nil);
|
|
if (err != kNuErrNone) {
|
|
ReportError(err, "comment thread add failed");
|
|
NuFreeDataSource(pDataSource);
|
|
err = kNuErrNone; /* oh well */
|
|
} else {
|
|
pDataSource = nil; /* 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 = nil;
|
|
DIR_TYPE* entry;
|
|
char nbuf[MAX_PATH_LEN]; /* malloc might be better; this soaks stack */
|
|
char fssep;
|
|
int len;
|
|
|
|
Assert(pState != nil);
|
|
Assert(pArchive != nil);
|
|
Assert(dirName != nil);
|
|
|
|
DBUG(("+++ DESCEND: '%s'\n", dirName));
|
|
|
|
dirp = opendir(dirName);
|
|
if (dirp == nil) {
|
|
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)) != nil) {
|
|
/* 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 != nil)
|
|
(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 != nil);
|
|
Assert(pArchive != nil);
|
|
Assert(pathname != nil);
|
|
|
|
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 = nil;
|
|
char* tmpStr = nil;
|
|
char* cp;
|
|
WIN32_FIND_DATA fnd;
|
|
|
|
dir = Malloc(sizeof(*dir));
|
|
tmpStr = Malloc(strlen(name) + (2 + sizeof(kWildMatchAll)));
|
|
if (dir == nil || tmpStr == nil)
|
|
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 = (uchar) fnd.dwFileAttributes;
|
|
dir->d_first = 1;
|
|
|
|
bail:
|
|
Free(tmpStr);
|
|
return dir;
|
|
|
|
failed:
|
|
Free(dir);
|
|
dir = nil;
|
|
goto bail;
|
|
}
|
|
|
|
/*
|
|
* Get an entry from an open directory.
|
|
*
|
|
* Returns a nil 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 nil;
|
|
strcpy(dir->d_name, fnd.cFileName);
|
|
dir->d_attr = (uchar) fnd.dwFileAttributes;
|
|
}
|
|
|
|
return dir;
|
|
}
|
|
|
|
/*
|
|
* Close a directory.
|
|
*/
|
|
static void
|
|
CloseDir(Win32dirent* dir)
|
|
{
|
|
if (dir == nil)
|
|
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 = nil;
|
|
Win32dirent* entry;
|
|
char nbuf[MAX_PATH_LEN]; /* malloc might be better; this soaks stack */
|
|
char fssep;
|
|
int len;
|
|
|
|
Assert(pState != nil);
|
|
Assert(pArchive != nil);
|
|
Assert(dirName != nil);
|
|
|
|
DBUG(("+++ DESCEND: '%s'\n", dirName));
|
|
|
|
dirp = OpenDir(dirName);
|
|
if (dirp == nil) {
|
|
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)) != nil) {
|
|
/* 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 != nil)
|
|
(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 != nil);
|
|
Assert(pArchive != nil);
|
|
Assert(pathname != nil);
|
|
|
|
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 nil 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 != nil);
|
|
|
|
#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 != nil);
|
|
Assert(pIsDir != nil);
|
|
|
|
#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;
|
|
}
|
|
|
|
|