1/*	$OpenBSD: utils.c,v 1.50 2021/11/28 19:28:41 deraadt Exp $	*/
2/*	$NetBSD: utils.c,v 1.6 1997/02/26 14:40:51 cgd Exp $	*/
3
4/*-
5 * Copyright (c) 1991, 1993, 1994
6 *	The Regents of the University of California.  All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33#include <sys/types.h>
34#include <sys/stat.h>
35#include <sys/mman.h>
36#include <sys/time.h>
37
38#include <err.h>
39#include <errno.h>
40#include <fcntl.h>
41#include <fts.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <unistd.h>
46#include <limits.h>
47
48#include "extern.h"
49
50#define _MAXBSIZE	(64 * 1024)
51
52int copy_overwrite(void);
53
54int
55copy_file(FTSENT *entp, int exists)
56{
57	static char *buf;
58	static char *zeroes;
59	struct stat to_stat, *fs;
60	int from_fd, rcount, rval, to_fd, wcount;
61	const size_t buflen = _MAXBSIZE;
62#ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
63	char *p;
64#endif
65
66	if (!buf) {
67		buf = malloc(buflen);
68		if (!buf)
69			err(1, "malloc");
70	}
71	if (!zeroes) {
72		zeroes = calloc(1, buflen);
73		if (!zeroes)
74			err(1, "calloc");
75	}
76
77	if ((from_fd = open(entp->fts_path, O_RDONLY)) == -1) {
78		warn("%s", entp->fts_path);
79		return (1);
80	}
81
82	fs = entp->fts_statp;
83
84	/*
85	 * In -f (force) mode, we always unlink the destination first
86	 * if it exists.  Note that -i and -f are mutually exclusive.
87	 */
88	if (exists && fflag)
89		(void)unlink(to.p_path);
90
91	/*
92	 * If the file DNE, set the mode to be the from file, minus setuid
93	 * bits, modified by the umask; arguably wrong, but it makes copying
94	 * executables work right and it's been that way forever.  (The
95	 * other choice is 666 or'ed with the execute bits on the from file
96	 * modified by the umask.)
97	 */
98	if (exists && !fflag) {
99		if (!copy_overwrite()) {
100			(void)close(from_fd);
101			return 2;
102 		}
103		to_fd = open(to.p_path, O_WRONLY | O_TRUNC);
104	} else
105		to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
106		    fs->st_mode & ~(S_ISTXT | S_ISUID | S_ISGID));
107
108	if (to_fd == -1) {
109		warn("%s", to.p_path);
110		(void)close(from_fd);
111		return (1);
112	}
113
114	rval = 0;
115
116	/*
117	 * Mmap and write if less than 8M (the limit is so we don't totally
118	 * trash memory on big files.  This is really a minor hack, but it
119	 * wins some CPU back.
120	 */
121#ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
122	/* XXX broken for 0-size mmap */
123	if (fs->st_size <= 8 * 1048576) {
124		if ((p = mmap(NULL, (size_t)fs->st_size, PROT_READ,
125		    MAP_FILE|MAP_SHARED, from_fd, (off_t)0)) == MAP_FAILED) {
126			warn("mmap: %s", entp->fts_path);
127			rval = 1;
128		} else {
129			madvise(p, fs->st_size, MADV_SEQUENTIAL);
130			if (write(to_fd, p, fs->st_size) != fs->st_size) {
131				warn("%s", to.p_path);
132				rval = 1;
133			}
134			/* Some systems don't unmap on close(2). */
135			if (munmap(p, fs->st_size) == -1) {
136				warn("%s", entp->fts_path);
137				rval = 1;
138			}
139		}
140	} else
141#endif
142	{
143		int skipholes = 0;
144		struct stat tosb;
145		if (!fstat(to_fd, &tosb) && S_ISREG(tosb.st_mode))
146			skipholes = 1;
147		while ((rcount = read(from_fd, buf, buflen)) > 0) {
148			if (skipholes && memcmp(buf, zeroes, rcount) == 0)
149				wcount = lseek(to_fd, rcount, SEEK_CUR) == -1 ? -1 : rcount;
150			else
151				wcount = write(to_fd, buf, rcount);
152			if (rcount != wcount || wcount == -1) {
153				warn("%s", to.p_path);
154				rval = 1;
155				break;
156			}
157		}
158		if (skipholes && rcount != -1)
159			rcount = ftruncate(to_fd, lseek(to_fd, 0, SEEK_CUR));
160		if (rcount == -1) {
161			warn("%s", entp->fts_path);
162			rval = 1;
163		}
164	}
165
166	if (rval == 1) {
167		(void)close(from_fd);
168		(void)close(to_fd);
169		return (1);
170	}
171
172	if (pflag && setfile(fs, to_fd))
173		rval = 1;
174	/*
175	 * If the source was setuid or setgid, lose the bits unless the
176	 * copy is owned by the same user and group.
177	 */
178#define	RETAINBITS \
179	(S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
180	if (!pflag && !exists &&
181	    fs->st_mode & (S_ISUID | S_ISGID) && fs->st_uid == myuid) {
182		if (fstat(to_fd, &to_stat)) {
183			warn("%s", to.p_path);
184			rval = 1;
185		} else if (fs->st_gid == to_stat.st_gid &&
186		    fchmod(to_fd, fs->st_mode & RETAINBITS & ~myumask)) {
187			warn("%s", to.p_path);
188			rval = 1;
189		}
190	}
191	(void)close(from_fd);
192	if (close(to_fd)) {
193		warn("%s", to.p_path);
194		rval = 1;
195	}
196	return (rval);
197}
198
199int
200copy_link(FTSENT *p, int exists)
201{
202	int len;
203	char name[PATH_MAX];
204
205	if (exists && !copy_overwrite())
206		return (2);
207	if ((len = readlink(p->fts_path, name, sizeof(name)-1)) == -1) {
208		warn("readlink: %s", p->fts_path);
209		return (1);
210	}
211	name[len] = '\0';
212	if (exists && unlink(to.p_path)) {
213		warn("unlink: %s", to.p_path);
214		return (1);
215	}
216	if (symlink(name, to.p_path)) {
217		warn("symlink: %s", name);
218		return (1);
219	}
220	return (pflag ? setfile(p->fts_statp, -1) : 0);
221}
222
223int
224copy_fifo(struct stat *from_stat, int exists)
225{
226	if (exists && !copy_overwrite())
227		return (2);
228	if (exists && unlink(to.p_path)) {
229		warn("unlink: %s", to.p_path);
230		return (1);
231	}
232	if (mkfifo(to.p_path, from_stat->st_mode)) {
233		warn("mkfifo: %s", to.p_path);
234		return (1);
235	}
236	return (pflag ? setfile(from_stat, -1) : 0);
237}
238
239int
240copy_special(struct stat *from_stat, int exists)
241{
242	if (exists && !copy_overwrite())
243		return (2);
244	if (exists && unlink(to.p_path)) {
245		warn("unlink: %s", to.p_path);
246		return (1);
247	}
248	if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
249		warn("mknod: %s", to.p_path);
250		return (1);
251	}
252	return (pflag ? setfile(from_stat, -1) : 0);
253}
254
255/*
256 * If the file exists and we're interactive, verify with the user.
257 */
258int
259copy_overwrite(void)
260{
261	int ch, checkch;
262
263	if (iflag) {
264		(void)fprintf(stderr, "overwrite %s? ", to.p_path);
265		checkch = ch = getchar();
266		while (ch != '\n' && ch != EOF)
267			ch = getchar();
268		if (checkch != 'y' && checkch != 'Y')
269			return (0);
270	}
271	return 1;
272}
273
274int
275setfile(struct stat *fs, int fd)
276{
277	struct timespec ts[2];
278	int rval;
279
280	rval = 0;
281	fs->st_mode &= S_ISTXT | S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO;
282
283	ts[0] = fs->st_atim;
284	ts[1] = fs->st_mtim;
285	if (fd >= 0 ? futimens(fd, ts) :
286	    utimensat(AT_FDCWD, to.p_path, ts, AT_SYMLINK_NOFOLLOW)) {
287		warn("update times: %s", to.p_path);
288		rval = 1;
289	}
290	/*
291	 * Changing the ownership probably won't succeed, unless we're root
292	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
293	 * the mode; current BSD behavior is to remove all setuid bits on
294	 * chown.  If chown fails, lose setuid/setgid bits.
295	 */
296	if (fd >= 0 ? fchown(fd, fs->st_uid, fs->st_gid) :
297	    lchown(to.p_path, fs->st_uid, fs->st_gid)) {
298		if (errno != EPERM) {
299			warn("chown: %s", to.p_path);
300			rval = 1;
301		}
302		fs->st_mode &= ~(S_ISTXT | S_ISUID | S_ISGID);
303	}
304	if (fd >= 0 ? fchmod(fd, fs->st_mode) :
305	    fchmodat(AT_FDCWD, to.p_path, fs->st_mode, AT_SYMLINK_NOFOLLOW)) {
306		warn("chmod: %s", to.p_path);
307		rval = 1;
308	}
309
310	/*
311	 * XXX
312	 * NFS doesn't support chflags; ignore errors unless there's reason
313	 * to believe we're losing bits.  (Note, this still won't be right
314	 * if the server supports flags and we were trying to *remove* flags
315	 * on a file that we copied, i.e., that we didn't create.)
316	 */
317	errno = 0;
318	if (fd >= 0 ? fchflags(fd, fs->st_flags) :
319	    chflagsat(AT_FDCWD, to.p_path, fs->st_flags, AT_SYMLINK_NOFOLLOW))
320		if (errno != EOPNOTSUPP || fs->st_flags != 0) {
321			warn("chflags: %s", to.p_path);
322			rval = 1;
323		}
324	return (rval);
325}
326
327
328void
329usage(void)
330{
331	(void)fprintf(stderr,
332	    "usage: %s [-afipv] [-R [-H | -L | -P]] source target\n", __progname);
333	(void)fprintf(stderr,
334	    "       %s [-afipv] [-R [-H | -L | -P]] source ... directory\n",
335	    __progname);
336	exit(1);
337}
338