/* * Copyright (c) 2013, Kelvin W Sherlock * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include #include /* Duplicate # duplicate files and directories Duplicate [-y | -n | -c] [-p] [-d | -r] name... target > progress -y # overwrite target files (avoids dialog) -n # don't overwrite target files (avoids dialog) -c # cancel if conflict occurs (avoids dialog) -p # write progress information to diagnostics -d # duplicate data fork only -r # duplicate resource fork only -rs # resolve leaf aliases in the source path(s) -rt # resolve leaf aliases in the target path -f # preserve Finder icon locations */ // todo -- support src1 src2 ... dest/ char *c2p(const char *cp) { int length; char *p; if (!cp) return NULL; length = strlen(cp); if (length > 255) { fprintf(stderr, "Error: Pathname is too long.\n"); exit(1); return NULL; } p = malloc(length + 2); // + 2 for \0 and length. if (!p) { fprintf(stderr, "Error: unable to allocate memory.\n"); exit(1); return NULL; } p[0] = length; memcpy(p + 1, cp, length + 1); return p; } void help(void) { fprintf(stdout, "Usage: Duplicate [-y | -n | -c] [-p] [-d |-r] source destination\n"); } int getopt(int *opts, int argc, char **argv) { int i = 0; for (i = 1; i < argc; ++i) { char *str = argv[i]; char c = str[0]; if (c != '-') return i; ++str; // -- to terminate if (str[0] == '-' && str[1] == 0) return i + 1; // skip -rt, -rs if (str[0] == 'r' && str[1] == 's' && str[2] == 0) continue; if (str[0] == 'r' && str[1] == 't' && str[2] == 0) continue; for (; *str; ++str) { c = *str; switch(c) { case 'r': case 'd': opts['r' - 'a'] = 0; opts['d' - 'a'] = 0; opts[c - 'a'] = 1; break; case 'y': case 'n': case 'c': opts['y' - 'a'] = 0; opts['n' - 'a'] = 0; opts['c' - 'a'] = 0; opts[c - 'a'] = 1; break; case 'p': opts[c - 'a'] = 1; break; case 'h': help(); exit(0); break; default: fprintf(stderr, "Duplicate - Invalid flag: \"%c\"\n", c); exit(1); break; } } } return i; } int copyFork(const char *src, const char *dest, unsigned fork) { static char buffer[4096]; int rfd, wfd; int rv; fork = fork ? O_RSRC : 0; rfd = open(src, O_RDONLY | O_BINARY); if (rfd < 0) { fprintf(stderr, "Error opening %s: %s\n", src, strerror(errno)); return -1; } if (fork) { close(rfd); rfd = open(src, O_RDONLY | O_BINARY | fork); if (rfd < 0) { // no resource fork return 0; } } // no 3rd parameter to open. wfd = open(dest, O_WRONLY | O_BINARY | O_CREAT |O_TRUNC| fork); if (wfd < 0) { fprintf(stderr, "Error opening %s: %s\n", dest, strerror(errno)); close(rfd); return -1; } rv = -1; for (;;) { ssize_t rsize; ssize_t wsize; rsize = read(rfd, buffer, sizeof(buffer)); if (rsize == 0) { rv = 0; break; } if (rsize < 0) { if (errno == EINTR) continue; fprintf(stderr, "Error reading %s: %s\n", src, strerror(errno)); break; } wsize = write(wfd, buffer, rsize); if (wsize != rsize) { fprintf(stderr, "Error writing %s: %s\n", dest, strerror(errno)); break; } } close(rfd); close(wfd); return rv; } int copyFinderInfo(const char *src, const char *dest) { FInfo finderInfo; OSErr status; char *psrc; char *pdest; psrc = c2p(src); pdest = c2p(dest); if (!psrc || !pdest) return -1; // getfinfo/setfinfo seem to have bugs. memset(&finderInfo, 0, sizeof(finderInfo)); status = GetFInfo((unsigned char *)psrc, 0, &finderInfo); if (status == 0) { status = SetFInfo((unsigned char *)pdest, 0, &finderInfo); } free(psrc); free(pdest); if (status) return -1; return 0; } int createFile(const char *src, const char *dest) { FInfo finderInfo; OSErr status; char *psrc; char *pdest; psrc = c2p(src); pdest = c2p(dest); if (!psrc || !pdest) return -1; memset(&finderInfo, 0, sizeof(finderInfo)); status = GetFInfo((unsigned char *)psrc, 0, &finderInfo); status = Create((unsigned char *)pdest, 0, finderInfo.fdCreator, finderInfo.fdType); free(psrc); free(pdest); if (status) return -1; return 0; } // -1 - error. // 0 - no file // 1 - regular file // 2 - directory. int mode(const char *path) { char *pname; CInfoPBRec rec; OSErr status; memset(&rec, 0, sizeof(rec)); pname = c2p(path); if (!pname) return -1; rec.hFileInfo.ioNamePtr = (unsigned char *)pname; status = PBGetCatInfo(&rec, false); free(pname); if (status) return 0; if (rec.hFileInfo.ioFlAttrib & kioFlAttribDirMask) return 2; return 1; } int main(int argc, char **argv) { int opts[26]; int optind; int ok; char *src; char *dest; int m; memset(opts, 0, sizeof(opts)); opts['n' - 'a'] = 1; optind = getopt(opts, argc, argv); argc -= optind; argv += optind; if (argc != 2) { help(); exit(1); } src = argv[0]; dest = argv[1]; // 1. check if src exists // 2. check if dest exists // 3. copy data fork unless -r // 4. copy resource fork unless -d // 5. copy finder info. ok = 0; // -n - do not overwrite // -c - cancel if conflict m = mode(dest); if (m == 2) { fprintf(stderr, "Error: directory destination is not yet supported.\n"); exit(1); } // if (m == 0 && opts['r' - 'a']) if (m == 0) { // workaround to create the file if // only copying the resource fork. // TODO -- call Create(name, 0, creator, filetype) if (opts['p' - 'a']) printf("Creating file %s\n", dest); ok = createFile(src, dest); if (ok < 0) { fprintf(stderr, "Error creating %s\n", dest); exit(1); } } if (m == 1) { // todo -- should this check at the file level or at the fork level? // seems to check at the file level. // file exists. if (opts['c' - 'a'] || opts['n' - 'a']) { if (opts['p' - 'a']) { printf("File exists - nothing done.\n"); } exit(0); } } if (opts['r' - 'a'] == 0) { if (opts['p' - 'a']) printf("Copying Data Fork.\n"); ok = copyFork(src, dest, 0); } if (opts['d' - 'a'] == 0) { if (opts['p' - 'a']) printf("Copying Resource Fork.\n"); ok = copyFork(src, dest, 1); } // copy the finder info if -r and -d not specified. if (!opts['d' - 'a'] && !opts['r' - 'a']) { ok = copyFinderInfo(src, dest); } return ok; }