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