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  */
process(int argc,char * const argv[],unsigned int options)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  */
main(int argc,char * const argv[])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