Commit | Line | Data |
---|---|---|
1f978f4c | 1 | /* |
ba5e8546 KB |
2 | * Copyright (c) 1988, 1993 |
3 | * The Regents of the University of California. All rights reserved. | |
4d2ae24a KB |
4 | * |
5 | * This code is derived from software contributed to Berkeley by | |
6 | * David Hitz of Auspex Systems Inc. | |
7 | * | |
27c71911 | 8 | * %sccs.include.redist.c% |
4d2ae24a KB |
9 | */ |
10 | ||
11 | #ifndef lint | |
ba5e8546 KB |
12 | static char copyright[] = |
13 | "@(#) Copyright (c) 1988, 1993\n\ | |
14 | The Regents of the University of California. All rights reserved.\n"; | |
4d2ae24a KB |
15 | #endif /* not lint */ |
16 | ||
17 | #ifndef lint | |
ba5e8546 | 18 | static char sccsid[] = "@(#)cp.c 8.1 (Berkeley) %G%"; |
4d2ae24a KB |
19 | #endif /* not lint */ |
20 | ||
21 | /* | |
1b37659d | 22 | * Cp copies source files to target files. |
ae5e5236 | 23 | * |
1b37659d KB |
24 | * The global PATH_T structure "to" always contains the path to the |
25 | * current target file. Since fts(3) does not change directories, | |
26 | * this path can be either absolute or dot-realative. | |
ae5e5236 | 27 | * |
59104c27 EA |
28 | * The basic algorithm is to initialize "to" and use fts(3) to traverse |
29 | * the file hierarchy rooted in the argument list. A trivial case is the | |
1b37659d | 30 | * case of 'cp file1 file2'. The more interesting case is the case of |
59104c27 | 31 | * 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the |
1b37659d KB |
32 | * path (relative to the root of the traversal) is appended to dir (stored |
33 | * in "to") to form the final target path. | |
1f978f4c KM |
34 | */ |
35 | ||
4d2ae24a | 36 | #include <sys/param.h> |
84592a94 | 37 | #include <sys/stat.h> |
e737685c | 38 | #include <sys/mman.h> |
0908a03a | 39 | #include <sys/time.h> |
1b37659d | 40 | |
1945b3da KB |
41 | #include <dirent.h> |
42 | #include <fcntl.h> | |
4d2ae24a | 43 | #include <errno.h> |
1945b3da KB |
44 | #include <unistd.h> |
45 | #include <stdio.h> | |
335a3e32 | 46 | #include <stdlib.h> |
6ebcb998 | 47 | #include <string.h> |
59104c27 | 48 | #include <fts.h> |
e737685c KB |
49 | #include "extern.h" |
50 | ||
1b37659d KB |
51 | #define STRIP_TRAILING_SLASH(p) { \ |
52 | while ((p).p_end > (p).p_path && (p).p_end[-1] == '/') \ | |
53 | *--(p).p_end = 0; \ | |
54 | } | |
ae5e5236 | 55 | |
1b37659d KB |
56 | static void copy __P((FTS *)); |
57 | static int mastercmp __P((const FTSENT **, const FTSENT **)); | |
59104c27 | 58 | |
be970122 | 59 | PATH_T to = { to.p_path, "" }; |
a783899b | 60 | |
1a4aed3b KB |
61 | uid_t myuid; |
62 | int exit_val, myumask; | |
1b37659d | 63 | int iflag, orflag, pflag, rflag; |
e737685c | 64 | char *progname; |
1a4aed3b | 65 | |
1b37659d KB |
66 | static enum { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE } type; |
67 | ||
59104c27 | 68 | int |
84592a94 | 69 | main(argc, argv) |
4d2ae24a | 70 | int argc; |
1b37659d | 71 | char *argv[]; |
84592a94 | 72 | { |
122b48d5 | 73 | struct stat to_stat, tmp_stat; |
59104c27 | 74 | FTS *ftsp; |
1b37659d KB |
75 | register int c, r; |
76 | int fts_options, Hflag, hflag; | |
77 | char *p, *target; | |
d57b5e76 KB |
78 | |
79 | /* | |
be970122 KB |
80 | * The utility cp(1) is used by mv(1) -- except for usage statements, |
81 | * print the "called as" program name. | |
d57b5e76 | 82 | */ |
be970122 | 83 | progname = (p = rindex(*argv,'/')) ? ++p : *argv; |
4d2ae24a | 84 | |
59104c27 EA |
85 | /* |
86 | * Symbolic link handling is as follows: | |
87 | * 1. Follow all symbolic links on the argument line. | |
88 | * 2. Otherwise, don't follow symbolic links UNLESS options -h | |
89 | * (in conjuction with -R) or -r (for backward compatibility) are | |
267af6c6 EA |
90 | * set, in which case follow all symbolic links, or when the -H |
91 | * option is set (in conjuction with -R), in which case follow | |
92 | * all symbolic links on the command line. | |
93 | * | |
59104c27 | 94 | */ |
1b37659d | 95 | Hflag = hflag = 0; |
267af6c6 | 96 | fts_options = FTS_NOCHDIR | FTS_LOGICAL; |
59104c27 EA |
97 | while ((c = getopt(argc, argv, "HRfhipr")) != EOF) |
98 | switch ((char)c) { | |
99 | case 'H': | |
122b48d5 | 100 | Hflag = 1; |
59104c27 EA |
101 | fts_options |= FTS_COMFOLLOW; |
102 | break; | |
a783899b | 103 | case 'f': |
335a3e32 | 104 | iflag = 0; |
a783899b | 105 | break; |
0493752e | 106 | case 'h': |
267af6c6 | 107 | hflag = 1; |
0493752e | 108 | break; |
4d2ae24a | 109 | case 'i': |
335a3e32 | 110 | iflag = isatty(fileno(stdin)); |
4d2ae24a KB |
111 | break; |
112 | case 'p': | |
335a3e32 | 113 | pflag = 1; |
4d2ae24a | 114 | break; |
4d2ae24a | 115 | case 'R': |
267af6c6 EA |
116 | fts_options &= ~FTS_LOGICAL; |
117 | fts_options |= FTS_PHYSICAL; | |
335a3e32 | 118 | rflag = 1; |
4d2ae24a | 119 | break; |
3c3c3fc0 KB |
120 | case 'r': |
121 | orflag = 1; | |
59104c27 EA |
122 | fts_options &= ~FTS_PHYSICAL; |
123 | fts_options |= FTS_LOGICAL; | |
3c3c3fc0 | 124 | break; |
4d2ae24a KB |
125 | case '?': |
126 | default: | |
127 | usage(); | |
128 | break; | |
129 | } | |
0493752e KB |
130 | argc -= optind; |
131 | argv += optind; | |
ae5e5236 | 132 | |
4d2ae24a KB |
133 | if (argc < 2) |
134 | usage(); | |
ae5e5236 | 135 | |
1b37659d KB |
136 | if (orflag) { |
137 | if (rflag) { | |
138 | (void)fprintf(stderr, | |
139 | "cp: the -R and -r options are mutually exclusive.\n"); | |
140 | exit(1); | |
141 | } | |
142 | if (Hflag || hflag) { | |
143 | (void)fprintf(stderr, | |
144 | "cp: the -r and the -H and -h options are mutually exclusive.\n"); | |
145 | exit(1); | |
146 | } | |
147 | } | |
148 | ||
267af6c6 EA |
149 | if (hflag) { |
150 | fts_options &= ~FTS_PHYSICAL; | |
151 | fts_options |= FTS_LOGICAL; | |
152 | } | |
153 | ||
1a4aed3b KB |
154 | myuid = getuid(); |
155 | ||
1b37659d | 156 | /* Copy the umask for explicit mode setting. */ |
1a4aed3b KB |
157 | myumask = umask(0); |
158 | (void)umask(myumask); | |
159 | ||
1b37659d | 160 | /* Save the target base in "to". */ |
59104c27 EA |
161 | target = argv[--argc]; |
162 | if (strlen(target) > MAXPATHLEN) { | |
163 | err("%s: name too long", target); | |
be970122 | 164 | exit(1); |
59104c27 EA |
165 | } |
166 | (void)strcpy(to.p_path, target); | |
167 | to.p_end = to.p_path + strlen(to.p_path); | |
168 | if (to.p_path == to.p_end) { | |
169 | *to.p_end++ = '.'; | |
170 | *to.p_end = 0; | |
171 | } | |
172 | STRIP_TRAILING_SLASH(to); | |
173 | to.target_end = to.p_end; | |
ae5e5236 | 174 | |
1b37659d | 175 | /* Set end of argument list for fts(3). */ |
59104c27 EA |
176 | argv[argc] = NULL; |
177 | ||
ae5e5236 | 178 | /* |
4d2ae24a KB |
179 | * Cp has two distinct cases: |
180 | * | |
1b37659d KB |
181 | * cp [-R] source target |
182 | * cp [-R] source1 ... sourceN directory | |
4d2ae24a KB |
183 | * |
184 | * In both cases, source can be either a file or a directory. | |
185 | * | |
186 | * In (1), the target becomes a copy of the source. That is, if the | |
187 | * source is a file, the target will be a file, and likewise for | |
188 | * directories. | |
189 | * | |
190 | * In (2), the real target is not directory, but "directory/source". | |
ae5e5236 | 191 | */ |
4d2ae24a KB |
192 | r = stat(to.p_path, &to_stat); |
193 | if (r == -1 && errno != ENOENT) { | |
e737685c | 194 | err("%s: %s", to.p_path, strerror(errno)); |
4d2ae24a KB |
195 | exit(1); |
196 | } | |
be970122 | 197 | if (r == -1 || !S_ISDIR(to_stat.st_mode)) { |
4d2ae24a KB |
198 | /* |
199 | * Case (1). Target is not a directory. | |
122b48d5 | 200 | */ |
4d2ae24a KB |
201 | if (argc > 1) { |
202 | usage(); | |
203 | exit(1); | |
204 | } | |
122b48d5 EA |
205 | /* |
206 | * Need to detect the case: | |
1b37659d KB |
207 | * cp -R dir foo |
208 | * Where dir is a directory and foo does not exist, where | |
209 | * we want pathname concatenations turned on but not for | |
210 | * the initial mkdir(). | |
122b48d5 EA |
211 | */ |
212 | if (r == -1) { | |
213 | if (orflag || (rflag && (hflag || Hflag))) | |
214 | stat(*argv, &tmp_stat); | |
215 | else | |
216 | lstat(*argv, &tmp_stat); | |
217 | ||
218 | if (S_ISDIR(tmp_stat.st_mode) && (rflag || orflag)) | |
219 | type = DIR_TO_DNE; | |
220 | else | |
221 | type = FILE_TO_FILE; | |
222 | } else | |
223 | type = FILE_TO_FILE; | |
1b37659d | 224 | } else |
4d2ae24a KB |
225 | /* |
226 | * Case (2). Target is a directory. | |
227 | */ | |
59104c27 EA |
228 | type = FILE_TO_DIR; |
229 | ||
230 | if ((ftsp = fts_open(argv, fts_options, mastercmp)) == NULL) { | |
231 | err("%s", strerror(errno)); | |
232 | exit(1); | |
ae5e5236 | 233 | } |
1b37659d | 234 | copy(ftsp); |
59104c27 EA |
235 | fts_close(ftsp); |
236 | ||
4d2ae24a | 237 | exit(exit_val); |
84592a94 BJ |
238 | } |
239 | ||
e737685c | 240 | static void |
1b37659d | 241 | copy(ftsp) |
59104c27 | 242 | FTS *ftsp; |
84592a94 | 243 | { |
59104c27 | 244 | register FTSENT *curr; |
122b48d5 | 245 | register int base, nlen; |
1b37659d KB |
246 | struct stat to_stat; |
247 | int dne; | |
248 | char *c, *n; | |
249 | ||
250 | while (curr = fts_read(ftsp)) { | |
251 | switch(curr->fts_info) { | |
252 | case FTS_NS: | |
253 | case FTS_ERR: | |
254 | err("%s: %s", | |
255 | curr->fts_path, strerror(curr->fts_errno)); | |
24e5a125 | 256 | exit_val = 1; |
15a96ff4 | 257 | continue; |
1b37659d | 258 | case FTS_DC: |
59104c27 | 259 | err("%s: directory causes a cycle", curr->fts_path); |
4d2ae24a | 260 | exit_val = 1; |
1b37659d KB |
261 | continue; |
262 | case FTS_DP: | |
263 | continue; | |
4d2ae24a | 264 | } |
59104c27 EA |
265 | |
266 | /* | |
122b48d5 | 267 | * If we are in case (2) or (3) above, we need to append the |
59104c27 EA |
268 | * source name to the target name. |
269 | */ | |
122b48d5 | 270 | if (type != FILE_TO_FILE) { |
1b37659d KB |
271 | if ((curr->fts_namelen + |
272 | to.target_end - to.p_path + 1) > MAXPATHLEN) { | |
59104c27 | 273 | err("%s/%s: name too long (not copied)", |
91f6ae98 | 274 | to.p_path, curr->fts_name); |
59104c27 EA |
275 | continue; |
276 | } | |
122b48d5 EA |
277 | |
278 | /* | |
1b37659d KB |
279 | * Need to remember the roots of traversals to create |
280 | * correct pathnames. If there's a directory being | |
281 | * copied to a non-existent directory, e.g. | |
282 | * cp -R a/dir noexist | |
283 | * the resulting path name should be noexist/foo, not | |
284 | * noexist/dir/foo (where foo is a file in dir), which | |
285 | * is the case where the target exists. | |
286 | * | |
287 | * Also, check for "..". This is for correct path | |
288 | * concatentation for paths ending in "..", e.g. | |
289 | * cp -R .. /tmp | |
290 | * Paths ending in ".." are changed to ".". This is | |
291 | * tricky, but seems the easiest way to fix the problem. | |
122b48d5 | 292 | */ |
1b37659d | 293 | if (curr->fts_level == FTS_ROOTLEVEL) |
122b48d5 EA |
294 | if (type != DIR_TO_DNE) { |
295 | c = rindex(curr->fts_path, '/'); | |
1b37659d | 296 | base = (c == NULL) ? 0 : |
122b48d5 EA |
297 | (int) (c - curr->fts_path + 1); |
298 | ||
122b48d5 EA |
299 | if (!strcmp(&curr->fts_path[base], |
300 | "..")) | |
301 | base += 1; | |
302 | } else | |
303 | base = curr->fts_pathlen; | |
122b48d5 | 304 | |
59104c27 EA |
305 | if (to.target_end[-1] != '/') { |
306 | *to.target_end = '/'; | |
307 | *(to.target_end + 1) = 0; | |
308 | } | |
122b48d5 EA |
309 | n = &curr->fts_path[base]; |
310 | nlen = curr->fts_pathlen - base; | |
311 | ||
312 | (void)strncat(to.target_end + 1, n, nlen); | |
313 | to.p_end = to.target_end + nlen + 1; | |
59104c27 EA |
314 | *to.p_end = 0; |
315 | STRIP_TRAILING_SLASH(to); | |
316 | } | |
317 | ||
318 | /* Not an error but need to remember it happened */ | |
319 | if (stat(to.p_path, &to_stat) == -1) | |
320 | dne = 1; | |
321 | else { | |
322 | if (to_stat.st_dev == curr->fts_statp->st_dev && | |
323 | to_stat.st_ino == curr->fts_statp->st_ino) { | |
324 | (void)fprintf(stderr, | |
1b37659d | 325 | "%s: %s and %s are identical (not copied).\n", |
59104c27 EA |
326 | progname, to.p_path, curr->fts_path); |
327 | exit_val = 1; | |
bd6a97fa KB |
328 | if (S_ISDIR(curr->fts_statp->st_mode)) |
329 | (void)fts_set(ftsp, curr, FTS_SKIP); | |
122b48d5 | 330 | continue; |
59104c27 EA |
331 | } |
332 | dne = 0; | |
333 | } | |
334 | ||
1b37659d | 335 | switch (curr->fts_statp->st_mode & S_IFMT) { |
59104c27 EA |
336 | case S_IFLNK: |
337 | copy_link(curr, !dne); | |
338 | break; | |
339 | case S_IFDIR: | |
340 | if (!rflag && !orflag) { | |
341 | (void)fprintf(stderr, | |
342 | "%s: %s is a directory (not copied).\n", | |
343 | progname, curr->fts_path); | |
0ad5408b | 344 | (void)fts_set(ftsp, curr, FTS_SKIP); |
59104c27 | 345 | exit_val = 1; |
e9239d20 | 346 | break; |
59104c27 EA |
347 | } |
348 | if (dne) { | |
24e5a125 KB |
349 | /* |
350 | * If the directory doesn't exist, create the new | |
351 | * one with the from file mode plus owner RWX bits, | |
352 | * modified by the umask. Trade-off between being | |
353 | * able to write the directory (if from directory is | |
354 | * 555) and not causing a permissions race. If the | |
355 | * umask blocks owner writes cp fails. | |
356 | */ | |
59104c27 EA |
357 | if (mkdir(to.p_path, |
358 | curr->fts_statp->st_mode|S_IRWXU) < 0) { | |
359 | err("%s: %s", to.p_path, | |
360 | strerror(errno)); | |
361 | return; | |
362 | } | |
1b37659d | 363 | } else if (!S_ISDIR(to_stat.st_mode)) { |
59104c27 EA |
364 | (void)fprintf(stderr, |
365 | "%s: %s: not a directory.\n", progname, | |
366 | to.p_path); | |
4d2ae24a KB |
367 | return; |
368 | } | |
59104c27 EA |
369 | /* |
370 | * If not -p and directory didn't exist, set it to be | |
371 | * the same as the from directory, umodified by the | |
372 | * umask; arguably wrong, but it's been that way | |
373 | * forever. | |
374 | */ | |
375 | if (pflag) | |
376 | setfile(curr->fts_statp, 0); | |
377 | else if (dne) | |
378 | (void)chmod(to.p_path, | |
379 | curr->fts_statp->st_mode); | |
380 | break; | |
381 | case S_IFCHR: | |
382 | case S_IFBLK: | |
267af6c6 | 383 | if (rflag) |
59104c27 | 384 | copy_special(curr->fts_statp, !dne); |
267af6c6 EA |
385 | else |
386 | copy_file(curr, dne); | |
59104c27 EA |
387 | break; |
388 | case S_IFIFO: | |
267af6c6 | 389 | if (rflag) |
59104c27 | 390 | copy_fifo(curr->fts_statp, !dne); |
267af6c6 EA |
391 | else |
392 | copy_file(curr, dne); | |
59104c27 EA |
393 | break; |
394 | default: | |
395 | copy_file(curr, dne); | |
396 | break; | |
4d2ae24a | 397 | } |
ae5e5236 | 398 | } |
4d2ae24a | 399 | } |
ae5e5236 | 400 | |
59104c27 | 401 | /* |
1b37659d KB |
402 | * mastercmp -- |
403 | * The comparison function for the copy order. The order is to copy | |
404 | * non-directory files before directory files. The reason for this | |
405 | * is because files tend to be in the same cylinder group as their | |
406 | * parent directory, whereas directories tend not to be. Copying the | |
407 | * files first reduces seeking. | |
59104c27 | 408 | */ |
59104c27 EA |
409 | static int |
410 | mastercmp(a, b) | |
411 | const FTSENT **a, **b; | |
0493752e | 412 | { |
1b37659d | 413 | register int a_info, b_info; |
59104c27 EA |
414 | |
415 | a_info = (*a)->fts_info; | |
416 | if (a_info == FTS_ERR || a_info == FTS_NS || a_info == FTS_DNR) | |
1b37659d | 417 | return (0); |
59104c27 EA |
418 | b_info = (*b)->fts_info; |
419 | if (b_info == FTS_ERR || b_info == FTS_NS || b_info == FTS_DNR) | |
1b37659d | 420 | return (0); |
59104c27 | 421 | if (a_info == FTS_D) |
1b37659d | 422 | return (-1); |
59104c27 | 423 | if (b_info == FTS_D) |
1b37659d KB |
424 | return (1); |
425 | return (0); | |
0493752e | 426 | } |