Copy files to destination directory

GitOrigin-RevId: 34f4a68c4aa26def783f1192a26768b4393ed087
This commit is contained in:
Dietrich Epp 2021-03-10 18:18:46 -05:00
parent 1264148f6d
commit b1a85945d7
4 changed files with 579 additions and 19 deletions

View File

@ -8,14 +8,17 @@ COptions-68K = {COptions} {Sym-68K}
### Source Files ###
SrcFiles =
mac_to_unix.c ∂
sync.c
### Object Files ###
ObjFiles-PPC =
mac_to_unix.c.x ∂
sync.c.x
ObjFiles-68K =
mac_to_unix.c.o ∂
sync.c.o
### Libraries ###
@ -69,8 +72,19 @@ SyncFiles ƒƒ {ObjFiles-68K} {LibFiles-68K}
Dependencies ƒ $OutOfDate
MakeDepend ∂
-append {MAKEFILE}
-append Makefile
-ignore "{CIncludes}"
-objext .x ∂
-objext .o ∂
{SrcFiles}
#*** Dependencies: Cut here ***
# These dependencies were produced at 10:09:51 PM on Sat, Mar 6, 2021 by MakeDepend
:mac_to_unix.c.x :mac_to_unix.c.o ƒ ∂
:mac_to_unix.c ∂
:defs.h
:sync.c.x :sync.c.o ƒ ∂
:sync.c ∂
:defs.h

2
defs.h Normal file
View File

@ -0,0 +1,2 @@
void mac_to_unix(unsigned char **outptr, unsigned char *outend,
const unsigned char **inptr, const unsigned char *inend);

65
mac_to_unix.c Normal file
View File

@ -0,0 +1,65 @@
#include "defs.h"
#include <stdio.h>
// Table that converts Macintosh Roman characters to UTF-8, and CR to LF.
static const unsigned short kToUnixTable[256] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 10, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
120, 121, 122, 123, 124, 125, 126, 127, 196, 197, 199, 201,
209, 214, 220, 225, 224, 226, 228, 227, 229, 231, 233, 232,
234, 235, 237, 236, 238, 239, 241, 243, 242, 244, 246, 245,
250, 249, 251, 252, 8224, 176, 162, 163, 167, 8226, 182, 223,
174, 169, 8482, 180, 168, 8800, 198, 216, 8734, 177, 8804, 8805,
165, 181, 8706, 8721, 8719, 960, 8747, 170, 186, 937, 230, 248,
191, 161, 172, 8730, 402, 8776, 8710, 171, 187, 8230, 160, 192,
195, 213, 338, 339, 8211, 8212, 8220, 8221, 8216, 8217, 247, 9674,
255, 376, 8260, 8364, 8249, 8250, 64257, 64258, 8225, 183, 8218, 8222,
8240, 194, 202, 193, 203, 200, 205, 206, 207, 204, 211, 212,
63743, 210, 218, 219, 217, 305, 710, 732, 175, 728, 729, 730,
184, 733, 731, 711,
};
void mac_to_unix(unsigned char **outptr, unsigned char *outend,
const unsigned char **inptr, const unsigned char *inend) {
unsigned char *op = *outptr;
const unsigned char *ip = *inptr;
unsigned cp;
while (ip < inend) {
cp = kToUnixTable[*ip];
if (cp < 0x80) {
if (outend - op < 1) {
break;
}
op[0] = cp;
op += 1;
} else if (cp < 0x400) {
if (outend - op < 2) {
break;
}
op[0] = (cp >> 6) | 0xc0;
op[1] = (cp & 0x3f) | 0x80;
op += 2;
} else {
if (outend - op < 3) {
break;
}
op[0] = (cp >> 12) | 0xe0;
op[1] = ((cp >> 6) & 0x3f) | 0x80;
op[2] = (cp & 0x3f) | 0x80;
op += 3;
}
ip++;
}
*outptr = op;
*inptr = ip;
}

515
sync.c
View File

@ -1,4 +1,7 @@
#include "defs.h"
#include <Files.h>
#include <Folders.h>
#include <MacMemory.h>
#include <StringCompare.h>
@ -6,6 +9,15 @@
#include <stdio.h>
#include <string.h>
enum {
// Maximum file size that we will copy.
kMaxFileSize = 64 * 1024,
};
typedef void (*convert_func)(unsigned char **outptr, unsigned char *outend,
const unsigned char **inptr,
const unsigned char *inend);
static void print_err(const char *msg, ...) {
va_list ap;
fputs("## Error: ", stderr);
@ -15,15 +27,63 @@ static void print_err(const char *msg, ...) {
fputc('\n', stderr);
}
// Map from OSErr codes to messages. As a heuristic, this should include error
// codes caused by toolbox calls in this program which are readily understood by
// the user. It can exclude error codes that are likely to indicate a
// programming error.
struct error_message {
OSErr err;
const char *msg;
};
const struct error_message kErrorMessages[] = {
{dirFulErr, "directory full"}, // -33
{dskFulErr, "disk full"}, // -34
{ioErr, "I/O error"}, // -36
{bdNamErr, "bad name"}, // -37
{fnfErr, "file not found"}, // -43
{wPrErr, "disk is write-protected"}, // -44
{fLckdErr, "file is locked"}, // -45
{vLckdErr, "volume is locked"}, // -46
{dupFNErr, "destination already exists"}, // -48
{opWrErr, "file already open for writing"}, // -49
{paramErr, "parameter error"}, // -50
{permErr, "cannot write to locked file"}, // -54
{dirNFErr, "directory not found"}, // -120
{wrgVolTypErr, "not an HFS volume"}, // -123
{diffVolErr, "files on different volumes"}, // -1303
{afpAccessDenied, "user does not have access privileges (AFP)"}, // -5000
{afpObjectTypeErr,
"file/directory specified where directory/file expected"}, // -5025
{afpSameObjectErr, "objects are the same"}, // -5038
};
static const char *mac_strerror(OSErr err) {
int i, n = sizeof(kErrorMessages) / sizeof(*kErrorMessages);
for (i = 0; i < n; i++) {
if (kErrorMessages[i].err == err) {
return kErrorMessages[i].msg;
}
}
return NULL;
}
static void print_errcode(OSErr err, const char *msg, ...) {
va_list ap;
const char *emsg;
fputs("## Error: ", stderr);
va_start(ap, msg);
vfprintf(stderr, msg, ap);
va_end(ap);
fprintf(stderr, ": err=%d\n", err);
emsg = mac_strerror(err);
if (emsg != NULL) {
fprintf(stderr, ": %s (%d)\n", emsg, err);
} else {
fprintf(stderr, ": err=%d\n", err);
}
}
// Convert a C to Pascal string.
static int c2pstr(Str255 ostr, const char *istr) {
size_t n = strlen(istr);
if (n > 255) {
@ -36,6 +96,13 @@ static int c2pstr(Str255 ostr, const char *istr) {
return 0;
}
// Convert a Pascal to C string.
static void p2cstr(char *ostr, const unsigned char *istr) {
unsigned len = istr[0];
memcpy(ostr, istr + 1, len);
ostr[len] = '\0';
}
struct file_meta {
Boolean exists;
long modTime;
@ -46,15 +113,24 @@ enum {
kDestDir,
};
enum {
kModeAuto,
kModePush,
kModePull,
};
struct file_info {
Str31 name;
struct file_meta meta[2];
int mode;
};
// Array of metadata entries for files.
static Handle gFiles;
static unsigned gFileCount;
static unsigned gFileAlloc;
// Get the metadata entry for a file with the given name.
static struct file_info *get_file(const unsigned char *name) {
unsigned i, n = gFileCount, len, newAlloc;
struct file_info *file, *array;
@ -103,6 +179,7 @@ static struct file_info *get_file(const unsigned char *name) {
return file;
}
// Get the volume and directory ID for a directory from its path.
static int dir_from_path(short *vRefNum, long *dirID, const char *dirpath) {
Str255 ppath;
FSSpec spec;
@ -139,6 +216,8 @@ static int dir_from_path(short *vRefNum, long *dirID, const char *dirpath) {
return 0;
}
// Return true if a file with the given name should be included. The name is a
// Pascal string.
static int filter_name(unsigned char *name) {
int len, i, stem;
unsigned char *ext;
@ -167,6 +246,8 @@ static int filter_name(unsigned char *name) {
return 0;
}
// List files in a directory, filter them, and add the files matching the filter
// to the file list. The value of 'which' should be kSrcDir or kDestDir.
static int list_dir(short vRefNum, long dirID, int which) {
Str255 ppath;
CInfoPBRec ci;
@ -200,14 +281,318 @@ static int list_dir(short vRefNum, long dirID, int which) {
return 0;
}
static int command_main(char *destpath) {
short srcVol, destVol;
long srcDir, destDir;
struct file_info *array, *file;
// Read the entire data fork of a file. The result must be freed with
// DisposePtr.
static int read_file(FSSpec *spec, Ptr *data, long *length) {
CInfoPBRec ci;
Ptr ptr;
long dataLength, pos, count;
OSErr err;
short fref;
// Get file size.
memset(&ci, 0, sizeof(ci));
ci.hFileInfo.ioNamePtr = spec->name;
ci.hFileInfo.ioVRefNum = spec->vRefNum;
ci.hFileInfo.ioDirID = spec->parID;
err = PBGetCatInfoSync(&ci);
if (err != 0) {
print_errcode(err, "could not get file metadata");
return 1;
}
if ((ci.hFileInfo.ioFlAttrib & kioFlAttribDirMask) != 0) {
print_err("is a directory");
return 1;
}
dataLength = ci.hFileInfo.ioFlLgLen;
if (dataLength > kMaxFileSize) {
print_err("file is too large: size=%ld, max=%ld", dataLength,
kMaxFileSize);
return 1;
}
// Allocate memory.
ptr = NewPtr(dataLength);
err = MemError();
if (err != 0) {
print_errcode(err, "out of memory");
return 1;
}
// Read file.
err = FSpOpenDF(spec, fsRdPerm, &fref);
if (err != 0) {
DisposePtr(ptr);
print_errcode(err, "could not open file");
return 1;
}
pos = 0;
while (pos < dataLength) {
count = dataLength - pos;
err = FSRead(fref, &count, ptr + pos);
if (err != 0) {
DisposePtr(ptr);
FSClose(fref);
print_errcode(err, "could not read file");
return 1;
}
pos += count;
}
FSClose(fref);
*data = ptr;
*length = dataLength;
return 0;
}
// Make an FSSpec for a temporary file.
static int make_temp(FSSpec *temp, short vRefNum, long dirID,
const unsigned char *name) {
Str31 tname;
unsigned pfxlen, maxpfx = 31 - 4;
OSErr err;
pfxlen = name[0];
if (pfxlen > maxpfx) {
pfxlen = maxpfx;
}
memcpy(tname + 1, name + 1, pfxlen);
memcpy(tname + 1 + pfxlen, ".tmp", 4);
tname[0] = pfxlen + 4;
err = FSMakeFSSpec(vRefNum, dirID, tname, temp);
if (err == 0) {
print_err("temporary file exists");
return 1;
} else if (err == fnfErr) {
return 0;
} else {
print_errcode(err, "could not create temp file spec");
return 1;
}
}
// Write the entire contents of a file.
static int write_file(FSSpec *dest, short tempVol, long tempDir, Ptr data,
long length, long modTime, Boolean destExists) {
FSSpec temp;
long pos, amt;
short ref;
HParamBlockRec pb;
CMovePBRec cm;
CInfoPBRec ci;
Str31 name;
OSErr err;
int r;
// Save the data to a temporary file.
r = make_temp(&temp, tempVol, tempDir, dest->name);
if (r != 0) {
return 1;
}
err = FSpCreate(&temp, 'MPS ', 'TEXT', smSystemScript);
if (err != 0) {
print_errcode(err, "could not create file");
return 1;
}
err = FSpOpenDF(&temp, fsRdWrPerm, &ref);
if (err != 0) {
print_errcode(err, "could not open temp file");
goto error;
}
pos = 0;
while (pos < length) {
amt = length - pos;
err = FSWrite(ref, &amt, data + pos);
if (err != 0) {
FSClose(ref);
print_errcode(err, "could not write temp file");
goto error;
}
pos += amt;
}
err = FSClose(ref);
if (err != 0) {
print_errcode(err, "could not close temp file");
goto error;
}
// Update the modification time.
memset(&ci, 0, sizeof(ci));
memcpy(name, temp.name, temp.name[0] + 1);
ci.hFileInfo.ioNamePtr = name;
ci.hFileInfo.ioVRefNum = temp.vRefNum;
ci.hFileInfo.ioDirID = temp.parID;
err = PBGetCatInfoSync(&ci);
if (err != 0) {
print_errcode(err, "could not get temp file info");
goto error;
}
memcpy(name, temp.name, temp.name[0] + 1);
ci.hFileInfo.ioNamePtr = name;
ci.hFileInfo.ioVRefNum = temp.vRefNum;
ci.hFileInfo.ioDirID = temp.parID;
ci.hFileInfo.ioFlMdDat = modTime;
err = PBSetCatInfoSync(&ci);
if (err != 0) {
print_errcode(err, "could not set temp file info");
goto error;
}
// First, try to exchange files if destination exists.
if (destExists) {
err = FSpExchangeFiles(&temp, dest);
if (err == 0) {
err = FSpDelete(&temp);
if (err != 0) {
print_errcode(err, "could not rename temporary file");
return 1;
}
return 0;
}
// paramErr: function not supported by volume.
if (err != paramErr) {
print_errcode(err, "could not delete temp file");
return 1;
}
// Otherwise, delete destination and move temp file over.
err = FSpDelete(dest);
if (err != 0) {
print_errcode(err, "could not remove destination file");
goto error;
}
}
// Next, try MoveRename.
memset(&pb, 0, sizeof(pb));
pb.copyParam.ioNamePtr = temp.name;
pb.copyParam.ioVRefNum = temp.vRefNum;
pb.copyParam.ioNewName = dest->name;
pb.copyParam.ioNewDirID = dest->parID;
pb.copyParam.ioDirID = temp.parID;
err = PBHMoveRenameSync(&pb);
if (err == 0) {
return 0;
}
// paramErr: function not supported by volume.
if (err != paramErr) {
print_errcode(err, "could not rename temporary file");
goto error;
}
// Finally, try move and then rename.
if (dest->parID != temp.parID) {
memset(&cm, 0, sizeof(cm));
cm.ioNamePtr = temp.name;
cm.ioVRefNum = temp.vRefNum;
cm.ioNewDirID = dest->parID;
cm.ioDirID = temp.parID;
err = PBCatMoveSync(&cm);
if (err != 0) {
print_errcode(err, "could not move temporary file");
goto error;
}
temp.parID = dest->parID;
}
if (memcmp(dest->name, temp.name, dest->name[0] + 1) != 0) {
err = FSpRename(&temp, dest->name);
if (err != 0) {
print_errcode(err, "could not rename temporary file");
goto error;
}
}
return 0;
error:
err = FSpDelete(&temp);
if (err != 0) {
print_errcode(err, "could not delete temp file");
}
return 1;
}
// Copy the source file to the destination file. A temporary file is created in
// the specified temporary directory.
static int sync_file(struct file_info *file, convert_func func, long srcVol,
short srcDir, long destVol, short destDir, long tempVol,
short tempDir, long modTime) {
FSSpec src, dest;
Ptr srcData = NULL, destData = NULL;
long srcLength, destLength;
int r;
OSErr err;
unsigned char *outptr, *outend;
const unsigned char *inptr, *inend;
Boolean destExists;
// Create file specs.
err = FSMakeFSSpec(srcVol, srcDir, file->name, &src);
if (err != 0) {
print_errcode(err, "could not create source spec");
return 1;
}
err = FSMakeFSSpec(destVol, destDir, file->name, &dest);
if (err == 0) {
destExists = TRUE;
} else if (err == fnfErr) {
destExists = FALSE;
} else if (err != 0) {
print_errcode(err, "could not create destination spec");
return 1;
}
// Read the source file into memory.
r = read_file(&src, &srcData, &srcLength);
if (r != 0) {
return 1;
}
// Convert data.
destLength = srcLength + (srcLength >> 2) + 16;
destData = NewPtr(destLength);
err = MemError();
if (err != 0) {
print_errcode(err, "out of memory");
goto error;
}
outptr = (unsigned char *)destData;
outend = outptr + destLength;
inptr = (unsigned char *)srcData;
inend = inptr + srcLength;
func(&outptr, outend, &inptr, inend);
if (inptr != inend) {
print_err("conversion function failed");
goto error;
}
destLength = outptr - (unsigned char *)destData;
r = write_file(&dest, tempVol, tempDir, destData, destLength, destExists,
modTime);
if (r != 0) {
goto error;
}
// Clean up.
DisposePtr(srcData);
DisposePtr(destData);
return 0;
error:
if (srcData != NULL) {
DisposePtr(srcData);
}
if (destData != NULL) {
DisposePtr(destData);
}
return 1;
}
static int command_main(char *destpath, int mode) {
short srcVol, destVol, tempVol;
long srcDir, destDir, tempDir;
struct file_info *array, *file, *srcNewer, *destNewer;
OSErr err;
int r, i, n;
char name[32];
// Get handles to src and dest directories.
err = HGetVol(NULL, &srcVol, &srcDir);
if (err != 0) {
print_errcode(err, "HGetVol");
@ -217,6 +602,8 @@ static int command_main(char *destpath) {
if (r != 0) {
return 1;
}
// List files in src and dest directories.
r = list_dir(srcVol, srcDir, kSrcDir);
if (r != 0) {
return 1;
@ -229,32 +616,124 @@ static int command_main(char *destpath) {
print_err("no files");
return 1;
}
HLock(gFiles);
array = (struct file_info *)*gFiles;
n = gFileCount;
// Figure out the direction for each file.
srcNewer = NULL;
destNewer = NULL;
for (i = 0; i < n; i++) {
file = &array[i];
memcpy(name, file->name + 1, file->name[0]);
name[file->name[0]] = '\0';
printf("File: %s\n", name);
printf(" exist: %c %c\n", file->meta[0].exists ? 'Y' : '-',
file->meta[1].exists ? 'Y' : '-');
printf(" modTime: %ld %ld\n", file->meta[0].modTime,
file->meta[1].modTime);
if (!file->meta[kSrcDir].exists) {
file->mode = kModePull;
destNewer = file;
} else if (!file->meta[kDestDir].exists) {
file->mode = kModePush;
srcNewer = file;
} else if (file->meta[kSrcDir].modTime < file->meta[kDestDir].modTime) {
file->mode = kModePull;
destNewer = file;
} else if (file->meta[kSrcDir].modTime > file->meta[kDestDir].modTime) {
file->mode = kModePush;
srcNewer = file;
}
}
// Figure out the mode: push or pull.
if (mode == kModeAuto) {
if (srcNewer != NULL) {
if (destNewer != NULL) {
fputs("## Error: both source and destination have new files\n",
stderr);
p2cstr(name, srcNewer->name);
fprintf(stderr, "## New file in source: %s\n", name);
p2cstr(name, destNewer->name);
fprintf(stderr, "## New file in destination: %s\n", name);
return 1;
}
mode = kModePush;
fputs("## Mode: push\n", stderr);
} else if (destNewer != NULL) {
mode = kModePull;
fputs("## Mode: pull\n", stderr);
} else {
fputs("## No changes.\n", stderr);
return 0;
}
}
// Synchronize the files.
tempVol = 0;
tempDir = 0;
for (i = 0; i < n; i++) {
file = &array[i];
if (file->mode == mode) {
if (mode == kModePush) {
// When pushing, we use the destination directory as the
// temporary folder, to avoid crossing filesystem boundaries on
// the host.
r = sync_file(file, mac_to_unix, srcVol, srcDir, destVol,
destDir, destVol, destDir,
file->meta[kSrcDir].modTime);
} else {
if (tempDir == 0) {
err = FindFolder(destVol, kTemporaryFolderType, TRUE,
&tempVol, &tempDir);
if (err != 0) {
print_errcode(err, "could not find temporary folder");
return 1;
}
}
r = sync_file(file, mac_to_unix, destVol, destDir, srcVol,
srcDir, tempVol, tempDir,
file->meta[kDestDir].modTime);
}
if (r) {
p2cstr(name, file->name);
print_err("failed to copy file: %s", name);
return 1;
}
} else if (file->mode != kModeAuto) {
p2cstr(name, file->name);
fprintf(stderr, "## Refusing to overwrite '%s', file is newer\n",
name);
}
}
HUnlock(gFiles);
return 0;
}
int main(int argc, char **argv) {
int r;
char *destDir = NULL, *arg, *opt;
int i, r, mode = kModeAuto;
if (argc != 2) {
fputs("## Usage: SyncFiles <directory>\n", stderr);
return 1;
for (i = 1; i < argc; i++) {
arg = argv[i];
if (*arg == '-') {
opt = arg + 1;
if (*opt == '-') {
opt++;
}
if (strcmp(opt, "push") == 0) {
mode = kModePush;
} else if (strcmp(opt, "pull") == 0) {
mode = kModePull;
} else {
print_err("unknown flag: %s", arg);
return 1;
}
} else {
if (destDir != NULL) {
print_err("unexpected argument: %s", arg);
return 1;
}
destDir = arg;
}
}
r = command_main(argv[1]);
r = command_main(argv[1], mode);
if (gFiles != NULL) {
DisposeHandle(gFiles);
}