xinstall.c revision 18489
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/*static char sccsid[] = "From: @(#)xinstall.c 8.1 (Berkeley) 7/21/93";*/ 42static const char rcsid[] = 43 "$Id: xinstall.c,v 1.13 1996/09/24 04:15:02 imp Exp $"; 44#endif /* not lint */ 45 46/*- 47 * Todo: 48 * o for -C, compare original files except in -s case. 49 * o for -C, don't change anything if nothing needs be changed. In 50 * particular, don't toggle the immutable flags just to allow null 51 * attribute changes and don't clear the dump flag. (I think inode 52 * ctimes are not updated for null attribute changes, but this is a 53 * bug.) 54 * o independent of -C, if a copy must be made, then copy to a tmpfile, 55 * set all attributes except the immutable flags, then rename, then 56 * set the immutable flags. It's annoying that the immutable flags 57 * defeat the atomicicity of rename - it seems that there must be 58 * o a window where the target is not immutable. 59 */ 60 61#include <sys/param.h> 62#include <sys/wait.h> 63#include <sys/mman.h> 64#include <sys/stat.h> 65#include <sys/mount.h> 66 67#include <ctype.h> 68#include <err.h> 69#include <errno.h> 70#include <fcntl.h> 71#include <grp.h> 72#include <paths.h> 73#include <pwd.h> 74#include <stdio.h> 75#include <stdlib.h> 76#include <string.h> 77#include <sysexits.h> 78#include <unistd.h> 79#include <utime.h> 80 81#include "pathnames.h" 82 83int debug, docompare, docopy, dopreserve, dostrip, verbose; 84int mode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH; 85char *group, *owner, pathbuf[MAXPATHLEN]; 86char pathbuf2[MAXPATHLEN]; 87 88#define DIRECTORY 0x01 /* Tell install it's a directory. */ 89#define SETFLAGS 0x02 /* Tell install to set flags. */ 90#define NOCHANGEBITS (UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND) 91 92void copy __P((int, char *, int, char *, off_t)); 93int compare __P((int, const char *, int, const char *, 94 const struct stat *, const struct stat *)); 95void install __P((char *, char *, u_long, u_int)); 96u_long string_to_flags __P((char **, u_long *, u_long *)); 97void strip __P((char *)); 98void usage __P((void)); 99int trymmap __P((int)); 100 101#define ALLOW_NUMERIC_IDS 1 102#ifdef ALLOW_NUMERIC_IDS 103 104uid_t uid; 105gid_t gid; 106 107uid_t resolve_uid __P((char *)); 108gid_t resolve_gid __P((char *)); 109u_long numeric_id __P((char *, char *)); 110 111#else 112 113struct passwd *pp; 114struct group *gp; 115 116#endif /* ALLOW_NUMERIC_IDS */ 117 118int 119main(argc, argv) 120 int argc; 121 char *argv[]; 122{ 123 struct stat from_sb, to_sb; 124 mode_t *set; 125 u_long fset; 126 u_int iflags; 127 int ch, no_target; 128 char *flags, *to_name; 129 130 iflags = 0; 131 while ((ch = getopt(argc, argv, "CcDf:g:m:o:psv")) != EOF) 132 switch((char)ch) { 133 case 'C': 134 docompare = docopy = 1; 135 break; 136 case 'c': 137 docopy = 1; 138 break; 139 case 'D': 140 debug++; 141 break; 142 case 'f': 143 flags = optarg; 144 if (string_to_flags(&flags, &fset, NULL)) 145 errx(EX_USAGE, "%s: invalid flag", flags); 146 iflags |= SETFLAGS; 147 break; 148 case 'g': 149 group = optarg; 150 break; 151 case 'm': 152 if (!(set = setmode(optarg))) 153 errx(EX_USAGE, "invalid file mode: %s", 154 optarg); 155 mode = getmode(set, 0); 156 break; 157 case 'o': 158 owner = optarg; 159 break; 160 case 'p': 161 docompare = docopy = dopreserve = 1; 162 break; 163 case 's': 164 dostrip = 1; 165 break; 166 case 'v': 167 verbose = 1; 168 break; 169 case '?': 170 default: 171 usage(); 172 } 173 argc -= optind; 174 argv += optind; 175 if (argc < 2) 176 usage(); 177 178#ifdef ALLOW_NUMERIC_IDS 179 180 if (owner) 181 uid = resolve_uid(owner); 182 if (group) 183 gid = resolve_gid(group); 184 185#else 186 187 /* get group and owner id's */ 188 if (owner && !(pp = getpwnam(owner))) 189 errx(EX_NOUSER, "unknown user %s", owner); 190 if (group && !(gp = getgrnam(group))) 191 errx(EX_NOUSER, "unknown group %s", group); 192 193#endif /* ALLOW_NUMERIC_IDS */ 194 195 no_target = stat(to_name = argv[argc - 1], &to_sb); 196 if (!no_target && (to_sb.st_mode & S_IFMT) == S_IFDIR) { 197 for (; *argv != to_name; ++argv) 198 install(*argv, to_name, fset, iflags | DIRECTORY); 199 exit(0); 200 } 201 202 /* can't do file1 file2 directory/file */ 203 if (argc != 2) 204 usage(); 205 206 if (!no_target) { 207 if (stat(*argv, &from_sb)) 208 err(EX_OSERR, "%s", *argv); 209 if (!S_ISREG(to_sb.st_mode)) { 210 errno = EFTYPE; 211 err(EX_OSERR, "%s", to_name); 212 } 213 if (to_sb.st_dev == from_sb.st_dev && 214 to_sb.st_ino == from_sb.st_ino) 215 errx(EX_USAGE, 216 "%s and %s are the same file", *argv, to_name); 217/* 218 * XXX - It's not at all clear why this code was here, since it completely 219 * duplicates code install(). The version in install() handles the -C flag 220 * correctly, so we'll just disable this for now. 221 */ 222#if 0 223 /* 224 * Unlink now... avoid ETXTBSY errors later. Try and turn 225 * off the append/immutable bits -- if we fail, go ahead, 226 * it might work. 227 */ 228 if (to_sb.st_flags & NOCHANGEBITS) 229 (void)chflags(to_name, 230 to_sb.st_flags & ~(NOCHANGEBITS)); 231 (void)unlink(to_name); 232#endif 233 } 234 install(*argv, to_name, fset, iflags); 235 exit(0); 236} 237 238#ifdef ALLOW_NUMERIC_IDS 239 240uid_t 241resolve_uid(s) 242 char *s; 243{ 244 struct passwd *pw; 245 246 return ((pw = getpwnam(s)) == NULL) ? 247 (uid_t) numeric_id(s, "user") : pw->pw_uid; 248} 249 250gid_t 251resolve_gid(s) 252 char *s; 253{ 254 struct group *gr; 255 256 return ((gr = getgrnam(s)) == NULL) ? 257 (gid_t) numeric_id(s, "group") : gr->gr_gid; 258} 259 260u_long 261numeric_id(name, type) 262 char *name, *type; 263{ 264 u_long val; 265 char *ep; 266 267 /* 268 * XXX 269 * We know that uid_t's and gid_t's are unsigned longs. 270 */ 271 errno = 0; 272 val = strtoul(name, &ep, 10); 273 if (errno) 274 err(EX_NOUSER, "%s", name); 275 if (*ep != '\0') 276 errx(EX_NOUSER, "unknown %s %s", type, name); 277 return (val); 278} 279 280#endif /* ALLOW_NUMERIC_IDS */ 281 282/* 283 * install -- 284 * build a path name and install the file 285 */ 286void 287install(from_name, to_name, fset, flags) 288 char *from_name, *to_name; 289 u_long fset; 290 u_int flags; 291{ 292 struct stat from_sb, to_sb; 293 int devnull, from_fd, to_fd, serrno; 294 char *p, *old_to_name = 0; 295 296 if (debug >= 2 && !docompare) 297 fprintf(stderr, "install: invoked without -C for %s to %s\n", 298 from_name, to_name); 299 300 /* If try to install NULL file to a directory, fails. */ 301 if (flags & DIRECTORY || strcmp(from_name, _PATH_DEVNULL)) { 302 if (stat(from_name, &from_sb)) 303 err(EX_OSERR, "%s", from_name); 304 if (!S_ISREG(from_sb.st_mode)) { 305 errno = EFTYPE; 306 err(EX_OSERR, "%s", from_name); 307 } 308 /* Build the target path. */ 309 if (flags & DIRECTORY) { 310 (void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s", 311 to_name, 312 (p = strrchr(from_name, '/')) ? ++p : from_name); 313 to_name = pathbuf; 314 } 315 devnull = 0; 316 } else { 317 from_sb.st_flags = 0; /* XXX */ 318 devnull = 1; 319 } 320 321 if (docompare) { 322 old_to_name = to_name; 323 /* 324 * Make a new temporary file in the same file system 325 * (actually, in in the same directory) as the target so 326 * that the temporary file can be renamed to the target. 327 */ 328 snprintf(pathbuf2, sizeof pathbuf2, "%s", to_name); 329 p = strrchr(pathbuf2, '/'); 330 p = (p == NULL ? pathbuf2 : p + 1); 331 snprintf(p, &pathbuf2[sizeof pathbuf2] - p, "INS@XXXX"); 332 to_fd = mkstemp(pathbuf2); 333 if (to_fd < 0) 334 /* XXX should fall back to not comparing. */ 335 err(EX_OSERR, "mkstemp: %s for %s", pathbuf2, to_name); 336 to_name = pathbuf2; 337 } else { 338 /* 339 * Unlink now... avoid errors later. Try to turn off the 340 * append/immutable bits -- if we fail, go ahead, it might 341 * work. 342 */ 343 if (stat(to_name, &to_sb) == 0 && to_sb.st_flags & NOCHANGEBITS) 344 (void)chflags(to_name, to_sb.st_flags & ~NOCHANGEBITS); 345 unlink(to_name); 346 347 /* Create target. */ 348 to_fd = open(to_name, 349 O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR); 350 if (to_fd < 0) 351 err(EX_OSERR, "%s", to_name); 352 } 353 354 if (!devnull) { 355 if ((from_fd = open(from_name, O_RDONLY, 0)) < 0) { 356 serrno = errno; 357 (void)unlink(to_name); 358 errno = serrno; 359 err(EX_OSERR, "%s", from_name); 360 } 361 copy(from_fd, from_name, to_fd, to_name, from_sb.st_size); 362 (void)close(from_fd); 363 } 364 365 if (dostrip) 366 strip(to_name); 367 368 /* 369 * Unfortunately, because we strip the installed file and not the 370 * original one, it is impossible to do the comparison without 371 * first laboriously copying things over and then comparing. 372 * It may be possible to better optimize the !dostrip case, however. 373 * For further study. 374 */ 375 if (docompare) { 376 struct stat old_sb, new_sb, timestamp_sb; 377 int old_fd; 378 struct utimbuf utb; 379 380 old_fd = open(old_to_name, O_RDONLY, 0); 381 if (old_fd < 0 && errno == ENOENT) 382 goto different; 383 if (old_fd < 0) 384 err(EX_OSERR, "%s", old_to_name); 385 fstat(old_fd, &old_sb); 386 if (old_sb.st_flags & NOCHANGEBITS) 387 (void)fchflags(old_fd, old_sb.st_flags & ~NOCHANGEBITS); 388 fstat(to_fd, &new_sb); 389 if (compare(old_fd, old_to_name, to_fd, to_name, &old_sb, 390 &new_sb)) { 391different: 392 if (debug != 0) 393 fprintf(stderr, 394 "install: renaming for %s: %s to %s\n", 395 from_name, to_name, old_to_name); 396 if (dopreserve && stat(from_name, ×tamp_sb) == 0) { 397 utb.actime = from_sb.st_atime; 398 utb.modtime = from_sb.st_mtime; 399 (void)utime(to_name, &utb); 400 } 401moveit: 402 if (verbose) { 403 printf("install: %s -> %s\n", 404 from_name, old_to_name); 405 } 406 if (rename(to_name, old_to_name) < 0) { 407 serrno = errno; 408 unlink(to_name); 409 unlink(old_to_name); 410 errno = serrno; 411 err(EX_OSERR, "rename: %s to %s", to_name, 412 old_to_name); 413 } 414 close(old_fd); 415 } else { 416 if (old_sb.st_nlink != 1) { 417 /* 418 * Replace the target, although it hasn't 419 * changed, to snap the extra links. But 420 * preserve the target file times. 421 */ 422 if (fstat(old_fd, ×tamp_sb) == 0) { 423 utb.actime = timestamp_sb.st_atime; 424 utb.modtime = timestamp_sb.st_mtime; 425 (void)utime(to_name, &utb); 426 } 427 goto moveit; 428 } 429 if (unlink(to_name) < 0) 430 err(EX_OSERR, "unlink: %s", to_name); 431 close(to_fd); 432 to_fd = old_fd; 433 } 434 to_name = old_to_name; 435 } 436 437 /* 438 * Set owner, group, mode for target; do the chown first, 439 * chown may lose the setuid bits. 440 */ 441 if ((group || owner) && 442#ifdef ALLOW_NUMERIC_IDS 443 fchown(to_fd, owner ? uid : -1, group ? gid : -1)) { 444#else 445 fchown(to_fd, owner ? pp->pw_uid : -1, group ? gp->gr_gid : -1)) { 446#endif 447 serrno = errno; 448 (void)unlink(to_name); 449 errno = serrno; 450 err(EX_OSERR,"%s: chown/chgrp", to_name); 451 } 452 if (fchmod(to_fd, mode)) { 453 serrno = errno; 454 (void)unlink(to_name); 455 errno = serrno; 456 err(EX_OSERR, "%s: chmod", to_name); 457 } 458 459 /* 460 * If provided a set of flags, set them, otherwise, preserve the 461 * flags, except for the dump flag. 462 */ 463 if (fchflags(to_fd, 464 flags & SETFLAGS ? fset : from_sb.st_flags & ~UF_NODUMP)) { 465 serrno = errno; 466 (void)unlink(to_name); 467 errno = serrno; 468 err(EX_OSERR, "%s: chflags", to_name); 469 } 470 471 (void)close(to_fd); 472 if (!docopy && !devnull && unlink(from_name)) 473 err(EX_OSERR, "%s", from_name); 474} 475 476/* 477 * compare -- 478 * compare two files; non-zero means files differ 479 */ 480int 481compare(int from_fd, const char *from_name, int to_fd, const char *to_name, 482 const struct stat *from_sb, const struct stat *to_sb) 483{ 484 char *p, *q; 485 int rv; 486 size_t tsize; 487 int done_compare; 488 489 if (from_sb->st_size != to_sb->st_size) 490 return 1; 491 492 tsize = (size_t)from_sb->st_size; 493 494 if (tsize <= 8 * 1024 * 1024) { 495 done_compare = 0; 496 if (trymmap(from_fd) && trymmap(to_fd)) { 497 p = mmap(NULL, tsize, PROT_READ, 0, from_fd, (off_t)0); 498 if ((long)p == -1) 499 goto out; 500 q = mmap(NULL, tsize, PROT_READ, 0, to_fd, (off_t)0); 501 if ((long)q == -1) { 502 munmap(p, tsize); 503 goto out; 504 } 505 506 rv = memcmp(p, q, tsize); 507 munmap(p, tsize); 508 munmap(q, tsize); 509 done_compare = 1; 510 } 511 out: 512 if (!done_compare) { 513 char buf1[MAXBSIZE]; 514 char buf2[MAXBSIZE]; 515 int n1, n2; 516 517 rv = 0; 518 lseek(from_fd, 0, SEEK_SET); 519 lseek(to_fd, 0, SEEK_SET); 520 while (rv == 0) { 521 n1 = read(from_fd, buf1, sizeof(buf1)); 522 if (n1 == 0) 523 break; /* EOF */ 524 else if (n1 > 0) { 525 n2 = read(to_fd, buf2, n1); 526 if (n2 == n1) 527 rv = memcmp(buf1, buf2, n1); 528 else 529 rv = 1; /* out of sync */ 530 } else 531 rv = 1; /* read failure */ 532 } 533 lseek(from_fd, 0, SEEK_SET); 534 lseek(to_fd, 0, SEEK_SET); 535 } 536 } else 537 rv = 1; /* don't bother in this case */ 538 539 return rv; 540} 541 542/* 543 * copy -- 544 * copy from one file to another 545 */ 546void 547copy(from_fd, from_name, to_fd, to_name, size) 548 register int from_fd, to_fd; 549 char *from_name, *to_name; 550 off_t size; 551{ 552 register int nr, nw; 553 int serrno; 554 char *p, buf[MAXBSIZE]; 555 int done_copy; 556 557 /* 558 * Mmap and write if less than 8M (the limit is so we don't totally 559 * trash memory on big files. This is really a minor hack, but it 560 * wins some CPU back. 561 */ 562 done_copy = 0; 563 if (size <= 8 * 1048576 && trymmap(from_fd)) { 564 if ((p = mmap(NULL, (size_t)size, PROT_READ, 565 0, from_fd, (off_t)0)) == (char *)-1) 566 goto out; 567 if ((nw = write(to_fd, p, size)) != size) { 568 serrno = errno; 569 (void)unlink(to_name); 570 errno = nw > 0 ? EIO : serrno; 571 err(EX_OSERR, "%s", to_name); 572 } 573 done_copy = 1; 574 out: 575 } 576 if (!done_copy) { 577 while ((nr = read(from_fd, buf, sizeof(buf))) > 0) 578 if ((nw = write(to_fd, buf, nr)) != nr) { 579 serrno = errno; 580 (void)unlink(to_name); 581 errno = nw > 0 ? EIO : serrno; 582 err(EX_OSERR, "%s", to_name); 583 } 584 if (nr != 0) { 585 serrno = errno; 586 (void)unlink(to_name); 587 errno = serrno; 588 err(EX_OSERR, "%s", from_name); 589 } 590 } 591} 592 593/* 594 * strip -- 595 * use strip(1) to strip the target file 596 */ 597void 598strip(to_name) 599 char *to_name; 600{ 601 int serrno, status; 602 603 switch (vfork()) { 604 case -1: 605 serrno = errno; 606 (void)unlink(to_name); 607 errno = serrno; 608 err(EX_TEMPFAIL, "fork"); 609 case 0: 610 execlp("strip", "strip", to_name, NULL); 611 err(EX_OSERR, "exec(strip)"); 612 default: 613 if (wait(&status) == -1 || status) { 614 (void)unlink(to_name); 615 exit(EX_SOFTWARE); 616 } 617 } 618} 619 620/* 621 * usage -- 622 * print a usage message and die 623 */ 624void 625usage() 626{ 627 (void)fprintf(stderr, 628"usage: install [-CcDps] [-f flags] [-g group] [-m mode] [-o owner] file1 file2;\n\tor file1 ... fileN directory\n"); 629 exit(1); 630} 631 632/* 633 * trymmap -- 634 * return true (1) if mmap should be tried, false (0) if not. 635 */ 636int 637trymmap(fd) 638 int fd; 639{ 640 struct statfs stfs; 641 642 if (fstatfs(fd, &stfs) < 0) 643 return 0; 644 switch(stfs.f_type) { 645 case MOUNT_UFS: /* should be safe.. */ 646 case MOUNT_CD9660: /* should be safe.. */ 647 return 1; 648 } 649 return 0; 650} 651