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 | |