cp.c revision 5879
1199482Srdivacky/*
2199482Srdivacky * Copyright (c) 1988, 1993, 1994
3199482Srdivacky *	The Regents of the University of California.  All rights reserved.
4199482Srdivacky *
5199482Srdivacky * This code is derived from software contributed to Berkeley by
6199482Srdivacky * David Hitz of Auspex Systems Inc.
7199482Srdivacky *
8199482Srdivacky * Redistribution and use in source and binary forms, with or without
9199482Srdivacky * modification, are permitted provided that the following conditions
10199482Srdivacky * are met:
11199482Srdivacky * 1. Redistributions of source code must retain the above copyright
12234353Sdim *    notice, this list of conditions and the following disclaimer.
13210299Sed * 2. Redistributions in binary form must reproduce the above copyright
14199482Srdivacky *    notice, this list of conditions and the following disclaimer in the
15199482Srdivacky *    documentation and/or other materials provided with the distribution.
16199482Srdivacky * 3. All advertising materials mentioning features or use of this software
17199482Srdivacky *    must display the following acknowledgement:
18199482Srdivacky *	This product includes software developed by the University of
19199482Srdivacky *	California, Berkeley and its contributors.
20199482Srdivacky * 4. Neither the name of the University nor the names of its contributors
21199482Srdivacky *    may be used to endorse or promote products derived from this software
22212904Sdim *    without specific prior written permission.
23207619Srdivacky *
24234353Sdim * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25212904Sdim * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26199482Srdivacky * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27226633Sdim * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28226633Sdim * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29199482Srdivacky * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30199482Srdivacky * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31199482Srdivacky * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32205408Srdivacky * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33205408Srdivacky * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34205408Srdivacky * SUCH DAMAGE.
35205408Srdivacky *
36226633Sdim *	$Id: cp.c,v 1.3 1994/12/30 13:12:12 bde Exp $
37205408Srdivacky */
38205408Srdivacky
39205408Srdivacky#ifndef lint
40205408Srdivackystatic char copyright[] =
41205408Srdivacky"@(#) Copyright (c) 1988, 1993, 1994\n\
42205408Srdivacky	The Regents of the University of California.  All rights reserved.\n";
43205408Srdivacky#endif /* not lint */
44199482Srdivacky
45199482Srdivacky#ifndef lint
46199482Srdivackystatic char sccsid[] = "@(#)cp.c	8.2 (Berkeley) 4/1/94";
47199482Srdivacky#endif /* not lint */
48226633Sdim
49226633Sdim/*
50239462Sdim * Cp copies source files to target files.
51200583Srdivacky *
52199482Srdivacky * The global PATH_T structure "to" always contains the path to the
53199482Srdivacky * current target file.  Since fts(3) does not change directories,
54199482Srdivacky * this path can be either absolute or dot-realative.
55226633Sdim *
56239462Sdim * The basic algorithm is to initialize "to" and use fts(3) to traverse
57199482Srdivacky * the file hierarchy rooted in the argument list.  A trivial case is the
58199482Srdivacky * case of 'cp file1 file2'.  The more interesting case is the case of
59239462Sdim * 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the
60239462Sdim * path (relative to the root of the traversal) is appended to dir (stored
61239462Sdim * in "to") to form the final target path.
62239462Sdim */
63239462Sdim
64218893Sdim#include <sys/param.h>
65226633Sdim#include <sys/stat.h>
66226633Sdim#include <sys/mman.h>
67218893Sdim#include <sys/time.h>
68218893Sdim
69218893Sdim#include <dirent.h>
70218893Sdim#include <err.h>
71218893Sdim#include <errno.h>
72218893Sdim#include <fcntl.h>
73218893Sdim#include <fts.h>
74218893Sdim#include <stdio.h>
75199482Srdivacky#include <stdlib.h>
76226633Sdim#include <string.h>
77199482Srdivacky#include <unistd.h>
78199482Srdivacky
79199482Srdivacky#include "extern.h"
80199482Srdivacky
81226633Sdim#define	STRIP_TRAILING_SLASH(p) {					\
82199482Srdivacky        while ((p).p_end > (p).p_path + 1 && (p).p_end[-1] == '/')	\
83199482Srdivacky                *--(p).p_end = 0;					\
84199482Srdivacky}
85199482Srdivacky
86226633SdimPATH_T to = { to.p_path, "" };
87212904Sdim
88218893Sdimuid_t myuid;
89226633Sdimint Rflag, iflag, pflag, rflag;
90226633Sdimint myumask;
91199482Srdivacky
92212904Sdimenum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE };
93226633Sdim
94226633Sdimint copy __P((char *[], enum op, int));
95234353Sdimint mastercmp __P((const FTSENT **, const FTSENT **));
96212904Sdim
97212904Sdimint
98212904Sdimmain(argc, argv)
99226633Sdim	int argc;
100212904Sdim	char *argv[];
101218893Sdim{
102226633Sdim	struct stat to_stat, tmp_stat;
103212904Sdim	enum op type;
104212904Sdim	int Hflag, Lflag, Pflag, ch, fts_options, r;
105212904Sdim	char *target;
106212904Sdim
107199482Srdivacky	Hflag = Lflag = Pflag = Rflag = 0;
108199482Srdivacky	while ((ch = getopt(argc, argv, "HLPRfipr")) != EOF)
109218893Sdim		switch (ch) {
110218893Sdim		case 'H':
111226633Sdim			Hflag = 1;
112218893Sdim			Lflag = Pflag = 0;
113226633Sdim			break;
114226633Sdim		case 'L':
115200583Srdivacky			Lflag = 1;
116212904Sdim			Hflag = Pflag = 0;
117200583Srdivacky			break;
118218893Sdim		case 'P':
119212904Sdim			Pflag = 1;
120199482Srdivacky			Hflag = Lflag = 0;
121199482Srdivacky			break;
122234353Sdim		case 'R':
123234353Sdim			Rflag = 1;
124234353Sdim			break;
125234353Sdim		case 'f':
126234353Sdim			iflag = 0;
127234353Sdim			break;
128234353Sdim		case 'i':
129234353Sdim			iflag = isatty(fileno(stdin));
130234353Sdim			break;
131234353Sdim		case 'p':
132234353Sdim			pflag = 1;
133234353Sdim			break;
134234353Sdim		case 'r':
135234353Sdim			rflag = 1;
136234353Sdim			break;
137234353Sdim		case '?':
138234353Sdim		default:
139239462Sdim			usage();
140234353Sdim			break;
141234353Sdim		}
142234353Sdim	argc -= optind;
143234353Sdim	argv += optind;
144234353Sdim
145234353Sdim	if (argc < 2)
146234353Sdim		usage();
147234353Sdim
148234353Sdim	fts_options = FTS_NOCHDIR | FTS_PHYSICAL;
149234353Sdim	if (rflag) {
150234353Sdim		if (Rflag)
151234353Sdim			errx(1,
152234353Sdim		    "the -R and -r options may not be specified together.");
153234353Sdim		if (Hflag || Lflag || Pflag)
154234353Sdim			errx(1,
155234353Sdim	"the -H, -L, and -P options may not be specified with the -r option.");
156234353Sdim		fts_options &= ~FTS_PHYSICAL;
157234353Sdim		fts_options |= FTS_LOGICAL;
158234353Sdim	}
159234353Sdim	if (Rflag) {
160234353Sdim		if (Hflag)
161234353Sdim			fts_options |= FTS_COMFOLLOW;
162234353Sdim		if (Lflag) {
163234353Sdim			fts_options &= ~FTS_PHYSICAL;
164234353Sdim			fts_options |= FTS_LOGICAL;
165234353Sdim		}
166234353Sdim	} else {
167234353Sdim		fts_options &= ~FTS_PHYSICAL;
168234353Sdim		fts_options |= FTS_LOGICAL;
169234353Sdim	}
170234353Sdim
171234353Sdim	myuid = getuid();
172234353Sdim
173234353Sdim	/* Copy the umask for explicit mode setting. */
174234353Sdim	myumask = umask(0);
175234353Sdim	(void)umask(myumask);
176234353Sdim
177234353Sdim	/* Save the target base in "to". */
178234353Sdim	target = argv[--argc];
179234353Sdim	if (strlen(target) > MAXPATHLEN)
180234353Sdim		errx(1, "%s: name too long", target);
181234353Sdim	(void)strcpy(to.p_path, target);
182234353Sdim	to.p_end = to.p_path + strlen(to.p_path);
183234353Sdim        if (to.p_path == to.p_end) {
184234353Sdim		*to.p_end++ = '.';
185234353Sdim		*to.p_end = 0;
186234353Sdim	}
187234353Sdim        STRIP_TRAILING_SLASH(to);
188234353Sdim	to.target_end = to.p_end;
189234353Sdim
190234353Sdim	/* Set end of argument list for fts(3). */
191234353Sdim	argv[argc] = NULL;
192234353Sdim
193234353Sdim	/*
194234353Sdim	 * Cp has two distinct cases:
195234353Sdim	 *
196234353Sdim	 * cp [-R] source target
197234353Sdim	 * cp [-R] source1 ... sourceN directory
198234353Sdim	 *
199234353Sdim	 * In both cases, source can be either a file or a directory.
200234353Sdim	 *
201234353Sdim	 * In (1), the target becomes a copy of the source. That is, if the
202234353Sdim	 * source is a file, the target will be a file, and likewise for
203234353Sdim	 * directories.
204234353Sdim	 *
205234353Sdim	 * In (2), the real target is not directory, but "directory/source".
206234353Sdim	 */
207234353Sdim	r = stat(to.p_path, &to_stat);
208234353Sdim	if (r == -1 && errno != ENOENT)
209234353Sdim		err(1, "%s", to.p_path);
210234353Sdim	if (r == -1 || !S_ISDIR(to_stat.st_mode)) {
211234353Sdim		/*
212234353Sdim		 * Case (1).  Target is not a directory.
213234353Sdim		 */
214234353Sdim		if (argc > 1) {
215234353Sdim			usage();
216234353Sdim			exit(1);
217234353Sdim		}
218234353Sdim		/*
219234353Sdim		 * Need to detect the case:
220234353Sdim		 *	cp -R dir foo
221234353Sdim		 * Where dir is a directory and foo does not exist, where
222234353Sdim		 * we want pathname concatenations turned on but not for
223234353Sdim		 * the initial mkdir().
224234353Sdim		 */
225234353Sdim		if (r == -1) {
226234353Sdim			if (rflag || (Rflag && (Lflag || Hflag)))
227234353Sdim				stat(*argv, &tmp_stat);
228234353Sdim			else
229234353Sdim				lstat(*argv, &tmp_stat);
230234353Sdim
231234353Sdim			if (S_ISDIR(tmp_stat.st_mode) && (Rflag || rflag))
232234353Sdim				type = DIR_TO_DNE;
233234353Sdim			else
234234353Sdim				type = FILE_TO_FILE;
235234353Sdim		} else
236234353Sdim			type = FILE_TO_FILE;
237234353Sdim	} else
238234353Sdim		/*
239234353Sdim		 * Case (2).  Target is a directory.
240234353Sdim		 */
241234353Sdim		type = FILE_TO_DIR;
242234353Sdim
243234353Sdim	exit (copy(argv, type, fts_options));
244234353Sdim}
245234353Sdim
246234353Sdimint
247234353Sdimcopy(argv, type, fts_options)
248234353Sdim	char *argv[];
249234353Sdim	enum op type;
250234353Sdim	int fts_options;
251234353Sdim{
252234353Sdim	struct stat to_stat;
253234353Sdim	FTS *ftsp;
254234353Sdim	FTSENT *curr;
255234353Sdim	int base, dne, nlen, rval;
256234353Sdim	char *p, *target_mid;
257234353Sdim
258234353Sdim	if ((ftsp = fts_open(argv, fts_options, mastercmp)) == NULL)
259234353Sdim		err(1, NULL);
260234353Sdim	for (rval = 0; (curr = fts_read(ftsp)) != NULL;) {
261234353Sdim		switch (curr->fts_info) {
262234353Sdim		case FTS_NS:
263234353Sdim		case FTS_ERR:
264234353Sdim			warnx("%s: %s",
265234353Sdim			    curr->fts_path, strerror(curr->fts_errno));
266234353Sdim			rval = 1;
267234353Sdim			continue;
268234353Sdim		case FTS_DC:			/* Warn, continue. */
269234353Sdim			warnx("%s: directory causes a cycle", curr->fts_path);
270234353Sdim			rval = 1;
271234353Sdim			continue;
272234353Sdim		case FTS_DP:			/* Ignore, continue. */
273234353Sdim			continue;
274234353Sdim		}
275234353Sdim
276234353Sdim		/*
277234353Sdim		 * If we are in case (2) or (3) above, we need to append the
278234353Sdim                 * source name to the target name.
279234353Sdim                 */
280234353Sdim		if (type != FILE_TO_FILE) {
281234353Sdim			/*
282234353Sdim			 * Need to remember the roots of traversals to create
283234353Sdim			 * correct pathnames.  If there's a directory being
284234353Sdim			 * copied to a non-existent directory, e.g.
285234353Sdim			 *	cp -R a/dir noexist
286234353Sdim			 * the resulting path name should be noexist/foo, not
287234353Sdim			 * noexist/dir/foo (where foo is a file in dir), which
288234353Sdim			 * is the case where the target exists.
289234353Sdim			 *
290234353Sdim			 * Also, check for "..".  This is for correct path
291234353Sdim			 * concatentation for paths ending in "..", e.g.
292234353Sdim			 *	cp -R .. /tmp
293234353Sdim			 * Paths ending in ".." are changed to ".".  This is
294234353Sdim			 * tricky, but seems the easiest way to fix the problem.
295234353Sdim			 *
296234353Sdim			 * XXX
297234353Sdim			 * Since the first level MUST be FTS_ROOTLEVEL, base
298234353Sdim			 * is always initialized.
299234353Sdim			 */
300234353Sdim			if (curr->fts_level == FTS_ROOTLEVEL)
301234353Sdim				if (type != DIR_TO_DNE) {
302234353Sdim					p = strrchr(curr->fts_path, '/');
303234353Sdim					base = (p == NULL) ? 0 :
304234353Sdim					    (int)(p - curr->fts_path + 1);
305234353Sdim
306234353Sdim					if (!strcmp(&curr->fts_path[base],
307234353Sdim					    ".."))
308234353Sdim						base += 1;
309234353Sdim				} else
310234353Sdim					base = curr->fts_pathlen;
311234353Sdim
312234353Sdim			p = &curr->fts_path[base];
313234353Sdim			nlen = curr->fts_pathlen - base;
314234353Sdim			target_mid = to.target_end;
315234353Sdim			if (*p != '/' && target_mid[-1] != '/')
316234353Sdim				*target_mid++ = '/';
317234353Sdim			*target_mid = 0;
318234353Sdim			if (target_mid - to.p_path + nlen > MAXPATHLEN) {
319234353Sdim				warnx("%s%s: name too long (not copied)",
320234353Sdim				    to.p_path, p);
321234353Sdim				rval = 1;
322234353Sdim				continue;
323234353Sdim			}
324234353Sdim			(void)strncat(target_mid, p, nlen);
325234353Sdim			to.p_end = target_mid + nlen;
326234353Sdim			*to.p_end = 0;
327234353Sdim			STRIP_TRAILING_SLASH(to);
328234353Sdim		}
329234353Sdim
330234353Sdim		/* Not an error but need to remember it happened */
331234353Sdim		if (stat(to.p_path, &to_stat) == -1)
332234353Sdim			dne = 1;
333234353Sdim		else {
334234353Sdim			if (to_stat.st_dev == curr->fts_statp->st_dev &&
335234353Sdim			    to_stat.st_ino == curr->fts_statp->st_ino) {
336234353Sdim				warnx("%s and %s are identical (not copied).",
337234353Sdim				    to.p_path, curr->fts_path);
338234353Sdim				rval = 1;
339234353Sdim				if (S_ISDIR(curr->fts_statp->st_mode))
340234353Sdim					(void)fts_set(ftsp, curr, FTS_SKIP);
341234353Sdim				continue;
342234353Sdim			}
343234353Sdim			dne = 0;
344234353Sdim		}
345234353Sdim
346234353Sdim		switch (curr->fts_statp->st_mode & S_IFMT) {
347234353Sdim		case S_IFLNK:
348234353Sdim			if (copy_link(curr, !dne))
349234353Sdim				rval = 1;
350234353Sdim			break;
351234353Sdim		case S_IFDIR:
352234353Sdim			if (!Rflag && !rflag) {
353234353Sdim				warnx("%s is a directory (not copied).",
354234353Sdim				    curr->fts_path);
355234353Sdim				(void)fts_set(ftsp, curr, FTS_SKIP);
356234353Sdim				rval = 1;
357199482Srdivacky				break;
358226633Sdim			}
359199482Srdivacky			/*
360199482Srdivacky			 * If the directory doesn't exist, create the new
361199482Srdivacky			 * one with the from file mode plus owner RWX bits,
362199482Srdivacky			 * modified by the umask.  Trade-off between being
363199482Srdivacky			 * able to write the directory (if from directory is
364199482Srdivacky			 * 555) and not causing a permissions race.  If the
365199482Srdivacky			 * umask blocks owner writes, we fail..
366199482Srdivacky			 */
367199482Srdivacky			if (dne) {
368199482Srdivacky				if (mkdir(to.p_path,
369199482Srdivacky				    curr->fts_statp->st_mode | S_IRWXU) < 0)
370199482Srdivacky					err(1, "%s", to.p_path);
371199990Srdivacky			} else if (!S_ISDIR(to_stat.st_mode)) {
372234353Sdim				errno = ENOTDIR;
373199482Srdivacky				err(1, "%s", to.p_path);
374199482Srdivacky			}
375199482Srdivacky			/*
376199482Srdivacky			 * If not -p and directory didn't exist, set it to be
377199482Srdivacky			 * the same as the from directory, umodified by the
378199482Srdivacky                         * umask; arguably wrong, but it's been that way
379199990Srdivacky                         * forever.
380199482Srdivacky			 */
381199482Srdivacky			if (pflag && setfile(curr->fts_statp, 0))
382199482Srdivacky				rval = 1;
383199482Srdivacky			else if (dne)
384199482Srdivacky				(void)chmod(to.p_path,
385199482Srdivacky				    curr->fts_statp->st_mode);
386199482Srdivacky			break;
387199482Srdivacky		case S_IFBLK:
388207619Srdivacky		case S_IFCHR:
389199482Srdivacky			if (Rflag) {
390199482Srdivacky				if (copy_special(curr->fts_statp, !dne))
391199482Srdivacky					rval = 1;
392199990Srdivacky			} else
393199482Srdivacky				if (copy_file(curr, dne))
394199482Srdivacky					rval = 1;
395199482Srdivacky			break;
396199482Srdivacky		case S_IFIFO:
397199482Srdivacky			if (Rflag)
398199482Srdivacky				if (copy_fifo(curr->fts_statp, !dne))
399199482Srdivacky					rval = 1;
400199482Srdivacky			else
401199482Srdivacky				if (copy_file(curr, dne))
402207619Srdivacky					rval = 1;
403199482Srdivacky			break;
404199482Srdivacky		default:
405199482Srdivacky			if (copy_file(curr, dne))
406200583Srdivacky				rval = 1;
407200583Srdivacky			break;
408199482Srdivacky		}
409199482Srdivacky	}
410199482Srdivacky	if (errno)
411199482Srdivacky		err(1, "fts_read");
412199482Srdivacky	return (rval);
413199482Srdivacky}
414210299Sed
415210299Sed/*
416210299Sed * mastercmp --
417199482Srdivacky *	The comparison function for the copy order.  The order is to copy
418199482Srdivacky *	non-directory files before directory files.  The reason for this
419207619Srdivacky *	is because files tend to be in the same cylinder group as their
420199482Srdivacky *	parent directory, whereas directories tend not to be.  Copying the
421199482Srdivacky *	files first reduces seeking.
422199482Srdivacky */
423199482Srdivackyint
424199482Srdivackymastercmp(a, b)
425199482Srdivacky	const FTSENT **a, **b;
426199482Srdivacky{
427226633Sdim	int a_info, b_info;
428202379Srdivacky
429226633Sdim	a_info = (*a)->fts_info;
430226633Sdim	if (a_info == FTS_ERR || a_info == FTS_NS || a_info == FTS_DNR)
431226633Sdim		return (0);
432226633Sdim	b_info = (*b)->fts_info;
433226633Sdim	if (b_info == FTS_ERR || b_info == FTS_NS || b_info == FTS_DNR)
434226633Sdim		return (0);
435226633Sdim	if (a_info == FTS_D)
436226633Sdim		return (-1);
437226633Sdim	if (b_info == FTS_D)
438226633Sdim		return (1);
439226633Sdim	return (0);
440226633Sdim}
441226633Sdim