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