utils.c revision 99363
1/*-
2 * Copyright (c) 1991, 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35#if 0
36static char sccsid[] = "@(#)utils.c	8.3 (Berkeley) 4/1/94";
37#endif
38#endif /* not lint */
39#include <sys/cdefs.h>
40__FBSDID("$FreeBSD: head/bin/cp/utils.c 99363 2002-07-03 16:35:20Z markm $");
41
42#include <sys/param.h>
43#include <sys/stat.h>
44#ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
45#include <sys/mman.h>
46#endif
47
48#include <err.h>
49#include <errno.h>
50#include <fcntl.h>
51#include <fts.h>
52#include <limits.h>
53#include <stdio.h>
54#include <stdlib.h>
55#include <sysexits.h>
56#include <unistd.h>
57
58#include "extern.h"
59
60int
61copy_file(FTSENT *entp, int dne)
62{
63	static char buf[MAXBSIZE];
64	struct stat *fs;
65	int ch, checkch, from_fd, rcount, rval, to_fd;
66	ssize_t wcount;
67	size_t wresid;
68	char *bufp;
69#ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
70	char *p;
71#endif
72
73	if ((from_fd = open(entp->fts_path, O_RDONLY, 0)) == -1) {
74		warn("%s", entp->fts_path);
75		return (1);
76	}
77
78	fs = entp->fts_statp;
79
80	/*
81	 * If the file exists and we're interactive, verify with the user.
82	 * If the file DNE, set the mode to be the from file, minus setuid
83	 * bits, modified by the umask; arguably wrong, but it makes copying
84	 * executables work right and it's been that way forever.  (The
85	 * other choice is 666 or'ed with the execute bits on the from file
86	 * modified by the umask.)
87	 */
88	if (!dne) {
89#define YESNO "(y/n [n]) "
90		if (iflag) {
91			(void)fprintf(stderr, "overwrite %s? %s",
92					to.p_path, YESNO);
93			checkch = ch = getchar();
94			while (ch != '\n' && ch != EOF)
95				ch = getchar();
96			if (checkch != 'y' && checkch != 'Y') {
97				(void)close(from_fd);
98				(void)fprintf(stderr, "not overwritten\n");
99				return (1);
100			}
101		}
102
103		if (fflag) {
104		    /* remove existing destination file name,
105		     * create a new file  */
106		    (void)unlink(to.p_path);
107		    to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
108				 fs->st_mode & ~(S_ISUID | S_ISGID));
109		} else
110		    /* overwrite existing destination file name */
111		    to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0);
112	} else
113		to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
114		    fs->st_mode & ~(S_ISUID | S_ISGID));
115
116	if (to_fd == -1) {
117		warn("%s", to.p_path);
118		(void)close(from_fd);
119		return (1);
120	}
121
122	rval = 0;
123
124	/*
125	 * Mmap and write if less than 8M (the limit is so we don't totally
126	 * trash memory on big files.  This is really a minor hack, but it
127	 * wins some CPU back.
128	 */
129#ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
130	if (S_ISREG(fs->st_mode) && fs->st_size <= 8 * 1048576) {
131		if ((p = mmap(NULL, (size_t)fs->st_size, PROT_READ,
132		    MAP_SHARED, from_fd, (off_t)0)) == MAP_FAILED) {
133			warn("%s", entp->fts_path);
134			rval = 1;
135		} else {
136			for (bufp = p, wresid = fs->st_size; ;
137			    bufp += wcount, wresid -= (size_t)wcount) {
138				wcount = write(to_fd, bufp, wresid);
139				if (wcount >= wresid || wcount <= 0)
140					break;
141			}
142			if (wcount != wresid) {
143				warn("%s", to.p_path);
144				rval = 1;
145			}
146			/* Some systems don't unmap on close(2). */
147			if (munmap(p, fs->st_size) < 0) {
148				warn("%s", entp->fts_path);
149				rval = 1;
150			}
151		}
152	} else
153#endif
154	{
155		while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
156			for (bufp = buf, wresid = rcount; ;
157			    bufp += wcount, wresid -= wcount) {
158				wcount = write(to_fd, bufp, wresid);
159				if (wcount >= wresid || wcount <= 0)
160					break;
161			}
162			if (wcount != wresid) {
163				warn("%s", to.p_path);
164				rval = 1;
165				break;
166			}
167		}
168		if (rcount < 0) {
169			warn("%s", entp->fts_path);
170			rval = 1;
171		}
172	}
173
174	/*
175	 * Don't remove the target even after an error.  The target might
176	 * not be a regular file, or its attributes might be important,
177	 * or its contents might be irreplaceable.  It would only be safe
178	 * to remove it if we created it and its length is 0.
179	 */
180
181	if (pflag && setfile(fs, to_fd))
182		rval = 1;
183	(void)close(from_fd);
184	if (close(to_fd)) {
185		warn("%s", to.p_path);
186		rval = 1;
187	}
188	return (rval);
189}
190
191int
192copy_link(FTSENT *p, int exists)
193{
194	int len;
195	char llink[PATH_MAX];
196
197	if ((len = readlink(p->fts_path, llink, sizeof(llink) - 1)) == -1) {
198		warn("readlink: %s", p->fts_path);
199		return (1);
200	}
201	llink[len] = '\0';
202	if (exists && unlink(to.p_path)) {
203		warn("unlink: %s", to.p_path);
204		return (1);
205	}
206	if (symlink(llink, to.p_path)) {
207		warn("symlink: %s", llink);
208		return (1);
209	}
210	return (0);
211}
212
213int
214copy_fifo(struct stat *from_stat, int exists)
215{
216	if (exists && unlink(to.p_path)) {
217		warn("unlink: %s", to.p_path);
218		return (1);
219	}
220	if (mkfifo(to.p_path, from_stat->st_mode)) {
221		warn("mkfifo: %s", to.p_path);
222		return (1);
223	}
224	return (pflag ? setfile(from_stat, 0) : 0);
225}
226
227int
228copy_special(struct stat *from_stat, int exists)
229{
230	if (exists && unlink(to.p_path)) {
231		warn("unlink: %s", to.p_path);
232		return (1);
233	}
234	if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
235		warn("mknod: %s", to.p_path);
236		return (1);
237	}
238	return (pflag ? setfile(from_stat, 0) : 0);
239}
240
241int
242setfile(struct stat *fs, int fd)
243{
244	static struct timeval tv[2];
245	struct stat ts;
246	int rval;
247	int gotstat;
248
249	rval = 0;
250	fs->st_mode &= S_ISUID | S_ISGID | S_ISVTX |
251		       S_IRWXU | S_IRWXG | S_IRWXO;
252
253	TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
254	TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
255	if (utimes(to.p_path, tv)) {
256		warn("utimes: %s", to.p_path);
257		rval = 1;
258	}
259	if (fd ? fstat(fd, &ts) : stat(to.p_path, &ts))
260		gotstat = 0;
261	else {
262		gotstat = 1;
263		ts.st_mode &= S_ISUID | S_ISGID | S_ISVTX |
264			      S_IRWXU | S_IRWXG | S_IRWXO;
265	}
266	/*
267	 * Changing the ownership probably won't succeed, unless we're root
268	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
269	 * the mode; current BSD behavior is to remove all setuid bits on
270	 * chown.  If chown fails, lose setuid/setgid bits.
271	 */
272	if (!gotstat || fs->st_uid != ts.st_uid || fs->st_gid != ts.st_gid)
273		if (fd ? fchown(fd, fs->st_uid, fs->st_gid) :
274		    chown(to.p_path, fs->st_uid, fs->st_gid)) {
275			if (errno != EPERM) {
276				warn("chown: %s", to.p_path);
277				rval = 1;
278			}
279			fs->st_mode &= ~(S_ISUID | S_ISGID);
280		}
281
282	if (!gotstat || fs->st_mode != ts.st_mode)
283		if (fd ? fchmod(fd, fs->st_mode) : chmod(to.p_path, fs->st_mode)) {
284			warn("chmod: %s", to.p_path);
285			rval = 1;
286		}
287
288	if (!gotstat || fs->st_flags != ts.st_flags)
289		if (fd ?
290		    fchflags(fd, fs->st_flags) : chflags(to.p_path, fs->st_flags)) {
291			warn("chflags: %s", to.p_path);
292			rval = 1;
293		}
294
295	return (rval);
296}
297
298void
299usage(void)
300{
301
302	(void)fprintf(stderr, "%s\n%s\n",
303"usage: cp [-R [-H | -L | -P]] [-f | -i] [-pv] src target",
304"       cp [-R [-H | -L | -P]] [-f | -i] [-pv] src1 ... srcN directory");
305	exit(EX_USAGE);
306}
307