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