| The Android Open Source Project | 88b6079 | 2009-03-03 19:28:42 -0800 | [diff] [blame] | 1 | /* | 
|  | 2 | * Copyright 2005 The Android Open Source Project | 
|  | 3 | * | 
|  | 4 | * Android "cp" replacement. | 
|  | 5 | * | 
|  | 6 | * The GNU/Linux "cp" uses O_LARGEFILE in its open() calls, utimes() instead | 
|  | 7 | * of utime(), and getxattr()/setxattr() instead of chmod().  These are | 
|  | 8 | * probably "better", but are non-portable, and not necessary for our | 
|  | 9 | * purposes. | 
|  | 10 | */ | 
|  | 11 | #include <stdlib.h> | 
|  | 12 | #include <stdio.h> | 
|  | 13 | #include <string.h> | 
|  | 14 | #include <unistd.h> | 
|  | 15 | #include <sys/types.h> | 
|  | 16 | #include <sys/stat.h> | 
|  | 17 | #include <getopt.h> | 
|  | 18 | #include <dirent.h> | 
|  | 19 | #include <fcntl.h> | 
|  | 20 | #include <utime.h> | 
|  | 21 | #include <limits.h> | 
|  | 22 | #include <errno.h> | 
|  | 23 | #include <assert.h> | 
|  | 24 | #include <host/CopyFile.h> | 
|  | 25 |  | 
|  | 26 | /*#define DEBUG_MSGS*/ | 
|  | 27 | #ifdef DEBUG_MSGS | 
|  | 28 | # define DBUG(x) printf x | 
|  | 29 | #else | 
|  | 30 | # define DBUG(x) ((void)0) | 
|  | 31 | #endif | 
|  | 32 |  | 
|  | 33 | #define FSSEP '/'       /* filename separator char */ | 
|  | 34 |  | 
|  | 35 |  | 
|  | 36 | /* | 
|  | 37 | * Process the command-line file arguments. | 
|  | 38 | * | 
|  | 39 | * Returns 0 on success. | 
|  | 40 | */ | 
|  | 41 | int process(int argc, char* const argv[], unsigned int options) | 
|  | 42 | { | 
|  | 43 | int retVal = 0; | 
|  | 44 | int i, cc; | 
|  | 45 | char* stripDest = NULL; | 
|  | 46 | int stripDestLen; | 
|  | 47 | struct stat destStat; | 
|  | 48 | bool destMustBeDir = false; | 
|  | 49 | struct stat sb; | 
|  | 50 |  | 
|  | 51 | assert(argc >= 2); | 
|  | 52 |  | 
|  | 53 | /* | 
|  | 54 | * Check for and trim a trailing slash on the last arg. | 
|  | 55 | * | 
|  | 56 | * It's useful to be able to say "cp foo bar/" when you want to copy | 
|  | 57 | * a single file into a directory.  If you say "cp foo bar", and "bar" | 
|  | 58 | * does not exist, it will create "bar", when what you really wanted | 
|  | 59 | * was for the cp command to fail with "directory does not exist". | 
|  | 60 | */ | 
|  | 61 | stripDestLen = strlen(argv[argc-1]); | 
|  | 62 | stripDest = malloc(stripDestLen+1); | 
|  | 63 | memcpy(stripDest, argv[argc-1], stripDestLen+1); | 
|  | 64 | if (stripDest[stripDestLen-1] == FSSEP) { | 
|  | 65 | stripDest[--stripDestLen] = '\0'; | 
|  | 66 | destMustBeDir = true; | 
|  | 67 | } | 
|  | 68 |  | 
|  | 69 | if (argc > 2) | 
|  | 70 | destMustBeDir = true; | 
|  | 71 |  | 
|  | 72 | /* | 
|  | 73 | * Start with a quick check to ensure that, if we're expecting to copy | 
|  | 74 | * to a directory, the target already exists and is actually a directory. | 
|  | 75 | * It's okay if it's a symlink to a directory. | 
|  | 76 | * | 
|  | 77 | * If it turns out to be a directory, go ahead and raise the | 
|  | 78 | * destMustBeDir flag so we do some path concatenation below. | 
|  | 79 | */ | 
|  | 80 | if (stat(stripDest, &sb) < 0) { | 
|  | 81 | if (destMustBeDir) { | 
|  | 82 | if (errno == ENOENT) | 
|  | 83 | fprintf(stderr, | 
|  | 84 | "acp: destination directory '%s' does not exist\n", | 
|  | 85 | stripDest); | 
|  | 86 | else | 
|  | 87 | fprintf(stderr, "acp: unable to stat dest dir\n"); | 
|  | 88 | retVal = 1; | 
|  | 89 | goto bail; | 
|  | 90 | } | 
|  | 91 | } else { | 
|  | 92 | if (S_ISDIR(sb.st_mode)) { | 
|  | 93 | DBUG(("--- dest exists and is a dir, setting flag\n")); | 
|  | 94 | destMustBeDir = true; | 
|  | 95 | } else if (destMustBeDir) { | 
|  | 96 | fprintf(stderr, | 
|  | 97 | "acp: destination '%s' is not a directory\n", | 
|  | 98 | stripDest); | 
|  | 99 | retVal = 1; | 
|  | 100 | goto bail; | 
|  | 101 | } | 
|  | 102 | } | 
|  | 103 |  | 
|  | 104 | /* | 
|  | 105 | * Copying files. | 
|  | 106 | * | 
|  | 107 | * Strip trailing slashes off.  They shouldn't be there, but | 
|  | 108 | * sometimes file completion will put them in for directories. | 
|  | 109 | * | 
|  | 110 | * The observed behavior of GNU and BSD cp is that they print warnings | 
|  | 111 | * if something fails, but continue on.  If any part fails, the command | 
|  | 112 | * exits with an error status. | 
|  | 113 | */ | 
|  | 114 | for (i = 0; i < argc-1; i++) { | 
|  | 115 | const char* srcName; | 
|  | 116 | char* src; | 
|  | 117 | char* dst; | 
|  | 118 | int copyResult; | 
|  | 119 | int srcLen; | 
|  | 120 |  | 
|  | 121 | /* make a copy of the source name, and strip trailing '/' */ | 
|  | 122 | srcLen = strlen(argv[i]); | 
|  | 123 | src = malloc(srcLen+1); | 
|  | 124 | memcpy(src, argv[i], srcLen+1); | 
|  | 125 |  | 
|  | 126 | if (src[srcLen-1] == FSSEP) | 
|  | 127 | src[--srcLen] = '\0'; | 
|  | 128 |  | 
|  | 129 | /* find just the name part */ | 
|  | 130 | srcName = strrchr(src, FSSEP); | 
|  | 131 | if (srcName == NULL) { | 
|  | 132 | srcName = src; | 
|  | 133 | } else { | 
|  | 134 | srcName++; | 
|  | 135 | assert(*srcName != '\0'); | 
|  | 136 | } | 
|  | 137 |  | 
|  | 138 | if (destMustBeDir) { | 
|  | 139 | /* concatenate dest dir and src name */ | 
|  | 140 | int srcNameLen = strlen(srcName); | 
|  | 141 |  | 
|  | 142 | dst = malloc(stripDestLen +1 + srcNameLen +1); | 
|  | 143 | memcpy(dst, stripDest, stripDestLen); | 
|  | 144 | dst[stripDestLen] = FSSEP; | 
|  | 145 | memcpy(dst + stripDestLen+1, srcName, srcNameLen+1); | 
|  | 146 | } else { | 
|  | 147 | /* simple */ | 
|  | 148 | dst = stripDest; | 
|  | 149 | } | 
|  | 150 |  | 
|  | 151 | /* | 
|  | 152 | * Copy the source to the destination. | 
|  | 153 | */ | 
|  | 154 | copyResult = copyFile(src, dst, options); | 
|  | 155 |  | 
|  | 156 | if (copyResult != 0) | 
|  | 157 | retVal = 1; | 
|  | 158 |  | 
|  | 159 | free(src); | 
|  | 160 | if (dst != stripDest) | 
|  | 161 | free(dst); | 
|  | 162 | } | 
|  | 163 |  | 
|  | 164 | bail: | 
|  | 165 | free(stripDest); | 
|  | 166 | return retVal; | 
|  | 167 | } | 
|  | 168 |  | 
|  | 169 | /* | 
|  | 170 | * Set up the options. | 
|  | 171 | */ | 
|  | 172 | int main(int argc, char* const argv[]) | 
|  | 173 | { | 
|  | 174 | bool wantUsage; | 
|  | 175 | int ic, retVal; | 
|  | 176 | int verboseLevel; | 
|  | 177 | unsigned int options; | 
|  | 178 |  | 
|  | 179 | verboseLevel = 0; | 
|  | 180 | options = 0; | 
|  | 181 | wantUsage = false; | 
|  | 182 |  | 
|  | 183 | while (1) { | 
|  | 184 | ic = getopt(argc, argv, "defprtuv"); | 
|  | 185 | if (ic < 0) | 
|  | 186 | break; | 
|  | 187 |  | 
|  | 188 | switch (ic) { | 
|  | 189 | case 'd': | 
|  | 190 | options |= COPY_NO_DEREFERENCE; | 
|  | 191 | break; | 
|  | 192 | case 'e': | 
|  | 193 | options |= COPY_TRY_EXE; | 
|  | 194 | break; | 
|  | 195 | case 'f': | 
|  | 196 | options |= COPY_FORCE; | 
|  | 197 | break; | 
|  | 198 | case 'p': | 
|  | 199 | options |= COPY_PERMISSIONS; | 
|  | 200 | break; | 
|  | 201 | case 't': | 
|  | 202 | options |= COPY_TIMESTAMPS; | 
|  | 203 | break; | 
|  | 204 | case 'r': | 
|  | 205 | options |= COPY_RECURSIVE; | 
|  | 206 | break; | 
|  | 207 | case 'u': | 
|  | 208 | options |= COPY_UPDATE_ONLY; | 
|  | 209 | break; | 
|  | 210 | case 'v': | 
|  | 211 | verboseLevel++; | 
|  | 212 | break; | 
|  | 213 | default: | 
|  | 214 | fprintf(stderr, "Unexpected arg -%c\n", ic); | 
|  | 215 | wantUsage = true; | 
|  | 216 | break; | 
|  | 217 | } | 
|  | 218 |  | 
|  | 219 | if (wantUsage) | 
|  | 220 | break; | 
|  | 221 | } | 
|  | 222 |  | 
|  | 223 | options |= verboseLevel & COPY_VERBOSE_MASK; | 
|  | 224 |  | 
|  | 225 | if (optind == argc-1) { | 
|  | 226 | fprintf(stderr, "acp: missing destination file\n"); | 
|  | 227 | return 2; | 
|  | 228 | } else if (optind+2 > argc) | 
|  | 229 | wantUsage = true; | 
|  | 230 |  | 
|  | 231 | if (wantUsage) { | 
|  | 232 | fprintf(stderr, "Usage: acp [OPTION]... SOURCE DEST\n"); | 
|  | 233 | fprintf(stderr, "  or:  acp [OPTION]... SOURCE... DIRECTORY\n"); | 
|  | 234 | fprintf(stderr, "\nOptions:\n"); | 
|  | 235 | fprintf(stderr, "  -d  never follow (dereference) symbolic links\n"); | 
|  | 236 | fprintf(stderr, "  -e  if source file doesn't exist, try adding " | 
|  | 237 | "'.exe' [Win32 only]\n"); | 
|  | 238 | fprintf(stderr, "  -f  use force, removing existing file if it's " | 
|  | 239 | "not writeable\n"); | 
|  | 240 | fprintf(stderr, "  -p  preserve mode, ownership\n"); | 
|  | 241 | fprintf(stderr, "  -r  recursive copy\n"); | 
|  | 242 | fprintf(stderr, "  -t  preserve timestamps\n"); | 
|  | 243 | fprintf(stderr, "  -u  update only: don't copy if dest is newer\n"); | 
|  | 244 | fprintf(stderr, "  -v  verbose output (-vv is more verbose)\n"); | 
|  | 245 | return 2; | 
|  | 246 | } | 
|  | 247 |  | 
|  | 248 | retVal = process(argc-optind, argv+optind, options); | 
|  | 249 | DBUG(("EXIT: %d\n", retVal)); | 
|  | 250 | return retVal; | 
|  | 251 | } | 
|  | 252 |  |