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 253