| /* |
| * Copyright 2005 The Android Open Source Project |
| * |
| * Android "cp" replacement. |
| * |
| * The GNU/Linux "cp" uses O_LARGEFILE in its open() calls, utimes() instead |
| * of utime(), and getxattr()/setxattr() instead of chmod(). These are |
| * probably "better", but are non-portable, and not necessary for our |
| * purposes. |
| */ |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <getopt.h> |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <utime.h> |
| #include <limits.h> |
| #include <errno.h> |
| #include <assert.h> |
| #include <host/CopyFile.h> |
| |
| /*#define DEBUG_MSGS*/ |
| #ifdef DEBUG_MSGS |
| # define DBUG(x) printf x |
| #else |
| # define DBUG(x) ((void)0) |
| #endif |
| |
| #define FSSEP '/' /* filename separator char */ |
| |
| |
| /* |
| * Process the command-line file arguments. |
| * |
| * Returns 0 on success. |
| */ |
| int process(int argc, char* const argv[], unsigned int options) |
| { |
| int retVal = 0; |
| int i, cc; |
| char* stripDest = NULL; |
| int stripDestLen; |
| struct stat destStat; |
| bool destMustBeDir = false; |
| struct stat sb; |
| |
| assert(argc >= 2); |
| |
| /* |
| * Check for and trim a trailing slash on the last arg. |
| * |
| * It's useful to be able to say "cp foo bar/" when you want to copy |
| * a single file into a directory. If you say "cp foo bar", and "bar" |
| * does not exist, it will create "bar", when what you really wanted |
| * was for the cp command to fail with "directory does not exist". |
| */ |
| stripDestLen = strlen(argv[argc-1]); |
| stripDest = malloc(stripDestLen+1); |
| memcpy(stripDest, argv[argc-1], stripDestLen+1); |
| if (stripDest[stripDestLen-1] == FSSEP) { |
| stripDest[--stripDestLen] = '\0'; |
| destMustBeDir = true; |
| } |
| |
| if (argc > 2) |
| destMustBeDir = true; |
| |
| /* |
| * Start with a quick check to ensure that, if we're expecting to copy |
| * to a directory, the target already exists and is actually a directory. |
| * It's okay if it's a symlink to a directory. |
| * |
| * If it turns out to be a directory, go ahead and raise the |
| * destMustBeDir flag so we do some path concatenation below. |
| */ |
| if (stat(stripDest, &sb) < 0) { |
| if (destMustBeDir) { |
| if (errno == ENOENT) |
| fprintf(stderr, |
| "acp: destination directory '%s' does not exist\n", |
| stripDest); |
| else |
| fprintf(stderr, "acp: unable to stat dest dir\n"); |
| retVal = 1; |
| goto bail; |
| } |
| } else { |
| if (S_ISDIR(sb.st_mode)) { |
| DBUG(("--- dest exists and is a dir, setting flag\n")); |
| destMustBeDir = true; |
| } else if (destMustBeDir) { |
| fprintf(stderr, |
| "acp: destination '%s' is not a directory\n", |
| stripDest); |
| retVal = 1; |
| goto bail; |
| } |
| } |
| |
| /* |
| * Copying files. |
| * |
| * Strip trailing slashes off. They shouldn't be there, but |
| * sometimes file completion will put them in for directories. |
| * |
| * The observed behavior of GNU and BSD cp is that they print warnings |
| * if something fails, but continue on. If any part fails, the command |
| * exits with an error status. |
| */ |
| for (i = 0; i < argc-1; i++) { |
| const char* srcName; |
| char* src; |
| char* dst; |
| int copyResult; |
| int srcLen; |
| |
| /* make a copy of the source name, and strip trailing '/' */ |
| srcLen = strlen(argv[i]); |
| src = malloc(srcLen+1); |
| memcpy(src, argv[i], srcLen+1); |
| |
| if (src[srcLen-1] == FSSEP) |
| src[--srcLen] = '\0'; |
| |
| /* find just the name part */ |
| srcName = strrchr(src, FSSEP); |
| if (srcName == NULL) { |
| srcName = src; |
| } else { |
| srcName++; |
| assert(*srcName != '\0'); |
| } |
| |
| if (destMustBeDir) { |
| /* concatenate dest dir and src name */ |
| int srcNameLen = strlen(srcName); |
| |
| dst = malloc(stripDestLen +1 + srcNameLen +1); |
| memcpy(dst, stripDest, stripDestLen); |
| dst[stripDestLen] = FSSEP; |
| memcpy(dst + stripDestLen+1, srcName, srcNameLen+1); |
| } else { |
| /* simple */ |
| dst = stripDest; |
| } |
| |
| /* |
| * Copy the source to the destination. |
| */ |
| copyResult = copyFile(src, dst, options); |
| |
| if (copyResult != 0) |
| retVal = 1; |
| |
| free(src); |
| if (dst != stripDest) |
| free(dst); |
| } |
| |
| bail: |
| free(stripDest); |
| return retVal; |
| } |
| |
| /* |
| * Set up the options. |
| */ |
| int main(int argc, char* const argv[]) |
| { |
| bool wantUsage; |
| int ic, retVal; |
| int verboseLevel; |
| unsigned int options; |
| |
| verboseLevel = 0; |
| options = 0; |
| wantUsage = false; |
| |
| while (1) { |
| ic = getopt(argc, argv, "defprtuv"); |
| if (ic < 0) |
| break; |
| |
| switch (ic) { |
| case 'd': |
| options |= COPY_NO_DEREFERENCE; |
| break; |
| case 'e': |
| options |= COPY_TRY_EXE; |
| break; |
| case 'f': |
| options |= COPY_FORCE; |
| break; |
| case 'p': |
| options |= COPY_PERMISSIONS; |
| break; |
| case 't': |
| options |= COPY_TIMESTAMPS; |
| break; |
| case 'r': |
| options |= COPY_RECURSIVE; |
| break; |
| case 'u': |
| options |= COPY_UPDATE_ONLY; |
| break; |
| case 'v': |
| verboseLevel++; |
| break; |
| default: |
| fprintf(stderr, "Unexpected arg -%c\n", ic); |
| wantUsage = true; |
| break; |
| } |
| |
| if (wantUsage) |
| break; |
| } |
| |
| options |= verboseLevel & COPY_VERBOSE_MASK; |
| |
| if (optind == argc-1) { |
| fprintf(stderr, "acp: missing destination file\n"); |
| return 2; |
| } else if (optind+2 > argc) |
| wantUsage = true; |
| |
| if (wantUsage) { |
| fprintf(stderr, "Usage: acp [OPTION]... SOURCE DEST\n"); |
| fprintf(stderr, " or: acp [OPTION]... SOURCE... DIRECTORY\n"); |
| fprintf(stderr, "\nOptions:\n"); |
| fprintf(stderr, " -d never follow (dereference) symbolic links\n"); |
| fprintf(stderr, " -e if source file doesn't exist, try adding " |
| "'.exe' [Win32 only]\n"); |
| fprintf(stderr, " -f use force, removing existing file if it's " |
| "not writeable\n"); |
| fprintf(stderr, " -p preserve mode, ownership\n"); |
| fprintf(stderr, " -r recursive copy\n"); |
| fprintf(stderr, " -t preserve timestamps\n"); |
| fprintf(stderr, " -u update only: don't copy if dest is newer\n"); |
| fprintf(stderr, " -v verbose output (-vv is more verbose)\n"); |
| return 2; |
| } |
| |
| retVal = process(argc-optind, argv+optind, options); |
| DBUG(("EXIT: %d\n", retVal)); |
| return retVal; |
| } |
| |