blob: eb1de1f96f4147351808a3784c0c496eccec6863 [file] [log] [blame]
The Android Open Source Project88b60792009-03-03 19:28:42 -08001/*
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 */
41int 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
164bail:
165 free(stripDest);
166 return retVal;
167}
168
169/*
170 * Set up the options.
171 */
172int 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