xinstall.c revision 18525
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.14 1996/09/24 17:29:42 bde 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 <unistd.h> 78#include <sysexits.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(EX_OK); 200 /* NOTREACHED */ 201 } 202 203 /* can't do file1 file2 directory/file */ 204 if (argc != 2) 205 usage(); 206 207 if (!no_target) { 208 if (stat(*argv, &from_sb)) 209 err(EX_OSERR, "%s", *argv); 210 if (!S_ISREG(to_sb.st_mode)) { 211 errno = EFTYPE; 212 err(EX_OSERR, "%s", to_name); 213 } 214 if (to_sb.st_dev == from_sb.st_dev && 215 to_sb.st_ino == from_sb.st_ino) 216 errx(EX_USAGE, 217 "%s and %s are the same file", *argv, to_name); 218/* 219 * XXX - It's not at all clear why this code was here, since it completely 220 * duplicates code install(). The version in install() handles the -C flag 221 * correctly, so we'll just disable this for now. 222 */ 223#if 0 224 /* 225 * Unlink now... avoid ETXTBSY errors later. Try and turn 226 * off the append/immutable bits -- if we fail, go ahead, 227 * it might work. 228 */ 229 if (to_sb.st_flags & NOCHANGEBITS) 230 (void)chflags(to_name, 231 to_sb.st_flags & ~(NOCHANGEBITS)); 232 (void)unlink(to_name); 233#endif 234 } 235 install(*argv, to_name, fset, iflags); 236 exit(EX_OK); 237 /* NOTREACHED */ 238} 239 240#ifdef ALLOW_NUMERIC_IDS 241 242uid_t 243resolve_uid(s) 244 char *s; 245{ 246 struct passwd *pw; 247 248 return ((pw = getpwnam(s)) == NULL) ? 249 (uid_t) numeric_id(s, "user") : pw->pw_uid; 250} 251 252gid_t 253resolve_gid(s) 254 char *s; 255{ 256 struct group *gr; 257 258 return ((gr = getgrnam(s)) == NULL) ? 259 (gid_t) numeric_id(s, "group") : gr->gr_gid; 260} 261 262u_long 263numeric_id(name, type) 264 char *name, *type; 265{ 266 u_long val; 267 char *ep; 268 269 /* 270 * XXX 271 * We know that uid_t's and gid_t's are unsigned longs. 272 */ 273 errno = 0; 274 val = strtoul(name, &ep, 10); 275 if (errno) 276 err(EX_NOUSER, "%s", name); 277 if (*ep != '\0') 278 errx(EX_NOUSER, "unknown %s %s", type, name); 279 return (val); 280} 281 282#endif /* ALLOW_NUMERIC_IDS */ 283 284/* 285 * install -- 286 * build a path name and install the file 287 */ 288void 289install(from_name, to_name, fset, flags) 290 char *from_name, *to_name; 291 u_long fset; 292 u_int flags; 293{ 294 struct stat from_sb, to_sb; 295 int devnull, from_fd, to_fd, serrno; 296 char *p, *old_to_name = 0; 297 298 if (debug >= 2 && !docompare) 299 fprintf(stderr, "install: invoked without -C for %s to %s\n", 300 from_name, to_name); 301 302 /* If try to install NULL file to a directory, fails. */ 303 if (flags & DIRECTORY || strcmp(from_name, _PATH_DEVNULL)) { 304 if (stat(from_name, &from_sb)) 305 err(EX_OSERR, "%s", from_name); 306 if (!S_ISREG(from_sb.st_mode)) { 307 errno = EFTYPE; 308 err(EX_OSERR, "%s", from_name); 309 } 310 /* Build the target path. */ 311 if (flags & DIRECTORY) { 312 (void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s", 313 to_name, 314 (p = strrchr(from_name, '/')) ? ++p : from_name); 315 to_name = pathbuf; 316 } 317 devnull = 0; 318 } else { 319 from_sb.st_flags = 0; /* XXX */ 320 devnull = 1; 321 } 322 323 if (docompare) { 324 old_to_name = to_name; 325 /* 326 * Make a new temporary file in the same file system 327 * (actually, in in the same directory) as the target so 328 * that the temporary file can be renamed to the target. 329 */ 330 snprintf(pathbuf2, sizeof pathbuf2, "%s", to_name); 331 p = strrchr(pathbuf2, '/'); 332 p = (p == NULL ? pathbuf2 : p + 1); 333 snprintf(p, &pathbuf2[sizeof pathbuf2] - p, "INS@XXXX"); 334 to_fd = mkstemp(pathbuf2); 335 if (to_fd < 0) 336 /* XXX should fall back to not comparing. */ 337 err(EX_OSERR, "mkstemp: %s for %s", pathbuf2, to_name); 338 to_name = pathbuf2; 339 } else { 340 /* 341 * Unlink now... avoid errors later. Try to turn off the 342 * append/immutable bits -- if we fail, go ahead, it might 343 * work. 344 */ 345 if (stat(to_name, &to_sb) == 0 && to_sb.st_flags & NOCHANGEBITS) 346 (void)chflags(to_name, to_sb.st_flags & ~NOCHANGEBITS); 347 unlink(to_name); 348 349 /* Create target. */ 350 to_fd = open(to_name, 351 O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR); 352 if (to_fd < 0) 353 err(EX_OSERR, "%s", to_name); 354 } 355 356 if (!devnull) { 357 if ((from_fd = open(from_name, O_RDONLY, 0)) < 0) { 358 serrno = errno; 359 (void)unlink(to_name); 360 errno = serrno; 361 err(EX_OSERR, "%s", from_name); 362 } 363 copy(from_fd, from_name, to_fd, to_name, from_sb.st_size); 364 (void)close(from_fd); 365 } 366 367 if (dostrip) 368 strip(to_name); 369 370 /* 371 * Unfortunately, because we strip the installed file and not the 372 * original one, it is impossible to do the comparison without 373 * first laboriously copying things over and then comparing. 374 * It may be possible to better optimize the !dostrip case, however. 375 * For further study. 376 */ 377 if (docompare) { 378 struct stat old_sb, new_sb, timestamp_sb; 379 int old_fd; 380 struct utimbuf utb; 381 382 old_fd = open(old_to_name, O_RDONLY, 0); 383 if (old_fd < 0 && errno == ENOENT) 384 goto different; 385 if (old_fd < 0) 386 err(EX_OSERR, "%s", old_to_name); 387 fstat(old_fd, &old_sb); 388 if (old_sb.st_flags & NOCHANGEBITS) 389 (void)fchflags(old_fd, old_sb.st_flags & ~NOCHANGEBITS); 390 fstat(to_fd, &new_sb); 391 if (compare(old_fd, old_to_name, to_fd, to_name, &old_sb, 392 &new_sb)) { 393different: 394 if (debug != 0) 395 fprintf(stderr, 396 "install: renaming for %s: %s to %s\n", 397 from_name, to_name, old_to_name); 398 if (dopreserve && stat(from_name, ×tamp_sb) == 0) { 399 utb.actime = from_sb.st_atime; 400 utb.modtime = from_sb.st_mtime; 401 (void)utime(to_name, &utb); 402 } 403moveit: 404 if (verbose) { 405 printf("install: %s -> %s\n", 406 from_name, old_to_name); 407 } 408 if (rename(to_name, old_to_name) < 0) { 409 serrno = errno; 410 unlink(to_name); 411 unlink(old_to_name); 412 errno = serrno; 413 err(EX_OSERR, "rename: %s to %s", to_name, 414 old_to_name); 415 } 416 close(old_fd); 417 } else { 418 if (old_sb.st_nlink != 1) { 419 /* 420 * Replace the target, although it hasn't 421 * changed, to snap the extra links. But 422 * preserve the target file times. 423 */ 424 if (fstat(old_fd, ×tamp_sb) == 0) { 425 utb.actime = timestamp_sb.st_atime; 426 utb.modtime = timestamp_sb.st_mtime; 427 (void)utime(to_name, &utb); 428 } 429 goto moveit; 430 } 431 if (unlink(to_name) < 0) 432 err(EX_OSERR, "unlink: %s", to_name); 433 close(to_fd); 434 to_fd = old_fd; 435 } 436 to_name = old_to_name; 437 } 438 439 /* 440 * Set owner, group, mode for target; do the chown first, 441 * chown may lose the setuid bits. 442 */ 443 if ((group || owner) && 444#ifdef ALLOW_NUMERIC_IDS 445 fchown(to_fd, owner ? uid : -1, group ? gid : -1)) { 446#else 447 fchown(to_fd, owner ? pp->pw_uid : -1, group ? gp->gr_gid : -1)) { 448#endif 449 serrno = errno; 450 (void)unlink(to_name); 451 errno = serrno; 452 err(EX_OSERR,"%s: chown/chgrp", to_name); 453 } 454 if (fchmod(to_fd, mode)) { 455 serrno = errno; 456 (void)unlink(to_name); 457 errno = serrno; 458 err(EX_OSERR, "%s: chmod", to_name); 459 } 460 461 /* 462 * If provided a set of flags, set them, otherwise, preserve the 463 * flags, except for the dump flag. 464 */ 465 if (fchflags(to_fd, 466 flags & SETFLAGS ? fset : from_sb.st_flags & ~UF_NODUMP)) { 467 serrno = errno; 468 (void)unlink(to_name); 469 errno = serrno; 470 err(EX_OSERR, "%s: chflags", to_name); 471 } 472 473 (void)close(to_fd); 474 if (!docopy && !devnull && unlink(from_name)) 475 err(EX_OSERR, "%s", from_name); 476} 477 478/* 479 * compare -- 480 * compare two files; non-zero means files differ 481 */ 482int 483compare(int from_fd, const char *from_name, int to_fd, const char *to_name, 484 const struct stat *from_sb, const struct stat *to_sb) 485{ 486 char *p, *q; 487 int rv; 488 size_t tsize; 489 int done_compare; 490 491 if (from_sb->st_size != to_sb->st_size) 492 return 1; 493 494 tsize = (size_t)from_sb->st_size; 495 496 if (tsize <= 8 * 1024 * 1024) { 497 done_compare = 0; 498 if (trymmap(from_fd) && trymmap(to_fd)) { 499 p = mmap(NULL, tsize, PROT_READ, 0, from_fd, (off_t)0); 500 if ((long)p == -1) 501 goto out; 502 q = mmap(NULL, tsize, PROT_READ, 0, to_fd, (off_t)0); 503 if ((long)q == -1) { 504 munmap(p, tsize); 505 goto out; 506 } 507 508 rv = memcmp(p, q, tsize); 509 munmap(p, tsize); 510 munmap(q, tsize); 511 done_compare = 1; 512 } 513 out: 514 if (!done_compare) { 515 char buf1[MAXBSIZE]; 516 char buf2[MAXBSIZE]; 517 int n1, n2; 518 519 rv = 0; 520 lseek(from_fd, 0, SEEK_SET); 521 lseek(to_fd, 0, SEEK_SET); 522 while (rv == 0) { 523 n1 = read(from_fd, buf1, sizeof(buf1)); 524 if (n1 == 0) 525 break; /* EOF */ 526 else if (n1 > 0) { 527 n2 = read(to_fd, buf2, n1); 528 if (n2 == n1) 529 rv = memcmp(buf1, buf2, n1); 530 else 531 rv = 1; /* out of sync */ 532 } else 533 rv = 1; /* read failure */ 534 } 535 lseek(from_fd, 0, SEEK_SET); 536 lseek(to_fd, 0, SEEK_SET); 537 } 538 } else 539 rv = 1; /* don't bother in this case */ 540 541 return rv; 542} 543 544/* 545 * copy -- 546 * copy from one file to another 547 */ 548void 549copy(from_fd, from_name, to_fd, to_name, size) 550 register int from_fd, to_fd; 551 char *from_name, *to_name; 552 off_t size; 553{ 554 register int nr, nw; 555 int serrno; 556 char *p, buf[MAXBSIZE]; 557 int done_copy; 558 559 /* 560 * Mmap and write if less than 8M (the limit is so we don't totally 561 * trash memory on big files. This is really a minor hack, but it 562 * wins some CPU back. 563 */ 564 done_copy = 0; 565 if (size <= 8 * 1048576 && trymmap(from_fd)) { 566 if ((p = mmap(NULL, (size_t)size, PROT_READ, 567 0, from_fd, (off_t)0)) == (char *)-1) 568 goto out; 569 if ((nw = write(to_fd, p, size)) != size) { 570 serrno = errno; 571 (void)unlink(to_name); 572 errno = nw > 0 ? EIO : serrno; 573 err(EX_OSERR, "%s", to_name); 574 } 575 done_copy = 1; 576 out: 577 } 578 if (!done_copy) { 579 while ((nr = read(from_fd, buf, sizeof(buf))) > 0) 580 if ((nw = write(to_fd, buf, nr)) != nr) { 581 serrno = errno; 582 (void)unlink(to_name); 583 errno = nw > 0 ? EIO : serrno; 584 err(EX_OSERR, "%s", to_name); 585 } 586 if (nr != 0) { 587 serrno = errno; 588 (void)unlink(to_name); 589 errno = serrno; 590 err(EX_OSERR, "%s", from_name); 591 } 592 } 593} 594 595/* 596 * strip -- 597 * use strip(1) to strip the target file 598 */ 599void 600strip(to_name) 601 char *to_name; 602{ 603 int serrno, status; 604 605 switch (vfork()) { 606 case -1: 607 serrno = errno; 608 (void)unlink(to_name); 609 errno = serrno; 610 err(EX_TEMPFAIL, "fork"); 611 case 0: 612 execlp("strip", "strip", to_name, NULL); 613 err(EX_OSERR, "exec(strip)"); 614 default: 615 if (wait(&status) == -1 || status) { 616 (void)unlink(to_name); 617 exit(EX_SOFTWARE); 618 /* NOTREACHED */ 619 } 620 } 621} 622 623/* 624 * usage -- 625 * print a usage message and die 626 */ 627void 628usage() 629{ 630 (void)fprintf(stderr, 631"usage: install [-CcDps] [-f flags] [-g group] [-m mode] [-o owner] file1 file2;\n\tor file1 ... fileN directory\n"); 632 exit(EX_USAGE); 633 /* NOTREACHED */ 634} 635 636/* 637 * trymmap -- 638 * return true (1) if mmap should be tried, false (0) if not. 639 */ 640int 641trymmap(fd) 642 int fd; 643{ 644 struct statfs stfs; 645 646 if (fstatfs(fd, &stfs) < 0) 647 return 0; 648 switch(stfs.f_type) { 649 case MOUNT_UFS: /* should be safe.. */ 650 case MOUNT_CD9660: /* should be safe.. */ 651 return 1; 652 } 653 return 0; 654} 655