xref: /original-bsd/bin/cp/cp.c (revision a95f03a8)
1 /*
2  * Copyright (c) 1988 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * David Hitz of Auspex Systems Inc.
7  *
8  * %sccs.include.redist.c%
9  */
10 
11 #ifndef lint
12 char copyright[] =
13 "@(#) Copyright (c) 1988 The Regents of the University of California.\n\
14  All rights reserved.\n";
15 #endif /* not lint */
16 
17 #ifndef lint
18 static char sccsid[] = "@(#)cp.c	5.36 (Berkeley) 07/22/92";
19 #endif /* not lint */
20 
21 /*
22  * Cp copies source files to target files.
23  *
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.
27  *
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
30  * case of 'cp file1 file2'.  The more interesting case is the case of
31  * 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the
32  * path (relative to the root of the traversal) is appended to dir (stored
33  * in "to") to form the final target path.
34  */
35 
36 #include <sys/param.h>
37 #include <sys/stat.h>
38 #include <sys/mman.h>
39 #include <sys/time.h>
40 
41 #include <dirent.h>
42 #include <fcntl.h>
43 #include <errno.h>
44 #include <unistd.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <fts.h>
49 #include "extern.h"
50 
51 #define	STRIP_TRAILING_SLASH(p) {					\
52         while ((p).p_end > (p).p_path && (p).p_end[-1] == '/')		\
53                 *--(p).p_end = 0;					\
54 }
55 
56 static void	copy __P((FTS *));
57 static int	mastercmp __P((const FTSENT **, const FTSENT **));
58 
59 PATH_T to = { to.p_path, "" };
60 
61 uid_t myuid;
62 int exit_val, myumask;
63 int iflag, orflag, pflag, rflag;
64 char *progname;
65 
66 static enum { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE } type;
67 
68 int
69 main(argc, argv)
70 	int argc;
71 	char *argv[];
72 {
73 	struct stat to_stat, tmp_stat;
74 	FTS *ftsp;
75 	register int c, r;
76 	int fts_options, Hflag, hflag;
77 	char *p, *target;
78 
79 	/*
80 	 * The utility cp(1) is used by mv(1) -- except for usage statements,
81 	 * print the "called as" program name.
82 	 */
83 	progname = (p = rindex(*argv,'/')) ? ++p : *argv;
84 
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
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          *
94          */
95 	Hflag = hflag = 0;
96 	fts_options = FTS_NOCHDIR | FTS_LOGICAL;
97 	while ((c = getopt(argc, argv, "HRfhipr")) != EOF)
98 		switch ((char)c) {
99 		case 'H':
100 			Hflag = 1;
101 			fts_options |= FTS_COMFOLLOW;
102 			break;
103 		case 'f':
104 			iflag = 0;
105 			break;
106 		case 'h':
107 			hflag = 1;
108 			break;
109 		case 'i':
110 			iflag = isatty(fileno(stdin));
111 			break;
112 		case 'p':
113 			pflag = 1;
114 			break;
115 		case 'R':
116 			fts_options &= ~FTS_LOGICAL;
117 			fts_options |= FTS_PHYSICAL;
118 			rflag = 1;
119 			break;
120 		case 'r':
121 			orflag = 1;
122 			fts_options &= ~FTS_PHYSICAL;
123 			fts_options |= FTS_LOGICAL;
124 			break;
125 		case '?':
126 		default:
127 			usage();
128 			break;
129 		}
130 	argc -= optind;
131 	argv += optind;
132 
133 	if (argc < 2)
134 		usage();
135 
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 
149 	if (hflag) {
150 		fts_options &= ~FTS_PHYSICAL;
151 		fts_options |= FTS_LOGICAL;
152 	}
153 
154 	myuid = getuid();
155 
156 	/* Copy the umask for explicit mode setting. */
157 	myumask = umask(0);
158 	(void)umask(myumask);
159 
160 	/* Save the target base in "to". */
161 	target = argv[--argc];
162 	if (strlen(target) > MAXPATHLEN) {
163 		err("%s: name too long", target);
164 		exit(1);
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;
174 
175 	/* Set end of argument list for fts(3). */
176 	argv[argc] = NULL;
177 
178 	/*
179 	 * Cp has two distinct cases:
180 	 *
181 	 * cp [-R] source target
182 	 * cp [-R] source1 ... sourceN directory
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".
191 	 */
192 	r = stat(to.p_path, &to_stat);
193 	if (r == -1 && errno != ENOENT) {
194 		err("%s: %s", to.p_path, strerror(errno));
195 		exit(1);
196 	}
197 	if (r == -1 || !S_ISDIR(to_stat.st_mode)) {
198 		/*
199 		 * Case (1).  Target is not a directory.
200 		 */
201 		if (argc > 1) {
202 			usage();
203 			exit(1);
204 		}
205 		/*
206 		 * Need to detect the case:
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().
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;
224 	} else
225 		/*
226 		 * Case (2).  Target is a directory.
227 		 */
228 		type = FILE_TO_DIR;
229 
230 	if ((ftsp = fts_open(argv, fts_options, mastercmp)) == NULL) {
231 		err("%s", strerror(errno));
232 	        exit(1);
233 	}
234 	copy(ftsp);
235 	fts_close(ftsp);
236 
237 	exit(exit_val);
238 }
239 
240 static void
241 copy(ftsp)
242 	FTS *ftsp;
243 {
244 	register FTSENT *curr;
245 	register int base, nlen;
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));
256 			exit_val = 1;
257 			continue;
258 		case FTS_DC:
259 			err("%s: directory causes a cycle", curr->fts_path);
260 			exit_val = 1;
261 			continue;
262 		case FTS_DP:
263 			continue;
264 		}
265 
266 		/*
267 		 * If we are in case (2) or (3) above, we need to append the
268                  * source name to the target name.
269                  */
270 		if (type != FILE_TO_FILE) {
271 			if ((curr->fts_namelen +
272 			    to.target_end - to.p_path + 1) > MAXPATHLEN) {
273 				err("%s/%s: name too long (not copied)",
274 				    to.p_path, curr->fts_name);
275 				continue;
276 			}
277 
278 			/*
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.
292 			 */
293 			if (curr->fts_level == FTS_ROOTLEVEL)
294 				if (type != DIR_TO_DNE) {
295 					c = rindex(curr->fts_path, '/');
296 					base = (c == NULL) ? 0 :
297 						(int) (c - curr->fts_path + 1);
298 
299 					if (!strcmp(&curr->fts_path[base],
300 					    ".."))
301 						base += 1;
302 				} else
303 					base = curr->fts_pathlen;
304 
305 			if (to.target_end[-1] != '/') {
306 				*to.target_end = '/';
307 				*(to.target_end + 1) = 0;
308 			}
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;
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,
325 			    "%s: %s and %s are identical (not copied).\n",
326 				    progname, to.p_path, curr->fts_path);
327 				exit_val = 1;
328 				if (S_ISDIR(curr->fts_statp->st_mode))
329 					(void)fts_set(ftsp, curr, FTS_SKIP);
330 				continue;
331 			}
332 			dne = 0;
333 		}
334 
335 		switch (curr->fts_statp->st_mode & S_IFMT) {
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);
344 				(void)fts_set(ftsp, curr, FTS_SKIP);
345 				exit_val = 1;
346 				break;
347 			}
348 			if (dne) {
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 			 */
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 		                }
363 			} else if (!S_ISDIR(to_stat.st_mode)) {
364 				(void)fprintf(stderr,
365 				    "%s: %s: not a directory.\n", progname,
366 				    to.p_path);
367 				return;
368 			}
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:
383 			if (rflag)
384 				copy_special(curr->fts_statp, !dne);
385 			else
386 				copy_file(curr, dne);
387 			break;
388 		case S_IFIFO:
389 			if (rflag)
390 				copy_fifo(curr->fts_statp, !dne);
391 			else
392 				copy_file(curr, dne);
393 			break;
394 		default:
395 			copy_file(curr, dne);
396 			break;
397 		}
398 	}
399 }
400 
401 /*
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.
408  */
409 static int
410 mastercmp(a, b)
411 	const FTSENT **a, **b;
412 {
413 	register int a_info, b_info;
414 
415 	a_info = (*a)->fts_info;
416 	if (a_info == FTS_ERR || a_info == FTS_NS || a_info == FTS_DNR)
417 		return (0);
418 	b_info = (*b)->fts_info;
419 	if (b_info == FTS_ERR || b_info == FTS_NS || b_info == FTS_DNR)
420 		return (0);
421 	if (a_info == FTS_D)
422 		return (-1);
423 	if (b_info == FTS_D)
424 		return (1);
425 	return (0);
426 }
427