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 35static 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 66int 67set_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 81struct finfo { 82 const char *from; 83 const char *to; 84 off_t size; 85}; 86 87static void 88progress(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 98int 99copy_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 303int 304copy_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 325int 326copy_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 339int 340copy_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 */ 363int 364setfile(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 416int 417preserve_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 468int 469preserve_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 556void 557usage(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