1/* $NetBSD: utils.c,v 1.42 2013/12/11 06:00:11 dholland 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.42 2013/12/11 06:00:11 dholland 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#ifdef __ANDROID__
68    tv[0].tv_sec = fs->st_atime;
69    tv[0].tv_usec = 0;
70    tv[1].tv_sec = fs->st_mtime;
71    tv[1].tv_usec = 0;
72
73    if (utimes(file, tv)) {
74        warn("utimes: %s", file);
75        return 1;
76    }
77#else
78    TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
79    TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
80
81    if (lutimes(file, tv)) {
82	warn("lutimes: %s", file);
83	return (1);
84    }
85#endif
86    return (0);
87}
88
89struct finfo {
90	const char *from;
91	const char *to;
92	size_t size;
93};
94
95static void
96progress(const struct finfo *fi, size_t written)
97{
98	int pcent = (int)((100.0 * written) / fi->size);
99
100	pinfo = 0;
101	(void)fprintf(stderr, "%s => %s %zu/%zu bytes %d%% written\n",
102	    fi->from, fi->to, written, fi->size, pcent);
103}
104
105int
106copy_file(FTSENT *entp, int dne)
107{
108	static char buf[MAXBSIZE];
109	struct stat to_stat, *fs;
110	int ch, checkch, from_fd, rcount, rval, to_fd, tolnk, wcount;
111	char *p;
112	size_t ptotal = 0;
113
114	if ((from_fd = open(entp->fts_path, O_RDONLY, 0)) == -1) {
115		warn("%s", entp->fts_path);
116		return (1);
117	}
118
119	to_fd = -1;
120	fs = entp->fts_statp;
121	tolnk = ((Rflag && !(Lflag || Hflag)) || Pflag);
122
123	/*
124	 * If the file exists and we're interactive, verify with the user.
125	 * If the file DNE, set the mode to be the from file, minus setuid
126	 * bits, modified by the umask; arguably wrong, but it makes copying
127	 * executables work right and it's been that way forever.  (The
128	 * other choice is 666 or'ed with the execute bits on the from file
129	 * modified by the umask.)
130	 */
131	if (!dne) {
132		struct stat sb;
133		int sval;
134
135		if (iflag) {
136			(void)fprintf(stderr, "overwrite %s? ", to.p_path);
137			checkch = ch = getchar();
138			while (ch != '\n' && ch != EOF)
139				ch = getchar();
140			if (checkch != 'y' && checkch != 'Y') {
141				(void)close(from_fd);
142				return (0);
143			}
144		}
145
146		sval = tolnk ?
147			lstat(to.p_path, &sb) : stat(to.p_path, &sb);
148		if (sval == -1) {
149			warn("stat: %s", to.p_path);
150			(void)close(from_fd);
151			return (1);
152		}
153
154		if (!(tolnk && S_ISLNK(sb.st_mode)))
155			to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0);
156	} else
157		to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
158		    fs->st_mode & ~(S_ISUID | S_ISGID));
159
160	if (to_fd == -1 && (fflag || tolnk)) {
161		/*
162		 * attempt to remove existing destination file name and
163		 * create a new file
164		 */
165		(void)unlink(to.p_path);
166		to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
167			     fs->st_mode & ~(S_ISUID | S_ISGID));
168	}
169
170	if (to_fd == -1) {
171		warn("%s", to.p_path);
172		(void)close(from_fd);
173		return (1);
174	}
175
176	rval = 0;
177
178	/* if hard linking then simply close the open fds, link and return */
179	if (lflag) {
180		(void)close(from_fd);
181		(void)close(to_fd);
182		(void)unlink(to.p_path);
183		if (link(entp->fts_path, to.p_path)) {
184			warn("%s", to.p_path);
185			return (1);
186		}
187		return (0);
188	}
189
190	/*
191	 * There's no reason to do anything other than close the file
192	 * now if it's empty, so let's not bother.
193	 */
194#ifndef __ANDROID__ // Files in /proc report length 0. mmap will fail but we'll fall back to read.
195	if (fs->st_size > 0) {
196#endif
197		struct finfo fi;
198
199		fi.from = entp->fts_path;
200		fi.to = to.p_path;
201		fi.size = (size_t)fs->st_size;
202
203		/*
204		 * Mmap and write if less than 8M (the limit is so
205		 * we don't totally trash memory on big files).
206		 * This is really a minor hack, but it wins some CPU back.
207		 */
208		bool use_read;
209
210		use_read = true;
211		if (fs->st_size <= MMAP_MAX_SIZE) {
212			size_t fsize = (size_t)fs->st_size;
213			p = mmap(NULL, fsize, PROT_READ, MAP_FILE|MAP_SHARED,
214			    from_fd, (off_t)0);
215			if (p != MAP_FAILED) {
216				size_t remainder;
217
218				use_read = false;
219
220				(void) madvise(p, (size_t)fs->st_size,
221				     MADV_SEQUENTIAL);
222
223				/*
224				 * Write out the data in small chunks to
225				 * avoid locking the output file for a
226				 * long time if the reading the data from
227				 * the source is slow.
228				 */
229				remainder = fsize;
230				do {
231					ssize_t chunk;
232
233					chunk = (remainder > MMAP_MAX_WRITE) ?
234					    MMAP_MAX_WRITE : remainder;
235					if (write(to_fd, &p[fsize - remainder],
236					    chunk) != chunk) {
237						warn("%s", to.p_path);
238						rval = 1;
239						break;
240					}
241					remainder -= chunk;
242					ptotal += chunk;
243					if (pinfo)
244						progress(&fi, ptotal);
245				} while (remainder > 0);
246
247				if (munmap(p, fsize) < 0) {
248					warn("%s", entp->fts_path);
249					rval = 1;
250				}
251			}
252		}
253
254		if (use_read) {
255			while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
256				wcount = write(to_fd, buf, (size_t)rcount);
257				if (rcount != wcount || wcount == -1) {
258					warn("%s", to.p_path);
259					rval = 1;
260					break;
261				}
262				ptotal += wcount;
263				if (pinfo)
264					progress(&fi, ptotal);
265			}
266			if (rcount < 0) {
267				warn("%s", entp->fts_path);
268				rval = 1;
269			}
270		}
271#ifndef __ANDROID__
272	}
273#endif
274
275#ifndef __ANDROID__
276	if (pflag && (fcpxattr(from_fd, to_fd) != 0))
277		warn("%s: error copying extended attributes", to.p_path);
278#endif
279
280	(void)close(from_fd);
281
282	if (rval == 1) {
283		(void)close(to_fd);
284		return (1);
285	}
286
287	if (pflag && setfile(fs, to_fd))
288		rval = 1;
289	/*
290	 * If the source was setuid or setgid, lose the bits unless the
291	 * copy is owned by the same user and group.
292	 */
293#define	RETAINBITS \
294	(S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
295	if (!pflag && dne
296	    && fs->st_mode & (S_ISUID | S_ISGID) && fs->st_uid == myuid) {
297		if (fstat(to_fd, &to_stat)) {
298			warn("%s", to.p_path);
299			rval = 1;
300		} else if (fs->st_gid == to_stat.st_gid &&
301		    fchmod(to_fd, fs->st_mode & RETAINBITS & ~myumask)) {
302			warn("%s", to.p_path);
303			rval = 1;
304		}
305	}
306	if (close(to_fd)) {
307		warn("%s", to.p_path);
308		rval = 1;
309	}
310	/* set the mod/access times now after close of the fd */
311	if (pflag && set_utimes(to.p_path, fs)) {
312	    rval = 1;
313	}
314	return (rval);
315}
316
317int
318copy_link(FTSENT *p, int exists)
319{
320	int len;
321	char target[MAXPATHLEN];
322
323	if ((len = readlink(p->fts_path, target, sizeof(target)-1)) == -1) {
324		warn("readlink: %s", p->fts_path);
325		return (1);
326	}
327	target[len] = '\0';
328	if (exists && unlink(to.p_path)) {
329		warn("unlink: %s", to.p_path);
330		return (1);
331	}
332	if (symlink(target, to.p_path)) {
333		warn("symlink: %s", target);
334		return (1);
335	}
336	return (pflag ? setfile(p->fts_statp, 0) : 0);
337}
338
339int
340copy_fifo(struct stat *from_stat, int exists)
341{
342	if (exists && unlink(to.p_path)) {
343		warn("unlink: %s", to.p_path);
344		return (1);
345	}
346	if (mkfifo(to.p_path, from_stat->st_mode)) {
347		warn("mkfifo: %s", to.p_path);
348		return (1);
349	}
350	return (pflag ? setfile(from_stat, 0) : 0);
351}
352
353int
354copy_special(struct stat *from_stat, int exists)
355{
356	if (exists && unlink(to.p_path)) {
357		warn("unlink: %s", to.p_path);
358		return (1);
359	}
360	if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
361		warn("mknod: %s", to.p_path);
362		return (1);
363	}
364	return (pflag ? setfile(from_stat, 0) : 0);
365}
366
367
368/*
369 * Function: setfile
370 *
371 * Purpose:
372 *   Set the owner/group/permissions for the "to" file to the information
373 *   in the stat structure.  If fd is zero, also call set_utimes() to set
374 *   the mod/access times.  If fd is non-zero, the caller must do a utimes
375 *   itself after close(fd).
376 */
377int
378setfile(struct stat *fs, int fd)
379{
380	int rval, islink;
381
382	rval = 0;
383	islink = S_ISLNK(fs->st_mode);
384	fs->st_mode &= S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO;
385
386	/*
387	 * Changing the ownership probably won't succeed, unless we're root
388	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
389	 * the mode; current BSD behavior is to remove all setuid bits on
390	 * chown.  If chown fails, lose setuid/setgid bits.
391	 */
392	if (fd ? fchown(fd, fs->st_uid, fs->st_gid) :
393	    lchown(to.p_path, fs->st_uid, fs->st_gid)) {
394		if (errno != EPERM) {
395			warn("chown: %s", to.p_path);
396			rval = 1;
397		}
398		fs->st_mode &= ~(S_ISUID | S_ISGID);
399	}
400#ifdef __ANDROID__
401	if (fd ? fchmod(fd, fs->st_mode) : chmod(to.p_path, fs->st_mode)) {
402#else
403	if (fd ? fchmod(fd, fs->st_mode) : lchmod(to.p_path, fs->st_mode)) {
404#endif
405		warn("chmod: %s", to.p_path);
406		rval = 1;
407	}
408
409#ifndef __ANDROID__
410	if (!islink && !Nflag) {
411		unsigned long fflags = fs->st_flags;
412		/*
413		 * XXX
414		 * NFS doesn't support chflags; ignore errors unless
415		 * there's reason to believe we're losing bits.
416		 * (Note, this still won't be right if the server
417		 * supports flags and we were trying to *remove* flags
418		 * on a file that we copied, i.e., that we didn't create.)
419		 */
420		errno = 0;
421		if ((fd ? fchflags(fd, fflags) :
422		    chflags(to.p_path, fflags)) == -1)
423			if (errno != EOPNOTSUPP || fs->st_flags != 0) {
424				warn("chflags: %s", to.p_path);
425				rval = 1;
426			}
427	}
428#endif
429	/* if fd is non-zero, caller must call set_utimes() after close() */
430	if (fd == 0 && set_utimes(to.p_path, fs))
431	    rval = 1;
432	return (rval);
433}
434
435void
436usage(void)
437{
438	(void)fprintf(stderr,
439	    "usage: %s [-R [-H | -L | -P]] [-f | -i] [-alNpv] src target\n"
440	    "       %s [-R [-H | -L | -P]] [-f | -i] [-alNpv] src1 ... srcN directory\n",
441	    getprogname(), getprogname());
442	exit(1);
443	/* NOTREACHED */
444}
445