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