1 /* $NetBSD: utils.c,v 1.50 2024/01/15 17:41:06 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
35 static char sccsid[] = "@(#)utils.c 8.3 (Berkeley) 4/1/94";
36 #else
37 __RCSID("$NetBSD: utils.c,v 1.50 2024/01/15 17:41:06 christos Exp $");
38 #endif
39 #endif /* not lint */
40
41 #define _ACL_PRIVATE
42 #include <sys/mman.h>
43 #include <sys/param.h>
44 #include <sys/stat.h>
45 #include <sys/time.h>
46 #ifndef SMALL
47 #include <sys/acl.h>
48 #endif
49 #include <sys/extattr.h>
50
51 #include <err.h>
52 #include <errno.h>
53 #include <fcntl.h>
54 #include <fts.h>
55 #include <stdbool.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <unistd.h>
60
61 #include "extern.h"
62
63 #define MMAP_MAX_SIZE (8 * 1048576)
64 #define MMAP_MAX_WRITE (64 * 1024)
65
66 int
set_utimes(const char * file,struct stat * fs)67 set_utimes(const char *file, struct stat *fs)
68 {
69 struct timespec ts[2];
70
71 ts[0] = fs->st_atimespec;
72 ts[1] = fs->st_mtimespec;
73
74 if (lutimens(file, ts)) {
75 warn("lutimens: %s", file);
76 return (1);
77 }
78 return (0);
79 }
80
81 struct finfo {
82 const char *from;
83 const char *to;
84 off_t size;
85 };
86
87 static void
progress(const struct finfo * fi,off_t written)88 progress(const struct finfo *fi, off_t written)
89 {
90 int pcent = (int)((100.0 * written) / fi->size);
91
92 pinfo = 0;
93 (void)fprintf(stderr, "%s => %s %llu/%llu bytes %d%% written\n",
94 fi->from, fi->to, (unsigned long long)written,
95 (unsigned long long)fi->size, pcent);
96 }
97
98 int
copy_file(FTSENT * entp,int dne)99 copy_file(FTSENT *entp, int dne)
100 {
101 static char buf[MAXBSIZE];
102 struct stat to_stat, *fs;
103 int ch, checkch, from_fd, rcount, rval, to_fd, tolnk, wcount;
104 char *p;
105 off_t ptotal = 0;
106
107 /* if hard linking then simply link and return */
108 if (lflag) {
109 (void)unlink(to.p_path);
110 if (link(entp->fts_path, to.p_path)) {
111 warn("%s", to.p_path);
112 return (1);
113 }
114 return (0);
115 }
116
117 if ((from_fd = open(entp->fts_path, O_RDONLY, 0)) == -1) {
118 warn("%s", entp->fts_path);
119 return (1);
120 }
121
122 to_fd = -1;
123 fs = entp->fts_statp;
124 tolnk = ((Rflag && !(Lflag || Hflag)) || Pflag);
125
126 /*
127 * If the file exists and we're interactive, verify with the user.
128 * If the file DNE, set the mode to be the from file, minus setuid
129 * bits, modified by the umask; arguably wrong, but it makes copying
130 * executables work right and it's been that way forever. (The
131 * other choice is 666 or'ed with the execute bits on the from file
132 * modified by the umask.)
133 */
134 if (!dne) {
135 struct stat sb;
136 int sval;
137
138 if (iflag) {
139 (void)fprintf(stderr, "overwrite %s? ", to.p_path);
140 checkch = ch = getchar();
141 while (ch != '\n' && ch != EOF)
142 ch = getchar();
143 if (checkch != 'y' && checkch != 'Y') {
144 (void)close(from_fd);
145 return (0);
146 }
147 }
148
149 sval = tolnk ?
150 lstat(to.p_path, &sb) : stat(to.p_path, &sb);
151 if (sval == -1) {
152 warn("stat: %s", to.p_path);
153 (void)close(from_fd);
154 return (1);
155 }
156
157 if (!(tolnk && S_ISLNK(sb.st_mode)))
158 to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0);
159 } else
160 to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
161 fs->st_mode & ~(S_ISUID | S_ISGID));
162
163 if (to_fd == -1 && (fflag || tolnk)) {
164 /*
165 * attempt to remove existing destination file name and
166 * create a new file
167 */
168 (void)unlink(to.p_path);
169 to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
170 fs->st_mode & ~(S_ISUID | S_ISGID));
171 }
172
173 if (to_fd == -1) {
174 warn("%s", to.p_path);
175 (void)close(from_fd);
176 return (1);
177 }
178
179 rval = 0;
180
181 /*
182 * We always copy regular files, even if they appear to be 0-sized
183 * because kernfs and procfs don't return file sizes.
184 */
185 bool need_copy = S_ISREG(fs->st_mode) || fs->st_size > 0;
186
187 struct finfo fi;
188
189 fi.from = entp->fts_path;
190 fi.to = to.p_path;
191 fi.size = fs->st_size;
192
193 /*
194 * Mmap and write if less than 8M (the limit is so
195 * we don't totally trash memory on big files).
196 * This is really a minor hack, but it wins some CPU back.
197 */
198 if (S_ISREG(fs->st_mode) && fs->st_size && 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 need_copy = false;
206
207 (void) madvise(p, (size_t)fs->st_size, MADV_SEQUENTIAL);
208
209 /*
210 * Write out the data in small chunks to
211 * avoid locking the output file for a
212 * long time if the reading the data from
213 * the source is slow.
214 */
215 remainder = fsize;
216 do {
217 ssize_t chunk;
218
219 chunk = (remainder > MMAP_MAX_WRITE) ?
220 MMAP_MAX_WRITE : remainder;
221 if (write(to_fd, &p[fsize - remainder],
222 chunk) != chunk) {
223 warn("%s", to.p_path);
224 rval = 1;
225 break;
226 }
227 remainder -= chunk;
228 ptotal += chunk;
229 if (pinfo)
230 progress(&fi, ptotal);
231 } while (remainder > 0);
232
233 if (munmap(p, fsize) < 0) {
234 warn("%s", entp->fts_path);
235 rval = 1;
236 }
237 }
238 }
239
240 if (need_copy) {
241 while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
242 wcount = write(to_fd, buf, (size_t)rcount);
243 if (rcount != wcount || wcount == -1) {
244 warn("%s", to.p_path);
245 rval = 1;
246 break;
247 }
248 ptotal += wcount;
249 if (pinfo)
250 progress(&fi, ptotal);
251 }
252 if (rcount < 0) {
253 warn("%s", entp->fts_path);
254 rval = 1;
255 }
256 }
257
258 if (pflag && (fcpxattr(from_fd, to_fd) != 0))
259 warn("%s: error copying extended attributes", to.p_path);
260
261 #ifndef SMALL
262 if (pflag && preserve_fd_acls(from_fd, to_fd) != 0)
263 rval = 1;
264 #endif
265
266 (void)close(from_fd);
267
268 if (rval == 1) {
269 (void)close(to_fd);
270 return (1);
271 }
272
273 if (pflag && setfile(fs, to_fd))
274 rval = 1;
275 /*
276 * If the source was setuid or setgid, lose the bits unless the
277 * copy is owned by the same user and group.
278 */
279 #define RETAINBITS \
280 (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
281 if (!pflag && dne
282 && fs->st_mode & (S_ISUID | S_ISGID) && fs->st_uid == myuid) {
283 if (fstat(to_fd, &to_stat)) {
284 warn("%s", to.p_path);
285 rval = 1;
286 } else if (fs->st_gid == to_stat.st_gid &&
287 fchmod(to_fd, fs->st_mode & RETAINBITS & ~myumask)) {
288 warn("%s", to.p_path);
289 rval = 1;
290 }
291 }
292 if (close(to_fd)) {
293 warn("%s", to.p_path);
294 rval = 1;
295 }
296 /* set the mod/access times now after close of the fd */
297 if (pflag && set_utimes(to.p_path, fs)) {
298 rval = 1;
299 }
300 return (rval);
301 }
302
303 int
copy_link(FTSENT * p,int exists)304 copy_link(FTSENT *p, int exists)
305 {
306 int len;
307 char target[MAXPATHLEN];
308
309 if ((len = readlink(p->fts_path, target, sizeof(target)-1)) == -1) {
310 warn("readlink: %s", p->fts_path);
311 return (1);
312 }
313 target[len] = '\0';
314 if (exists && unlink(to.p_path)) {
315 warn("unlink: %s", to.p_path);
316 return (1);
317 }
318 if (symlink(target, to.p_path)) {
319 warn("symlink: %s", target);
320 return (1);
321 }
322 return (pflag ? setfile(p->fts_statp, 0) : 0);
323 }
324
325 int
copy_fifo(struct stat * from_stat,int exists)326 copy_fifo(struct stat *from_stat, int exists)
327 {
328 if (exists && unlink(to.p_path)) {
329 warn("unlink: %s", to.p_path);
330 return (1);
331 }
332 if (mkfifo(to.p_path, from_stat->st_mode)) {
333 warn("mkfifo: %s", to.p_path);
334 return (1);
335 }
336 return (pflag ? setfile(from_stat, 0) : 0);
337 }
338
339 int
copy_special(struct stat * from_stat,int exists)340 copy_special(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 (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
347 warn("mknod: %s", to.p_path);
348 return (1);
349 }
350 return (pflag ? setfile(from_stat, 0) : 0);
351 }
352
353
354 /*
355 * Function: setfile
356 *
357 * Purpose:
358 * Set the owner/group/permissions for the "to" file to the information
359 * in the stat structure. If fd is zero, also call set_utimes() to set
360 * the mod/access times. If fd is non-zero, the caller must do a utimes
361 * itself after close(fd).
362 */
363 int
setfile(struct stat * fs,int fd)364 setfile(struct stat *fs, int fd)
365 {
366 int rval, islink;
367
368 rval = 0;
369 islink = S_ISLNK(fs->st_mode);
370 fs->st_mode &= S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO;
371
372 /*
373 * Changing the ownership probably won't succeed, unless we're root
374 * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting
375 * the mode; current BSD behavior is to remove all setuid bits on
376 * chown. If chown fails, lose setuid/setgid bits.
377 */
378 if (fd ? fchown(fd, fs->st_uid, fs->st_gid) :
379 lchown(to.p_path, fs->st_uid, fs->st_gid)) {
380 if (errno != EPERM) {
381 warn("chown: %s", to.p_path);
382 rval = 1;
383 }
384 fs->st_mode &= ~(S_ISUID | S_ISGID);
385 }
386 if (fd ? fchmod(fd, fs->st_mode) : lchmod(to.p_path, fs->st_mode)) {
387 warn("chmod: %s", to.p_path);
388 rval = 1;
389 }
390
391 if (!islink && !Nflag) {
392 unsigned long fflags = fs->st_flags;
393 /*
394 * XXX
395 * NFS doesn't support chflags; ignore errors unless
396 * there's reason to believe we're losing bits.
397 * (Note, this still won't be right if the server
398 * supports flags and we were trying to *remove* flags
399 * on a file that we copied, i.e., that we didn't create.)
400 */
401 errno = 0;
402 if ((fd ? fchflags(fd, fflags) :
403 chflags(to.p_path, fflags)) == -1)
404 if (errno != EOPNOTSUPP || fs->st_flags != 0) {
405 warn("chflags: %s", to.p_path);
406 rval = 1;
407 }
408 }
409 /* if fd is non-zero, caller must call set_utimes() after close() */
410 if (fd == 0 && set_utimes(to.p_path, fs))
411 rval = 1;
412 return (rval);
413 }
414
415 #ifndef SMALL
416 int
preserve_fd_acls(int source_fd,int dest_fd)417 preserve_fd_acls(int source_fd, int dest_fd)
418 {
419 acl_t acl;
420 acl_type_t acl_type;
421 int acl_supported = 0, ret, trivial;
422
423 ret = fpathconf(source_fd, _PC_ACL_NFS4);
424 if (ret > 0 ) {
425 acl_supported = 1;
426 acl_type = ACL_TYPE_NFS4;
427 } else if (ret < 0 && errno != EINVAL) {
428 warn("fpathconf(..., _PC_ACL_NFS4) failed for %s", to.p_path);
429 return (1);
430 }
431 if (acl_supported == 0) {
432 ret = fpathconf(source_fd, _PC_ACL_EXTENDED);
433 if (ret > 0 ) {
434 acl_supported = 1;
435 acl_type = ACL_TYPE_ACCESS;
436 } else if (ret < 0 && errno != EINVAL) {
437 warn("fpathconf(..., _PC_ACL_EXTENDED) failed for %s",
438 to.p_path);
439 return (1);
440 }
441 }
442 if (acl_supported == 0)
443 return (0);
444
445 acl = acl_get_fd_np(source_fd, acl_type);
446 if (acl == NULL) {
447 warn("failed to get acl entries while setting %s", to.p_path);
448 return (1);
449 }
450 if (acl_is_trivial_np(acl, &trivial)) {
451 warn("acl_is_trivial() failed for %s", to.p_path);
452 acl_free(acl);
453 return (1);
454 }
455 if (trivial) {
456 acl_free(acl);
457 return (0);
458 }
459 if (acl_set_fd_np(dest_fd, acl, acl_type) < 0) {
460 warn("failed to set acl entries for %s", to.p_path);
461 acl_free(acl);
462 return (1);
463 }
464 acl_free(acl);
465 return (0);
466 }
467
468 int
preserve_dir_acls(struct stat * fs,char * source_dir,char * dest_dir)469 preserve_dir_acls(struct stat *fs, char *source_dir, char *dest_dir)
470 {
471 acl_t (*aclgetf)(const char *, acl_type_t);
472 int (*aclsetf)(const char *, acl_type_t, acl_t);
473 struct acl *aclp;
474 acl_t acl;
475 acl_type_t acl_type;
476 int acl_supported = 0, ret, trivial;
477
478 ret = pathconf(source_dir, _PC_ACL_NFS4);
479 if (ret > 0) {
480 acl_supported = 1;
481 acl_type = ACL_TYPE_NFS4;
482 } else if (ret < 0 && errno != EINVAL) {
483 warn("fpathconf(..., _PC_ACL_NFS4) failed for %s", source_dir);
484 return (1);
485 }
486 if (acl_supported == 0) {
487 ret = pathconf(source_dir, _PC_ACL_EXTENDED);
488 if (ret > 0) {
489 acl_supported = 1;
490 acl_type = ACL_TYPE_ACCESS;
491 } else if (ret < 0 && errno != EINVAL) {
492 warn("fpathconf(..., _PC_ACL_EXTENDED) failed for %s",
493 source_dir);
494 return (1);
495 }
496 }
497 if (acl_supported == 0)
498 return (0);
499
500 /*
501 * If the file is a link we will not follow it.
502 */
503 if (S_ISLNK(fs->st_mode)) {
504 aclgetf = acl_get_link_np;
505 aclsetf = acl_set_link_np;
506 } else {
507 aclgetf = acl_get_file;
508 aclsetf = acl_set_file;
509 }
510 if (acl_type == ACL_TYPE_ACCESS) {
511 /*
512 * Even if there is no ACL_TYPE_DEFAULT entry here, a zero
513 * size ACL will be returned. So it is not safe to simply
514 * check the pointer to see if the default ACL is present.
515 */
516 acl = aclgetf(source_dir, ACL_TYPE_DEFAULT);
517 if (acl == NULL) {
518 warn("failed to get default acl entries on %s",
519 source_dir);
520 return (1);
521 }
522 aclp = &acl->ats_acl;
523 if (aclp->acl_cnt != 0 && aclsetf(dest_dir,
524 ACL_TYPE_DEFAULT, acl) < 0) {
525 warn("failed to set default acl entries on %s",
526 dest_dir);
527 acl_free(acl);
528 return (1);
529 }
530 acl_free(acl);
531 }
532 acl = aclgetf(source_dir, acl_type);
533 if (acl == NULL) {
534 warn("failed to get acl entries on %s", source_dir);
535 return (1);
536 }
537 if (acl_is_trivial_np(acl, &trivial)) {
538 warn("acl_is_trivial() failed on %s", source_dir);
539 acl_free(acl);
540 return (1);
541 }
542 if (trivial) {
543 acl_free(acl);
544 return (0);
545 }
546 if (aclsetf(dest_dir, acl_type, acl) < 0) {
547 warn("failed to set acl entries on %s", dest_dir);
548 acl_free(acl);
549 return (1);
550 }
551 acl_free(acl);
552 return (0);
553 }
554 #endif
555
556 void
usage(void)557 usage(void)
558 {
559 (void)fprintf(stderr,
560 "usage: %s [-R [-H | -L | -P]] [-f | -i] [-alNpv] src target\n"
561 " %s [-R [-H | -L | -P]] [-f | -i] [-alNpv] src1 ... srcN directory\n",
562 getprogname(), getprogname());
563 exit(1);
564 /* NOTREACHED */
565 }
566