xinstall.c revision 29379
1/* 2 * Copyright (c) 1987, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by the University of 16 * California, Berkeley and its contributors. 17 * 4. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34#ifndef lint 35static const char copyright[] = 36"@(#) Copyright (c) 1987, 1993\n\ 37 The Regents of the University of California. All rights reserved.\n"; 38#endif /* not lint */ 39 40#ifndef lint 41#if 0 42static char sccsid[] = "From: @(#)xinstall.c 8.1 (Berkeley) 7/21/93"; 43#endif 44static const char rcsid[] = 45 "$Id: xinstall.c,v 1.24 1997/08/27 06:29:23 charnier Exp $"; 46#endif /* not lint */ 47 48/*- 49 * Todo: 50 * o for -C, compare original files except in -s case. 51 * o for -C, don't change anything if nothing needs be changed. In 52 * particular, don't toggle the immutable flags just to allow null 53 * attribute changes and don't clear the dump flag. (I think inode 54 * ctimes are not updated for null attribute changes, but this is a 55 * bug.) 56 * o independent of -C, if a copy must be made, then copy to a tmpfile, 57 * set all attributes except the immutable flags, then rename, then 58 * set the immutable flags. It's annoying that the immutable flags 59 * defeat the atomicicity of rename - it seems that there must be 60 * o a window where the target is not immutable. 61 */ 62 63#include <sys/param.h> 64#include <sys/wait.h> 65#include <sys/mman.h> 66#include <sys/stat.h> 67#include <sys/mount.h> 68 69#include <ctype.h> 70#include <err.h> 71#include <errno.h> 72#include <fcntl.h> 73#include <grp.h> 74#include <paths.h> 75#include <pwd.h> 76#include <stdio.h> 77#include <stdlib.h> 78#include <string.h> 79#include <unistd.h> 80#include <sysexits.h> 81#include <utime.h> 82 83#include "pathnames.h" 84 85/* Bootstrap aid - this doesn't exist in most older releases */ 86#ifndef MAP_FAILED 87#define MAP_FAILED ((caddr_t)-1) /* from <sys/mman.h> */ 88#endif 89 90int debug, docompare, docopy, dodir, dopreserve, dostrip, verbose; 91int mode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH; 92char *group, *owner, pathbuf[MAXPATHLEN]; 93char pathbuf2[MAXPATHLEN]; 94 95#define DIRECTORY 0x01 /* Tell install it's a directory. */ 96#define SETFLAGS 0x02 /* Tell install to set flags. */ 97#define NOCHANGEBITS (UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND) 98 99void copy __P((int, char *, int, char *, off_t)); 100int compare __P((int, const char *, int, const char *, 101 const struct stat *, const struct stat *)); 102void install __P((char *, char *, u_long, u_int)); 103void install_dir __P((char *)); 104u_long string_to_flags __P((char **, u_long *, u_long *)); 105void strip __P((char *)); 106void usage __P((void)); 107int trymmap __P((int)); 108 109#define ALLOW_NUMERIC_IDS 1 110#ifdef ALLOW_NUMERIC_IDS 111 112uid_t uid; 113gid_t gid; 114 115uid_t resolve_uid __P((char *)); 116gid_t resolve_gid __P((char *)); 117u_long numeric_id __P((char *, char *)); 118 119#else 120 121struct passwd *pp; 122struct group *gp; 123 124#endif /* ALLOW_NUMERIC_IDS */ 125 126int 127main(argc, argv) 128 int argc; 129 char *argv[]; 130{ 131 struct stat from_sb, to_sb; 132 mode_t *set; 133 u_long fset; 134 u_int iflags; 135 int ch, no_target; 136 char *flags, *to_name; 137 138 iflags = 0; 139 while ((ch = getopt(argc, argv, "CcdDf:g:m:o:psv")) != -1) 140 switch((char)ch) { 141 case 'C': 142 docompare = docopy = 1; 143 break; 144 case 'c': 145 docopy = 1; 146 break; 147 case 'D': 148 debug++; 149 break; 150 case 'd': 151 dodir = 1; 152 break; 153 case 'f': 154 flags = optarg; 155 if (string_to_flags(&flags, &fset, NULL)) 156 errx(EX_USAGE, "%s: invalid flag", flags); 157 iflags |= SETFLAGS; 158 break; 159 case 'g': 160 group = optarg; 161 break; 162 case 'm': 163 if (!(set = setmode(optarg))) 164 errx(EX_USAGE, "invalid file mode: %s", 165 optarg); 166 mode = getmode(set, 0); 167 break; 168 case 'o': 169 owner = optarg; 170 break; 171 case 'p': 172 docompare = docopy = dopreserve = 1; 173 break; 174 case 's': 175 dostrip = 1; 176 break; 177 case 'v': 178 verbose = 1; 179 break; 180 case '?': 181 default: 182 usage(); 183 } 184 argc -= optind; 185 argv += optind; 186 187 /* some options make no sense when creating directories */ 188 if ((docompare || dostrip) && dodir) 189 usage(); 190 191 /* must have at least two arguments, except when creating directories */ 192 if (argc < 2 && !dodir) 193 usage(); 194 195#ifdef ALLOW_NUMERIC_IDS 196 197 if (owner) 198 uid = resolve_uid(owner); 199 if (group) 200 gid = resolve_gid(group); 201 202#else 203 204 /* get group and owner id's */ 205 if (owner && !(pp = getpwnam(owner))) 206 errx(EX_NOUSER, "unknown user %s", owner); 207 if (group && !(gp = getgrnam(group))) 208 errx(EX_NOUSER, "unknown group %s", group); 209 210#endif /* ALLOW_NUMERIC_IDS */ 211 212 if (dodir) { 213 for (; *argv != NULL; ++argv) 214 install_dir(*argv); 215 exit(EX_OK); 216 /* NOTREACHED */ 217 } 218 219 no_target = stat(to_name = argv[argc - 1], &to_sb); 220 if (!no_target && (to_sb.st_mode & S_IFMT) == S_IFDIR) { 221 for (; *argv != to_name; ++argv) 222 install(*argv, to_name, fset, iflags | DIRECTORY); 223 exit(EX_OK); 224 /* NOTREACHED */ 225 } 226 227 /* can't do file1 file2 directory/file */ 228 if (argc != 2) 229 usage(); 230 231 if (!no_target) { 232 if (stat(*argv, &from_sb)) 233 err(EX_OSERR, "%s", *argv); 234 if (!S_ISREG(to_sb.st_mode)) { 235 errno = EFTYPE; 236 err(EX_OSERR, "%s", to_name); 237 } 238 if (to_sb.st_dev == from_sb.st_dev && 239 to_sb.st_ino == from_sb.st_ino) 240 errx(EX_USAGE, 241 "%s and %s are the same file", *argv, to_name); 242/* 243 * XXX - It's not at all clear why this code was here, since it completely 244 * duplicates code install(). The version in install() handles the -C flag 245 * correctly, so we'll just disable this for now. 246 */ 247#if 0 248 /* 249 * Unlink now... avoid ETXTBSY errors later. Try and turn 250 * off the append/immutable bits -- if we fail, go ahead, 251 * it might work. 252 */ 253 if (to_sb.st_flags & NOCHANGEBITS) 254 (void)chflags(to_name, 255 to_sb.st_flags & ~(NOCHANGEBITS)); 256 (void)unlink(to_name); 257#endif 258 } 259 install(*argv, to_name, fset, iflags); 260 exit(EX_OK); 261 /* NOTREACHED */ 262} 263 264#ifdef ALLOW_NUMERIC_IDS 265 266uid_t 267resolve_uid(s) 268 char *s; 269{ 270 struct passwd *pw; 271 272 return ((pw = getpwnam(s)) == NULL) ? 273 (uid_t) numeric_id(s, "user") : pw->pw_uid; 274} 275 276gid_t 277resolve_gid(s) 278 char *s; 279{ 280 struct group *gr; 281 282 return ((gr = getgrnam(s)) == NULL) ? 283 (gid_t) numeric_id(s, "group") : gr->gr_gid; 284} 285 286u_long 287numeric_id(name, type) 288 char *name, *type; 289{ 290 u_long val; 291 char *ep; 292 293 /* 294 * XXX 295 * We know that uid_t's and gid_t's are unsigned longs. 296 */ 297 errno = 0; 298 val = strtoul(name, &ep, 10); 299 if (errno) 300 err(EX_NOUSER, "%s", name); 301 if (*ep != '\0') 302 errx(EX_NOUSER, "unknown %s %s", type, name); 303 return (val); 304} 305 306#endif /* ALLOW_NUMERIC_IDS */ 307 308/* 309 * install -- 310 * build a path name and install the file 311 */ 312void 313install(from_name, to_name, fset, flags) 314 char *from_name, *to_name; 315 u_long fset; 316 u_int flags; 317{ 318 struct stat from_sb, to_sb; 319 int devnull, from_fd, to_fd, serrno; 320 char *p, *old_to_name = 0; 321 322 if (debug >= 2 && !docompare) 323 fprintf(stderr, "install: invoked without -C for %s to %s\n", 324 from_name, to_name); 325 326 /* If try to install NULL file to a directory, fails. */ 327 if (flags & DIRECTORY || strcmp(from_name, _PATH_DEVNULL)) { 328 if (stat(from_name, &from_sb)) 329 err(EX_OSERR, "%s", from_name); 330 if (!S_ISREG(from_sb.st_mode)) { 331 errno = EFTYPE; 332 err(EX_OSERR, "%s", from_name); 333 } 334 /* Build the target path. */ 335 if (flags & DIRECTORY) { 336 (void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s", 337 to_name, 338 (p = strrchr(from_name, '/')) ? ++p : from_name); 339 to_name = pathbuf; 340 } 341 devnull = 0; 342 } else { 343 from_sb.st_flags = 0; /* XXX */ 344 devnull = 1; 345 } 346 347 if (docompare) { 348 old_to_name = to_name; 349 /* 350 * Make a new temporary file in the same file system 351 * (actually, in in the same directory) as the target so 352 * that the temporary file can be renamed to the target. 353 */ 354 snprintf(pathbuf2, sizeof pathbuf2, "%s", to_name); 355 p = strrchr(pathbuf2, '/'); 356 p = (p == NULL ? pathbuf2 : p + 1); 357 snprintf(p, &pathbuf2[sizeof pathbuf2] - p, "INS@XXXX"); 358 to_fd = mkstemp(pathbuf2); 359 if (to_fd < 0) 360 /* XXX should fall back to not comparing. */ 361 err(EX_OSERR, "mkstemp: %s for %s", pathbuf2, to_name); 362 to_name = pathbuf2; 363 } else { 364 /* 365 * Unlink now... avoid errors later. Try to turn off the 366 * append/immutable bits -- if we fail, go ahead, it might 367 * work. 368 */ 369 if (stat(to_name, &to_sb) == 0 && to_sb.st_flags & NOCHANGEBITS) 370 (void)chflags(to_name, to_sb.st_flags & ~NOCHANGEBITS); 371 unlink(to_name); 372 373 /* Create target. */ 374 to_fd = open(to_name, 375 O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR); 376 if (to_fd < 0) 377 err(EX_OSERR, "%s", to_name); 378 } 379 380 if (!devnull) { 381 if ((from_fd = open(from_name, O_RDONLY, 0)) < 0) { 382 serrno = errno; 383 (void)unlink(to_name); 384 errno = serrno; 385 err(EX_OSERR, "%s", from_name); 386 } 387 copy(from_fd, from_name, to_fd, to_name, from_sb.st_size); 388 (void)close(from_fd); 389 } 390 391 if (dostrip) { 392 (void)close(to_fd); 393 394 strip(to_name); 395 396 /* Reopen target. */ 397 to_fd = open(to_name, O_RDWR, 0); 398 if (to_fd < 0) 399 err(EX_OSERR, "%s", to_name); 400 } 401 402 /* 403 * Unfortunately, because we strip the installed file and not the 404 * original one, it is impossible to do the comparison without 405 * first laboriously copying things over and then comparing. 406 * It may be possible to better optimize the !dostrip case, however. 407 * For further study. 408 */ 409 if (docompare) { 410 struct stat old_sb, new_sb, timestamp_sb; 411 int old_fd; 412 struct utimbuf utb; 413 414 old_fd = open(old_to_name, O_RDONLY, 0); 415 if (old_fd < 0 && errno == ENOENT) 416 goto different; 417 if (old_fd < 0) 418 err(EX_OSERR, "%s", old_to_name); 419 fstat(old_fd, &old_sb); 420 if (old_sb.st_flags & NOCHANGEBITS) 421 (void)fchflags(old_fd, old_sb.st_flags & ~NOCHANGEBITS); 422 fstat(to_fd, &new_sb); 423 if (compare(old_fd, old_to_name, to_fd, to_name, &old_sb, 424 &new_sb)) { 425different: 426 if (debug != 0) 427 fprintf(stderr, 428 "install: renaming for %s: %s to %s\n", 429 from_name, to_name, old_to_name); 430 if (dopreserve && stat(from_name, ×tamp_sb) == 0) { 431 utb.actime = from_sb.st_atime; 432 utb.modtime = from_sb.st_mtime; 433 (void)utime(to_name, &utb); 434 } 435moveit: 436 if (verbose) { 437 printf("install: %s -> %s\n", 438 from_name, old_to_name); 439 } 440 if (rename(to_name, old_to_name) < 0) { 441 serrno = errno; 442 unlink(to_name); 443 unlink(old_to_name); 444 errno = serrno; 445 err(EX_OSERR, "rename: %s to %s", to_name, 446 old_to_name); 447 } 448 close(old_fd); 449 } else { 450 if (old_sb.st_nlink != 1) { 451 /* 452 * Replace the target, although it hasn't 453 * changed, to snap the extra links. But 454 * preserve the target file times. 455 */ 456 if (fstat(old_fd, ×tamp_sb) == 0) { 457 utb.actime = timestamp_sb.st_atime; 458 utb.modtime = timestamp_sb.st_mtime; 459 (void)utime(to_name, &utb); 460 } 461 goto moveit; 462 } 463 if (unlink(to_name) < 0) 464 err(EX_OSERR, "unlink: %s", to_name); 465 close(to_fd); 466 to_fd = old_fd; 467 } 468 to_name = old_to_name; 469 } 470 471 /* 472 * Set owner, group, mode for target; do the chown first, 473 * chown may lose the setuid bits. 474 */ 475 if ((group || owner) && 476#ifdef ALLOW_NUMERIC_IDS 477 fchown(to_fd, owner ? uid : -1, group ? gid : -1)) { 478#else 479 fchown(to_fd, owner ? pp->pw_uid : -1, group ? gp->gr_gid : -1)) { 480#endif 481 serrno = errno; 482 (void)unlink(to_name); 483 errno = serrno; 484 err(EX_OSERR,"%s: chown/chgrp", to_name); 485 } 486 if (fchmod(to_fd, mode)) { 487 serrno = errno; 488 (void)unlink(to_name); 489 errno = serrno; 490 err(EX_OSERR, "%s: chmod", to_name); 491 } 492 493 /* 494 * If provided a set of flags, set them, otherwise, preserve the 495 * flags, except for the dump flag. 496 */ 497 if (fchflags(to_fd, 498 flags & SETFLAGS ? fset : from_sb.st_flags & ~UF_NODUMP)) { 499 serrno = errno; 500 (void)unlink(to_name); 501 errno = serrno; 502 err(EX_OSERR, "%s: chflags", to_name); 503 } 504 505 (void)close(to_fd); 506 if (!docopy && !devnull && unlink(from_name)) 507 err(EX_OSERR, "%s", from_name); 508} 509 510/* 511 * compare -- 512 * compare two files; non-zero means files differ 513 */ 514int 515compare(int from_fd, const char *from_name, int to_fd, const char *to_name, 516 const struct stat *from_sb, const struct stat *to_sb) 517{ 518 char *p, *q; 519 int rv; 520 size_t tsize; 521 int done_compare; 522 523 if (from_sb->st_size != to_sb->st_size) 524 return 1; 525 526 tsize = (size_t)from_sb->st_size; 527 528 if (tsize <= 8 * 1024 * 1024) { 529 done_compare = 0; 530 if (trymmap(from_fd) && trymmap(to_fd)) { 531 p = mmap(NULL, tsize, PROT_READ, MAP_SHARED, from_fd, (off_t)0); 532 if (p == (char *)MAP_FAILED) 533 goto out; 534 q = mmap(NULL, tsize, PROT_READ, MAP_SHARED, to_fd, (off_t)0); 535 if (q == (char *)MAP_FAILED) { 536 munmap(p, tsize); 537 goto out; 538 } 539 540 rv = memcmp(p, q, tsize); 541 munmap(p, tsize); 542 munmap(q, tsize); 543 done_compare = 1; 544 } 545 out: 546 if (!done_compare) { 547 char buf1[MAXBSIZE]; 548 char buf2[MAXBSIZE]; 549 int n1, n2; 550 551 rv = 0; 552 lseek(from_fd, 0, SEEK_SET); 553 lseek(to_fd, 0, SEEK_SET); 554 while (rv == 0) { 555 n1 = read(from_fd, buf1, sizeof(buf1)); 556 if (n1 == 0) 557 break; /* EOF */ 558 else if (n1 > 0) { 559 n2 = read(to_fd, buf2, n1); 560 if (n2 == n1) 561 rv = memcmp(buf1, buf2, n1); 562 else 563 rv = 1; /* out of sync */ 564 } else 565 rv = 1; /* read failure */ 566 } 567 lseek(from_fd, 0, SEEK_SET); 568 lseek(to_fd, 0, SEEK_SET); 569 } 570 } else 571 rv = 1; /* don't bother in this case */ 572 573 return rv; 574} 575 576/* 577 * copy -- 578 * copy from one file to another 579 */ 580void 581copy(from_fd, from_name, to_fd, to_name, size) 582 register int from_fd, to_fd; 583 char *from_name, *to_name; 584 off_t size; 585{ 586 register int nr, nw; 587 int serrno; 588 char *p, buf[MAXBSIZE]; 589 int done_copy; 590 591 /* 592 * Mmap and write if less than 8M (the limit is so we don't totally 593 * trash memory on big files. This is really a minor hack, but it 594 * wins some CPU back. 595 */ 596 done_copy = 0; 597 if (size <= 8 * 1048576 && trymmap(from_fd)) { 598 if ((p = mmap(NULL, (size_t)size, PROT_READ, 599 MAP_SHARED, from_fd, (off_t)0)) == (char *)MAP_FAILED) 600 goto out; 601 if ((nw = write(to_fd, p, size)) != size) { 602 serrno = errno; 603 (void)unlink(to_name); 604 errno = nw > 0 ? EIO : serrno; 605 err(EX_OSERR, "%s", to_name); 606 } 607 done_copy = 1; 608 out: 609 } 610 if (!done_copy) { 611 while ((nr = read(from_fd, buf, sizeof(buf))) > 0) 612 if ((nw = write(to_fd, buf, nr)) != nr) { 613 serrno = errno; 614 (void)unlink(to_name); 615 errno = nw > 0 ? EIO : serrno; 616 err(EX_OSERR, "%s", to_name); 617 } 618 if (nr != 0) { 619 serrno = errno; 620 (void)unlink(to_name); 621 errno = serrno; 622 err(EX_OSERR, "%s", from_name); 623 } 624 } 625} 626 627/* 628 * strip -- 629 * use strip(1) to strip the target file 630 */ 631void 632strip(to_name) 633 char *to_name; 634{ 635 int serrno, status; 636 637 switch (vfork()) { 638 case -1: 639 serrno = errno; 640 (void)unlink(to_name); 641 errno = serrno; 642 err(EX_TEMPFAIL, "fork"); 643 case 0: 644 execlp("strip", "strip", to_name, NULL); 645 err(EX_OSERR, "exec(strip)"); 646 default: 647 if (wait(&status) == -1 || status) { 648 (void)unlink(to_name); 649 exit(EX_SOFTWARE); 650 /* NOTREACHED */ 651 } 652 } 653} 654 655/* 656 * install_dir -- 657 * build directory heirarchy 658 */ 659void 660install_dir(path) 661 char *path; 662{ 663 register char *p; 664 struct stat sb; 665 int ch; 666 667 for (p = path;; ++p) 668 if (!*p || (p != path && *p == '/')) { 669 ch = *p; 670 *p = '\0'; 671 if (stat(path, &sb)) { 672 if (errno != ENOENT || mkdir(path, 0777) < 0) { 673 err(EX_OSERR, "%s", path); 674 /* NOTREACHED */ 675 } 676 } 677 if (!(*p = ch)) 678 break; 679 } 680 681 if (((gid != (gid_t)-1 || uid != (uid_t)-1) && chown(path, uid, gid)) || 682 chmod(path, mode)) { 683 warn("%s", path); 684 } 685} 686 687/* 688 * usage -- 689 * print a usage message and die 690 */ 691void 692usage() 693{ 694 (void)fprintf(stderr,"\ 695usage: install [-CcDps] [-f flags] [-g group] [-m mode] [-o owner] file1 file2\n\ 696 install [-CcDps] [-f flags] [-g group] [-m mode] [-o owner] file1 ...\n\ 697 fileN directory\n\ 698 install -d [-g group] [-m mode] [-o owner] directory ...\n"); 699 exit(EX_USAGE); 700 /* NOTREACHED */ 701} 702 703/* 704 * trymmap -- 705 * return true (1) if mmap should be tried, false (0) if not. 706 */ 707int 708trymmap(fd) 709 int fd; 710{ 711 struct statfs stfs; 712 713 if (fstatfs(fd, &stfs) < 0) 714 return 0; 715 switch(stfs.f_type) { 716 case MOUNT_UFS: /* should be safe.. */ 717 case MOUNT_CD9660: /* should be safe.. */ 718 return 1; 719 } 720 return 0; 721} 722