1/* $NetBSD: utils.c,v 1.40 2011/08/03 04:11:15 manu Exp $ */
2
3/*-
4 * Copyright (c) 1991, 1993, 1994
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33#ifndef lint
34#if 0
35static char sccsid[] = "@(#)utils.c	8.3 (Berkeley) 4/1/94";
36#else
37__RCSID("$NetBSD: utils.c,v 1.40 2011/08/03 04:11:15 manu Exp $");
38#endif
39#endif /* not lint */
40
41#include <sys/mman.h>
42#include <sys/param.h>
43#include <sys/stat.h>
44#include <sys/time.h>
45#include <sys/extattr.h>
46
47#include <err.h>
48#include <errno.h>
49#include <fcntl.h>
50#include <fts.h>
51#include <stdbool.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <unistd.h>
56
57#include "extern.h"
58
59#define	MMAP_MAX_SIZE	(8 * 1048576)
60#define	MMAP_MAX_WRITE	(64 * 1024)
61
62int
63set_utimes(const char *file, struct stat *fs)
64{
65    static struct timeval tv[2];
66
67    TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
68    TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
69
70    if (lutimes(file, tv)) {
71	warn("lutimes: %s", file);
72	return (1);
73    }
74    return (0);
75}
76
77struct finfo {
78	const char *from;
79	const char *to;
80	size_t size;
81};
82
83static void
84progress(const struct finfo *fi, size_t written)
85{
86	int pcent = (int)((100.0 * written) / fi->size);
87
88	pinfo = 0;
89	(void)fprintf(stderr, "%s => %s %zu/%zu bytes %d%% written\n",
90	    fi->from, fi->to, written, fi->size, pcent);
91}
92
93int
94copy_file(FTSENT *entp, int dne)
95{
96	static char buf[MAXBSIZE];
97	struct stat to_stat, *fs;
98	int ch, checkch, from_fd, rcount, rval, to_fd, tolnk, wcount;
99	char *p;
100	size_t ptotal = 0;
101
102	if ((from_fd = open(entp->fts_path, O_RDONLY, 0)) == -1) {
103		warn("%s", entp->fts_path);
104		return (1);
105	}
106
107	to_fd = -1;
108	fs = entp->fts_statp;
109	tolnk = ((Rflag && !(Lflag || Hflag)) || Pflag);
110
111	/*
112	 * If the file exists and we're interactive, verify with the user.
113	 * If the file DNE, set the mode to be the from file, minus setuid
114	 * bits, modified by the umask; arguably wrong, but it makes copying
115	 * executables work right and it's been that way forever.  (The
116	 * other choice is 666 or'ed with the execute bits on the from file
117	 * modified by the umask.)
118	 */
119	if (!dne) {
120		struct stat sb;
121		int sval;
122
123		if (iflag) {
124			(void)fprintf(stderr, "overwrite %s? ", to.p_path);
125			checkch = ch = getchar();
126			while (ch != '\n' && ch != EOF)
127				ch = getchar();
128			if (checkch != 'y' && checkch != 'Y') {
129				(void)close(from_fd);
130				return (0);
131			}
132		}
133
134		sval = tolnk ?
135			lstat(to.p_path, &sb) : stat(to.p_path, &sb);
136		if (sval == -1) {
137			warn("stat: %s", to.p_path);
138			(void)close(from_fd);
139			return (1);
140		}
141
142		if (!(tolnk && S_ISLNK(sb.st_mode)))
143			to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0);
144	} else
145		to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
146		    fs->st_mode & ~(S_ISUID | S_ISGID));
147
148	if (to_fd == -1 && (fflag || tolnk)) {
149		/*
150		 * attempt to remove existing destination file name and
151		 * create a new file
152		 */
153		(void)unlink(to.p_path);
154		to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
155			     fs->st_mode & ~(S_ISUID | S_ISGID));
156	}
157
158	if (to_fd == -1) {
159		warn("%s", to.p_path);
160		(void)close(from_fd);
161		return (1);
162	}
163
164	rval = 0;
165
166	/* if hard linking then simply close the open fds, link and return */
167	if (lflag) {
168		(void)close(from_fd);
169		(void)close(to_fd);
170		(void)unlink(to.p_path);
171		if (link(entp->fts_path, to.p_path)) {
172			warn("%s", to.p_path);
173			return (1);
174		}
175		return (0);
176	}
177	/* NOTREACHED */
178
179	/*
180	 * There's no reason to do anything other than close the file
181	 * now if it's empty, so let's not bother.
182	 */
183	if (fs->st_size > 0) {
184		struct finfo fi;
185
186		fi.from = entp->fts_path;
187		fi.to = to.p_path;
188		fi.size = (size_t)fs->st_size;
189
190		/*
191		 * Mmap and write if less than 8M (the limit is so
192		 * we don't totally trash memory on big files).
193		 * This is really a minor hack, but it wins some CPU back.
194		 */
195		bool use_read;
196
197		use_read = true;
198		if (fs->st_size <= MMAP_MAX_SIZE) {
199			size_t fsize = (size_t)fs->st_size;
200			p = mmap(NULL, fsize, PROT_READ, MAP_FILE|MAP_SHARED,
201			    from_fd, (off_t)0);
202			if (p != MAP_FAILED) {
203				size_t remainder;
204
205				use_read = false;
206
207				(void) madvise(p, (size_t)fs->st_size,
208				     MADV_SEQUENTIAL);
209
210				/*
211				 * Write out the data in small chunks to
212				 * avoid locking the output file for a
213				 * long time if the reading the data from
214				 * the source is slow.
215				 */
216				remainder = fsize;
217				do {
218					ssize_t chunk;
219
220					chunk = (remainder > MMAP_MAX_WRITE) ?
221					    MMAP_MAX_WRITE : remainder;
222					if (write(to_fd, &p[fsize - remainder],
223					    chunk) != chunk) {
224						warn("%s", to.p_path);
225						rval = 1;
226						break;
227					}
228					remainder -= chunk;
229					ptotal += chunk;
230					if (pinfo)
231						progress(&fi, ptotal);
232				} while (remainder > 0);
233
234				if (munmap(p, fsize) < 0) {
235					warn("%s", entp->fts_path);
236					rval = 1;
237				}
238			}
239		}
240
241		if (use_read) {
242			while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
243				wcount = write(to_fd, buf, (size_t)rcount);
244				if (rcount != wcount || wcount == -1) {
245					warn("%s", to.p_path);
246					rval = 1;
247					break;
248				}
249				ptotal += wcount;
250				if (pinfo)
251					progress(&fi, ptotal);
252			}
253			if (rcount < 0) {
254				warn("%s", entp->fts_path);
255				rval = 1;
256			}
257		}
258	}
259
260	if (pflag && (fcpxattr(from_fd, to_fd) != 0))
261		warn("%s: error copying extended attributes", to.p_path);
262
263	(void)close(from_fd);
264
265	if (rval == 1) {
266		(void)close(to_fd);
267		return (1);
268	}
269
270	if (pflag && setfile(fs, to_fd))
271		rval = 1;
272	/*
273	 * If the source was setuid or setgid, lose the bits unless the
274	 * copy is owned by the same user and group.
275	 */
276#define	RETAINBITS \
277	(S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
278	if (!pflag && dne
279	    && fs->st_mode & (S_ISUID | S_ISGID) && fs->st_uid == myuid) {
280		if (fstat(to_fd, &to_stat)) {
281			warn("%s", to.p_path);
282			rval = 1;
283		} else if (fs->st_gid == to_stat.st_gid &&
284		    fchmod(to_fd, fs->st_mode & RETAINBITS & ~myumask)) {
285			warn("%s", to.p_path);
286			rval = 1;
287		}
288	}
289	if (close(to_fd)) {
290		warn("%s", to.p_path);
291		rval = 1;
292	}
293	/* set the mod/access times now after close of the fd */
294	if (pflag && set_utimes(to.p_path, fs)) {
295	    rval = 1;
296	}
297	return (rval);
298}
299
300int
301copy_link(FTSENT *p, int exists)
302{
303	int len;
304	char target[MAXPATHLEN];
305
306	if ((len = readlink(p->fts_path, target, sizeof(target)-1)) == -1) {
307		warn("readlink: %s", p->fts_path);
308		return (1);
309	}
310	target[len] = '\0';
311	if (exists && unlink(to.p_path)) {
312		warn("unlink: %s", to.p_path);
313		return (1);
314	}
315	if (symlink(target, to.p_path)) {
316		warn("symlink: %s", target);
317		return (1);
318	}
319	return (pflag ? setfile(p->fts_statp, 0) : 0);
320}
321
322int
323copy_fifo(struct stat *from_stat, int exists)
324{
325	if (exists && unlink(to.p_path)) {
326		warn("unlink: %s", to.p_path);
327		return (1);
328	}
329	if (mkfifo(to.p_path, from_stat->st_mode)) {
330		warn("mkfifo: %s", to.p_path);
331		return (1);
332	}
333	return (pflag ? setfile(from_stat, 0) : 0);
334}
335
336int
337copy_special(struct stat *from_stat, int exists)
338{
339	if (exists && unlink(to.p_path)) {
340		warn("unlink: %s", to.p_path);
341		return (1);
342	}
343	if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
344		warn("mknod: %s", to.p_path);
345		return (1);
346	}
347	return (pflag ? setfile(from_stat, 0) : 0);
348}
349
350
351/*
352 * Function: setfile
353 *
354 * Purpose:
355 *   Set the owner/group/permissions for the "to" file to the information
356 *   in the stat structure.  If fd is zero, also call set_utimes() to set
357 *   the mod/access times.  If fd is non-zero, the caller must do a utimes
358 *   itself after close(fd).
359 */
360int
361setfile(struct stat *fs, int fd)
362{
363	int rval, islink;
364
365	rval = 0;
366	islink = S_ISLNK(fs->st_mode);
367	fs->st_mode &= S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO;
368
369	/*
370	 * Changing the ownership probably won't succeed, unless we're root
371	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
372	 * the mode; current BSD behavior is to remove all setuid bits on
373	 * chown.  If chown fails, lose setuid/setgid bits.
374	 */
375	if (fd ? fchown(fd, fs->st_uid, fs->st_gid) :
376	    lchown(to.p_path, fs->st_uid, fs->st_gid)) {
377		if (errno != EPERM) {
378			warn("chown: %s", to.p_path);
379			rval = 1;
380		}
381		fs->st_mode &= ~(S_ISUID | S_ISGID);
382	}
383	if (fd ? fchmod(fd, fs->st_mode) : lchmod(to.p_path, fs->st_mode)) {
384		warn("chmod: %s", to.p_path);
385		rval = 1;
386	}
387
388	if (!islink && !Nflag) {
389		unsigned long fflags = fs->st_flags;
390		/*
391		 * XXX
392		 * NFS doesn't support chflags; ignore errors unless
393		 * there's reason to believe we're losing bits.
394		 * (Note, this still won't be right if the server
395		 * supports flags and we were trying to *remove* flags
396		 * on a file that we copied, i.e., that we didn't create.)
397		 */
398		errno = 0;
399		if ((fd ? fchflags(fd, fflags) :
400		    chflags(to.p_path, fflags)) == -1)
401			if (errno != EOPNOTSUPP || fs->st_flags != 0) {
402				warn("chflags: %s", to.p_path);
403				rval = 1;
404			}
405	}
406	/* if fd is non-zero, caller must call set_utimes() after close() */
407	if (fd == 0 && set_utimes(to.p_path, fs))
408	    rval = 1;
409	return (rval);
410}
411
412void
413usage(void)
414{
415	(void)fprintf(stderr,
416	    "usage: %s [-R [-H | -L | -P]] [-f | -i] [-alNpv] src target\n"
417	    "       %s [-R [-H | -L | -P]] [-f | -i] [-alNpv] src1 ... srcN directory\n",
418	    getprogname(), getprogname());
419	exit(1);
420	/* NOTREACHED */
421}
422