Index: usr.bin/xinstall/xinstall.c =================================================================== --- usr.bin/xinstall/xinstall.c (revision 245428) +++ usr.bin/xinstall/xinstall.c (working copy) @@ -1,4 +1,5 @@ /* + * Copyright (c) 2012, 2013 SRI International * Copyright (c) 1987, 1993 * The Regents of the University of California. All rights reserved. * @@ -53,14 +54,21 @@ #include #include #include +#include +#include #include #include +#include +#include +#include +#include #include #include #include #include #include #include +#include #include "mtree.h" @@ -71,24 +79,62 @@ #define MAX_CMP_SIZE (16 * 1024 * 1024) +#define LN_ABSOLUTE 0x01 +#define LN_RELATIVE 0x02 +#define LN_HARD 0x04 +#define LN_SYMBOLIC 0x08 +#define LN_MIXED 0x10 + #define DIRECTORY 0x01 /* Tell install it's a directory. */ #define SETFLAGS 0x02 /* Tell install to set flags. */ #define NOCHANGEBITS (UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND) #define BACKUP_SUFFIX ".old" +typedef union { + MD5_CTX MD5; + RIPEMD160_CTX RIPEMD160; + SHA1_CTX SHA1; + SHA256_CTX SHA256; + SHA512_CTX SHA512; +} DIGEST_CTX; + static gid_t gid; static uid_t uid; -static int dobackup, docompare, dodir, dopreserve, dostrip, nommap, safecopy, - verbose; +static int dobackup, docompare, dodir, dolink, dopreserve, dostrip, dounpriv, + safecopy, verbose; +static int haveopt_f, haveopt_g, haveopt_m, haveopt_o; static mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; +static FILE *metafp; +static const char *group, *owner; static const char *suffix = BACKUP_SUFFIX; +static char *destdir, *digest, *fflags, *metafile, *tags; -static int compare(int, const char *, size_t, int, const char *, size_t); -static void copy(int, const char *, int, const char *, off_t); +enum { + DIGEST_NONE = 0, + DIGEST_MD5, + DIGEST_RIPEMD160, + DIGEST_SHA1, + DIGEST_SHA256, + DIGEST_SHA512, +} digesttype = DIGEST_NONE; + +static int compare(int, const char *, size_t, int, const char *, size_t, + char **); +static char *copy(int, const char *, int, const char *, off_t); static int create_newfile(const char *, int, struct stat *); static int create_tempfile(const char *, char *, size_t); +static char *quiet_mktemp(char *template); +static char *digest_file(const char *); +static void digest_init(DIGEST_CTX *); +static void digest_update(DIGEST_CTX *, const unsigned char *, size_t); +static char *digest_end(DIGEST_CTX *, char *); +static int do_link(const char *, const char *, const struct stat *); +static void do_symlink(const char *, const char *, const struct stat *); +static void makelink(const char *, const char *, const struct stat *); static void install(const char *, const char *, u_long, u_int); static void install_dir(char *); +static void metadata_log(const char *, const char *, struct timeval *, + const char *, const char *, off_t); static int parseid(const char *, id_t *); static void strip(const char *); static int trymmap(int); @@ -102,12 +148,13 @@ u_long fset; int ch, no_target; u_int iflags; - char *flags; - const char *group, *owner, *to_name; + char *p; + const char *to_name; iflags = 0; group = owner = NULL; - while ((ch = getopt(argc, argv, "B:bCcdf:g:Mm:N:o:pSsv")) != -1) + while ((ch = getopt(argc, argv, "B:bCcD:df:g:h:l:M:m:N:o:pSsT:Uv")) != + -1) switch((char)ch) { case 'B': suffix = optarg; @@ -121,22 +168,56 @@ case 'c': /* For backwards compatibility. */ break; + case 'D': + destdir = optarg; + break; case 'd': dodir = 1; break; case 'f': - flags = optarg; - if (strtofflags(&flags, &fset, NULL)) - errx(EX_USAGE, "%s: invalid flag", flags); - iflags |= SETFLAGS; + haveopt_f = 1; + fflags = optarg; break; case 'g': + haveopt_g = 1; group = optarg; break; + case 'h': + digest = optarg; + break; + case 'l': + for (p = optarg; *p; p++) + switch (*p) { + case 's': + dolink &= ~(LN_HARD|LN_MIXED); + dolink |= LN_SYMBOLIC; + break; + case 'h': + dolink &= ~(LN_SYMBOLIC|LN_MIXED); + dolink |= LN_HARD; + break; + case 'm': + dolink &= ~(LN_SYMBOLIC|LN_HARD); + dolink |= LN_MIXED; + break; + case 'a': + dolink &= ~LN_RELATIVE; + dolink |= LN_ABSOLUTE; + break; + case 'r': + dolink &= ~LN_ABSOLUTE; + dolink |= LN_RELATIVE; + break; + default: + errx(1, "%c: invalid link type", *p); + /* NOTREACHED */ + } + break; case 'M': - nommap = 1; + metafile = optarg; break; case 'm': + haveopt_m = 1; if (!(set = setmode(optarg))) errx(EX_USAGE, "invalid file mode: %s", optarg); @@ -145,10 +226,11 @@ break; case 'N': if (!setup_getid(optarg)) - err(1, "Unable to use user and group " + err(EX_OSERR, "Unable to use user and group " "databases in `%s'", optarg); break; case 'o': + haveopt_o = 1; owner = optarg; break; case 'p': @@ -160,6 +242,12 @@ case 's': dostrip = 1; break; + case 'T': + tags = optarg; + break; + case 'U': + dounpriv = 1; + break; case 'v': verbose = 1; break; @@ -185,14 +273,34 @@ if (argc == 0 || (argc == 1 && !dodir)) usage(); + if (digest != NULL) { + if (0) { + } else if (strcmp(digest, "none") == 0) { + digesttype = DIGEST_NONE; + } else if (strcmp(digest, "md5") == 0) { + digesttype = DIGEST_MD5; + } else if (strcmp(digest, "rmd160") == 0) { + digesttype = DIGEST_RIPEMD160; + } else if (strcmp(digest, "sha1") == 0) { + digesttype = DIGEST_SHA1; + } else if (strcmp(digest, "sha256") == 0) { + digesttype = DIGEST_SHA256; + } else if (strcmp(digest, "sha512") == 0) { + digesttype = DIGEST_SHA512; + } else { + warnx("unknown digest `%s'", digest); + usage(); + } + } + /* need to make a temp copy so we can compare stripped version */ if (docompare && dostrip) safecopy = 1; /* get group and owner id's */ - if (group != NULL) { + if (group != NULL && !dounpriv) { if (gid_from_group(group, &gid) == -1) { - id_t id; + id_t id; if (!parseid(group, &id)) errx(1, "unknown group %s", group); gid = id; @@ -200,7 +308,7 @@ } else gid = (gid_t)-1; - if (owner != NULL) { + if (owner != NULL && !dounpriv) { if (uid_from_user(owner, &uid) == -1) { id_t id; if (!parseid(owner, &id)) @@ -210,6 +318,18 @@ } else uid = (uid_t)-1; + if (fflags != NULL && !dounpriv) { + if (strtofflags(&fflags, &fset, NULL)) + errx(EX_USAGE, "%s: invalid flag", fflags); + iflags |= SETFLAGS; + } + + if (metafile != NULL) { + if ((metafp = fopen(metafile, "a")) == NULL) + warn("open %s", metafile); + } else + digesttype = DIGEST_NONE; + if (dodir) { for (; *argv != NULL; ++argv) install_dir(*argv); @@ -236,7 +356,7 @@ usage(); } - if (!no_target) { + if (!no_target && !dolink) { if (stat(*argv, &from_sb)) err(EX_OSERR, "%s", *argv); if (!S_ISREG(to_sb.st_mode)) { @@ -253,6 +373,97 @@ /* NOTREACHED */ } +static char * +digest_file(const char *name) +{ + + switch (digesttype) { + case DIGEST_MD5: + return (MD5File(name, NULL)); + case DIGEST_RIPEMD160: + return (RIPEMD160_File(name, NULL)); + case DIGEST_SHA1: + return (SHA1_File(name, NULL)); + case DIGEST_SHA256: + return (SHA256_File(name, NULL)); + case DIGEST_SHA512: + return (SHA512_File(name, NULL)); + default: + return (NULL); + } +} + +static void +digest_init(DIGEST_CTX *c) +{ + + switch (digesttype) { + case DIGEST_NONE: + break; + case DIGEST_MD5: + MD5Init(&(c->MD5)); + break; + case DIGEST_RIPEMD160: + RIPEMD160_Init(&(c->RIPEMD160)); + break; + case DIGEST_SHA1: + SHA1_Init(&(c->SHA1)); + break; + case DIGEST_SHA256: + SHA256_Init(&(c->SHA256)); + break; + case DIGEST_SHA512: + SHA512_Init(&(c->SHA512)); + break; + + } +} + +static void +digest_update(DIGEST_CTX *c, const unsigned char *data, size_t len) +{ + + switch (digesttype) { + case DIGEST_NONE: + break; + case DIGEST_MD5: + MD5Update(&(c->MD5), data, len); + break; + case DIGEST_RIPEMD160: + RIPEMD160_Update(&(c->RIPEMD160), data, len); + break; + case DIGEST_SHA1: + SHA1_Update(&(c->SHA1), data, len); + break; + case DIGEST_SHA256: + SHA256_Update(&(c->SHA256), data, len); + break; + case DIGEST_SHA512: + SHA512_Update(&(c->SHA512), data, len); + break; + } +} + +static char * +digest_end(DIGEST_CTX *c, char *buf) +{ + + switch (digesttype) { + case DIGEST_MD5: + return (MD5End(&(c->MD5), buf)); + case DIGEST_RIPEMD160: + return (RIPEMD160_End(&(c->RIPEMD160), buf)); + case DIGEST_SHA1: + return (SHA1_End(&(c->SHA1), buf)); + case DIGEST_SHA256: + return (SHA256_End(&(c->SHA256), buf)); + case DIGEST_SHA512: + return (SHA512_End(&(c->SHA512), buf)); + default: + return (NULL); + } +} + /* * parseid -- * parse uid or gid from arg into id, returning non-zero if successful @@ -269,6 +480,223 @@ } /* + * quiet_mktemp -- + * mktemp implementation used mkstemp to avoid mktemp warnings. We + * really do need mktemp semantics here as we will be creating a link. + */ +static char * +quiet_mktemp(char *template) +{ + int fd; + + if ((fd = mkstemp(template)) == -1) + return (NULL); + close (fd); + if (unlink(template) == -1) + err(EX_OSERR, "unlink %s", template); + return (template); +} + +/* + * do_link -- + * make a hard link, obeying dorename if set + * return -1 on failure + */ +static int +do_link(const char *from_name, const char *to_name, + const struct stat *target_sb) +{ + char tmpl[MAXPATHLEN]; + int ret; + + if (safecopy && target_sb != NULL) { + (void)snprintf(tmpl, sizeof(tmpl), "%s.inst.XXXXXX", to_name); + /* This usage is safe. */ + if (quiet_mktemp(tmpl) == NULL) + err(EX_OSERR, "%s: mktemp", tmpl); + ret = link(from_name, tmpl); + if (ret == 0) { + if (target_sb->st_mode & S_IFDIR && rmdir(to_name) == + -1) { + unlink(tmpl); + err(EX_OSERR, "%s", to_name); + } + if (target_sb->st_flags & NOCHANGEBITS) + (void)chflags(to_name, target_sb->st_flags & + ~NOCHANGEBITS); + unlink(to_name); + ret = rename(tmpl, to_name); + /* If rename has posix semantics, then the temporary + * file may still exist when from_name and to_name point + * to the same file, so unlink it unconditionally. + */ + (void)unlink(tmpl); + } + return (ret); + } else + return (link(from_name, to_name)); +} + +/* + * do_symlink -- + * make a symbolic link, obeying dorename if set + * exit on failure + */ +static void +do_symlink(const char *from_name, const char *to_name, + const struct stat *target_sb) +{ + char tmpl[MAXPATHLEN]; + + if (safecopy && target_sb != NULL) { + (void)snprintf(tmpl, sizeof(tmpl), "%s.inst.XXXXXX", to_name); + /* This usage is safe. */ + if (quiet_mktemp(tmpl) == NULL) + err(EX_OSERR, "%s: mktemp", tmpl); + + if (symlink(from_name, tmpl) == -1) + err(EX_OSERR, "symlink %s -> %s", from_name, tmpl); + + if (target_sb->st_mode & S_IFDIR && rmdir(to_name) == -1) { + (void)unlink(tmpl); + err(EX_OSERR, "%s", to_name); + } + if (target_sb->st_flags & NOCHANGEBITS) + (void)chflags(to_name, target_sb->st_flags & + ~NOCHANGEBITS); + unlink(to_name); + + if (rename(tmpl, to_name) == -1) { + /* remove temporary link before exiting */ + (void)unlink(tmpl); + err(EX_OSERR, "%s: rename", to_name); + } + } else { + if (symlink(from_name, to_name) == -1) + err(EX_OSERR, "symlink %s -> %s", from_name, to_name); + } +} + +/* + * makelink -- + * make a link from source to destination + */ +static void +makelink(const char *from_name, const char *to_name, + const struct stat *target_sb) +{ + char src[MAXPATHLEN], dst[MAXPATHLEN], lnk[MAXPATHLEN]; + struct stat to_sb; + + /* Try hard links first */ + if (dolink & (LN_HARD|LN_MIXED)) { + if (do_link(from_name, to_name, target_sb) == -1) { + if ((dolink & LN_HARD) || errno != EXDEV) + err(EX_OSERR, "link %s -> %s", from_name, to_name); + } else { + if (stat(to_name, &to_sb)) + err(EX_OSERR, "%s: stat", to_name); + if (S_ISREG(to_sb.st_mode)) { + /* XXX: hard links to anything + * other than plain files are not + * metalogged + */ + int omode; + const char *oowner, *ogroup; + char *offlags; + char *dres; + + /* XXX: use underlying perms, + * unless overridden on command line. + */ + omode = mode; + if (!haveopt_m) + mode = (to_sb.st_mode & 0777); + oowner = owner; + if (!haveopt_o) + owner = NULL; + ogroup = group; + if (!haveopt_g) + group = NULL; + offlags = fflags; + if (!haveopt_f) + fflags = NULL; + dres = digest_file(from_name); + metadata_log(to_name, "file", NULL, NULL, + dres, to_sb.st_size); + free(dres); + mode = omode; + owner = oowner; + group = ogroup; + fflags = offlags; + } + return; + } + } + + /* Symbolic links */ + if (dolink & LN_ABSOLUTE) { + /* Convert source path to absolute */ + if (realpath(from_name, src) == NULL) + err(EX_OSERR, "%s: realpath", from_name); + do_symlink(src, to_name, target_sb); + /* XXX: src may point outside of destdir */ + metadata_log(to_name, "link", NULL, src, NULL, 0); + return; + } + + if (dolink & LN_RELATIVE) { + char *cp, *d, *s; + + /* Resolve pathnames */ + if (realpath(from_name, src) == NULL) + err(EX_OSERR, "%s: realpath", from_name); + + /* + * The last component of to_name may be a symlink, + * so use realpath to resolve only the directory. + */ + cp = dirname(to_name); + if (realpath(cp, dst) == NULL) + err(EX_OSERR, "%s: realpath", cp); + /* .. and add the last component */ + if (strcmp(dst, "/") != 0) { + if (strlcat(dst, "/", sizeof(dst)) > sizeof(dst)) + errx(1, "resolved pathname too long"); + } + cp = basename(to_name); + if (strlcat(dst, cp, sizeof(dst)) > sizeof(dst)) + errx(1, "resolved pathname too long"); + + /* trim common path components */ + for (s = src, d = dst; *s == *d; s++, d++) + continue; + while (*s != '/') + s--, d--; + + /* count the number of directories we need to backtrack */ + for (++d, lnk[0] = '\0'; *d; d++) + if (*d == '/') + (void)strlcat(lnk, "../", sizeof(lnk)); + + (void)strlcat(lnk, ++s, sizeof(lnk)); + + do_symlink(lnk, to_name, target_sb); + /* XXX: lnk may point outside of destdir */ + metadata_log(to_name, "link", NULL, lnk, NULL, 0); + return; + } + + /* + * If absolute or relative was not specified, + * try the names the user provided + */ + do_symlink(from_name, to_name, target_sb); + /* XXX: from_name may point outside of destdir */ + metadata_log(to_name, "link", NULL, from_name, NULL, 0); +} + +/* * install -- * build a path name and install the file */ @@ -280,6 +708,7 @@ int devnull, files_match, from_fd, serrno, target; int tempcopy, temp_fd, to_fd; char backup[MAXPATHLEN], *p, pathbuf[MAXPATHLEN], tempfile[MAXPATHLEN]; + char *digestresult; files_match = 0; from_fd = -1; @@ -287,11 +716,13 @@ /* If try to install NULL file to a directory, fails. */ if (flags & DIRECTORY || strcmp(from_name, _PATH_DEVNULL)) { - if (stat(from_name, &from_sb)) - err(EX_OSERR, "%s", from_name); - if (!S_ISREG(from_sb.st_mode)) { - errno = EFTYPE; - err(EX_OSERR, "%s", from_name); + if (!dolink) { + if (stat(from_name, &from_sb)) + err(EX_OSERR, "%s", from_name); + if (!S_ISREG(from_sb.st_mode)) { + errno = EFTYPE; + err(EX_OSERR, "%s", from_name); + } } /* Build the target path. */ if (flags & DIRECTORY) { @@ -305,8 +736,24 @@ devnull = 1; } - target = stat(to_name, &to_sb) == 0; + if (!dolink) + target = (stat(to_name, &to_sb) == 0); + else + target = (lstat(to_name, &to_sb) == 0); + if (dolink) { + if (target && !safecopy) { + if (to_sb.st_mode & S_IFDIR && rmdir(to_name) == -1) + err(EX_OSERR, "%s", to_name); + if (to_sb.st_flags & NOCHANGEBITS) + (void)chflags(to_name, + to_sb.st_flags & ~NOCHANGEBITS); + unlink(to_name); + } + makelink(from_name, to_name, target ? &to_sb : NULL); + return; + } + /* Only install to regular files. */ if (target && !S_ISREG(to_sb.st_mode)) { errno = EFTYPE; @@ -329,7 +776,7 @@ else files_match = !(compare(from_fd, from_name, (size_t)from_sb.st_size, to_fd, - to_name, (size_t)to_sb.st_size)); + to_name, (size_t)to_sb.st_size, &digestresult)); /* Close "to" file unless we match. */ if (!files_match) @@ -351,8 +798,10 @@ from_name, to_name); } if (!devnull) - copy(from_fd, from_name, to_fd, + digestresult = copy(from_fd, from_name, to_fd, tempcopy ? tempfile : to_name, from_sb.st_size); + else + digestresult = NULL; } if (dostrip) { @@ -386,7 +835,8 @@ } if (compare(temp_fd, tempfile, (size_t)temp_sb.st_size, to_fd, - to_name, (size_t)to_sb.st_size) == 0) { + to_name, (size_t)to_sb.st_size, &digestresult) + == 0) { /* * If target has more than one link we need to * replace it in order to snap the extra links. @@ -406,6 +856,10 @@ } } + if (dostrip && (!docompare || !target)) { + digestresult = digest_file(tempfile); + } + /* * Move the new file into place if doing a safe copy * and the files are different (or just not compared). @@ -469,15 +923,16 @@ * Set owner, group, mode for target; do the chown first, * chown may lose the setuid bits. */ - if ((gid != (gid_t)-1 && gid != to_sb.st_gid) || + if (!dounpriv && ((gid != (gid_t)-1 && gid != to_sb.st_gid) || (uid != (uid_t)-1 && uid != to_sb.st_uid) || - (mode != (to_sb.st_mode & ALLPERMS))) { + (mode != (to_sb.st_mode & ALLPERMS)))) { /* Try to turn off the immutable bits. */ if (to_sb.st_flags & NOCHANGEBITS) (void)fchflags(to_fd, to_sb.st_flags & ~NOCHANGEBITS); } - if ((gid != (gid_t)-1 && gid != to_sb.st_gid) || + if (!dounpriv & + (gid != (gid_t)-1 && gid != to_sb.st_gid) || (uid != (uid_t)-1 && uid != to_sb.st_uid)) if (fchown(to_fd, uid, gid) == -1) { serrno = errno; @@ -486,13 +941,16 @@ err(EX_OSERR,"%s: chown/chgrp", to_name); } - if (mode != (to_sb.st_mode & ALLPERMS)) + if (mode != (to_sb.st_mode & ALLPERMS)) { + if (dounpriv) + mode &= S_IRWXU|S_IRWXG|S_IRWXO; if (fchmod(to_fd, mode)) { serrno = errno; (void)unlink(to_name); errno = serrno; err(EX_OSERR, "%s: chmod", to_name); } + } /* * If provided a set of flags, set them, otherwise, preserve the @@ -501,7 +959,7 @@ * trying to turn off UF_NODUMP. If we're trying to set real flags, * then warn if the fs doesn't support it, otherwise fail. */ - if (!devnull && (flags & SETFLAGS || + if (!dounpriv & !devnull && (flags & SETFLAGS || (from_sb.st_flags & ~UF_NODUMP) != to_sb.st_flags) && fchflags(to_fd, flags & SETFLAGS ? fset : from_sb.st_flags & ~UF_NODUMP)) { @@ -520,6 +978,9 @@ (void)close(to_fd); if (!devnull) (void)close(from_fd); + + metadata_log(to_name, "file", tvb, NULL, digestresult, to_sb.st_size); + free(digestresult); } /* @@ -528,29 +989,37 @@ */ static int compare(int from_fd, const char *from_name __unused, size_t from_len, - int to_fd, const char *to_name __unused, size_t to_len) + int to_fd, const char *to_name __unused, size_t to_len, + char **dresp) { char *p, *q; int rv; int done_compare; + DIGEST_CTX ctx; rv = 0; if (from_len != to_len) return 1; if (from_len <= MAX_CMP_SIZE) { + if (dresp != NULL) + digest_init(&ctx); done_compare = 0; if (trymmap(from_fd) && trymmap(to_fd)) { - p = mmap(NULL, from_len, PROT_READ, MAP_SHARED, from_fd, (off_t)0); + p = mmap(NULL, from_len, PROT_READ, MAP_SHARED, + from_fd, (off_t)0); if (p == (char *)MAP_FAILED) goto out; - q = mmap(NULL, from_len, PROT_READ, MAP_SHARED, to_fd, (off_t)0); + q = mmap(NULL, from_len, PROT_READ, MAP_SHARED, + to_fd, (off_t)0); if (q == (char *)MAP_FAILED) { munmap(p, from_len); goto out; } rv = memcmp(p, q, from_len); + if (dresp != NULL) + digest_update(&ctx, p, from_len); munmap(p, from_len); munmap(q, from_len); done_compare = 1; @@ -576,6 +1045,7 @@ rv = 1; /* out of sync */ } else rv = 1; /* read failure */ + digest_update(&ctx, buf1, n1); } lseek(from_fd, 0, SEEK_SET); lseek(to_fd, 0, SEEK_SET); @@ -583,6 +1053,13 @@ } else rv = 1; /* don't bother in this case */ + if (dresp != NULL) { + if (rv == 0) + *dresp = digest_end(&ctx, NULL); + else + (void)digest_end(&ctx, NULL); + } + return rv; } @@ -653,7 +1130,7 @@ * copy -- * copy from one file to another */ -static void +static char * copy(int from_fd, const char *from_name, int to_fd, const char *to_name, off_t size) { @@ -661,6 +1138,7 @@ int serrno; char *p, buf[MAXBSIZE]; int done_copy; + DIGEST_CTX ctx; /* Rewind file descriptors. */ if (lseek(from_fd, (off_t)0, SEEK_SET) == (off_t)-1) @@ -668,6 +1146,8 @@ if (lseek(to_fd, (off_t)0, SEEK_SET) == (off_t)-1) err(EX_OSERR, "lseek: %s", to_name); + digest_init(&ctx); + /* * Mmap and write if less than 8M (the limit is so we don't totally * trash memory on big files. This is really a minor hack, but it @@ -690,10 +1170,12 @@ err(EX_OSERR, "%s", to_name); } } + digest_update(&ctx, p, size); + (void)munmap(p, size); done_copy = 1; } if (!done_copy) { - while ((nr = read(from_fd, buf, sizeof(buf))) > 0) + while ((nr = read(from_fd, buf, sizeof(buf))) > 0) { if ((nw = write(to_fd, buf, nr)) != nr) { serrno = errno; (void)unlink(to_name); @@ -707,6 +1189,8 @@ err(EX_OSERR, "%s", to_name); } } + digest_update(&ctx, buf, nr); + } if (nr != 0) { serrno = errno; (void)unlink(to_name); @@ -714,6 +1198,7 @@ err(EX_OSERR, "%s", from_name); } } + return digest_end(&ctx, NULL); } /* @@ -776,13 +1261,95 @@ break; } - if ((gid != (gid_t)-1 || uid != (uid_t)-1) && chown(path, uid, gid)) - warn("chown %u:%u %s", uid, gid, path); - if (chmod(path, mode)) - warn("chmod %o %s", mode, path); + if (!dounpriv) { + if ((gid != (gid_t)-1 || uid != (uid_t)-1) && + chown(path, uid, gid)) + warn("chown %u:%u %s", uid, gid, path); + /* XXXBED: should we do the chmod in the dounpriv case? */ + if (chmod(path, mode)) + warn("chmod %o %s", mode, path); + } + metadata_log(path, "dir", NULL, NULL, NULL, 0); } /* + * metadata_log -- + * if metafp is not NULL, output mtree(8) full path name and settings to + * metafp, to allow permissions to be set correctly by other tools, + * or to allow integrity checks to be performed. + */ +static void +metadata_log(const char *path, const char *type, struct timeval *tv, + const char *slink, const char *digestresult, off_t size) +{ + static const char extra[] = { ' ', '\t', '\n', '\\', '#', '\0' }; + const char *p; + char *buf; + size_t destlen; + struct flock metalog_lock; + + if (!metafp) + return; + buf = (char *)malloc(4 * strlen(path) + 1); /* buf for strsvis(3) */ + if (buf == NULL) { + warnx("%s", strerror(ENOMEM)); + return; + } + /* lock log file */ + metalog_lock.l_start = 0; + metalog_lock.l_len = 0; + metalog_lock.l_whence = SEEK_SET; + metalog_lock.l_type = F_WRLCK; + if (fcntl(fileno(metafp), F_SETLKW, &metalog_lock) == -1) { + warn("can't lock %s", metafile); + free(buf); + return; + } + + p = path; /* remove destdir */ + if (destdir) { + destlen = strlen(destdir); + if (strncmp(p, destdir, destlen) == 0 && + (p[destlen] == '/' || p[destlen] == '\0')) + p += destlen; + } + while (*p && *p == '/') /* remove leading /s */ + p++; + strsvis(buf, p, VIS_CSTYLE, extra); /* encode name */ + p = buf; + /* print details */ + fprintf(metafp, ".%s%s type=%s", *p ? "/" : "", p, type); + if (owner) + fprintf(metafp, " uname=%s", owner); + if (group) + fprintf(metafp, " gname=%s", group); + fprintf(metafp, " mode=%#o", mode); + if (slink) { + strsvis(buf, slink, VIS_CSTYLE, extra); /* encode link */ + fprintf(metafp, " link=%s", buf); + } + if (*type == 'f') /* type=file */ + fprintf(metafp, " size=%lld", (long long)size); + if (tv != NULL && dopreserve) + fprintf(metafp, " time=%lld.%ld", + (long long)tv[1].tv_sec, (long)tv[1].tv_usec); + if (digestresult && digest) + fprintf(metafp, " %s=%s", digest, digestresult); + if (fflags) + fprintf(metafp, " flags=%s", fflags); + if (tags) + fprintf(metafp, " tags=%s", tags); + fputc('\n', metafp); + fflush(metafp); /* flush output */ + /* unlock log file */ + metalog_lock.l_type = F_UNLCK; + if (fcntl(fileno(metafp), F_SETLKW, &metalog_lock) == -1) { + warn("can't unlock %s", metafile); + } + free(buf); +} + +/* * usage -- * print a usage message and die */ @@ -790,11 +1357,16 @@ usage(void) { (void)fprintf(stderr, -"usage: install [-bCcMpSsv] [-B suffix] [-f flags] [-g group] [-m mode]\n" -" [-N dbdir] [-o owner] file1 file2\n" -" install [-bCcMpSsv] [-B suffix] [-f flags] [-g group] [-m mode]\n" -" [-N dbdir] [-o owner] file1 ... fileN directory\n" -" install -d [-v] [-g group] [-m mode] [-N dbdir] [-o owner]\n" +"usage: install [-bCcpSsUv] [-f flags] [-g group] [-m mode] [-o owner]\n" +" [-M log] [-D dest] [-h hash] [-T tags]\n" +" [-B suffix] [-l linkflags] [-N dbdir]\n" +" file1 file2\n" +" install [-bCcpSsUv] [-f flags] [-g group] [-m mode] [-o owner]\n" +" [-M log] [-D dest] [-h hash] [-T tags]\n" +" [-B suffix] [-l linkflags] [-N dbdir]\n" +" file1 ... fileN directory\n" +" install -dU [-vU] [-g group] [-m mode] [-N dbdir] [-o owner]\n" +" [-M log] [-D dest] [-h hash] [-T tags]\n" " directory ...\n"); exit(EX_USAGE); /* NOTREACHED */ @@ -814,7 +1386,7 @@ #ifdef MFSNAMELEN struct statfs stfs; - if (nommap || fstatfs(fd, &stfs) != 0) + if (fstatfs(fd, &stfs) != 0) return (0); if (strcmp(stfs.f_fstypename, "ufs") == 0 || strcmp(stfs.f_fstypename, "cd9660") == 0) Index: usr.bin/xinstall/install.1 =================================================================== --- usr.bin/xinstall/install.1 (revision 245428) +++ usr.bin/xinstall/install.1 (working copy) @@ -36,34 +36,49 @@ .Nd install binaries .Sh SYNOPSIS .Nm -.Op Fl bCcMpSsv +.Op Fl bCcMpSsUv .Op Fl B Ar suffix +.Op Fl D Ar destdir .Op Fl f Ar flags .Op Fl g Ar group +.Op Fl h Ar hash +.Op Fl l Ar linkflags +.Op Fl M Ar metalog .Op Fl m Ar mode .Op Fl N Ar dbdir .Op Fl o Ar owner +.Op Fl T Ar tags .Ar file1 file2 .Nm -.Op Fl bCcMpSsv +.Op Fl bCcMpSsUv .Op Fl B Ar suffix +.Op Fl D Ar destdir .Op Fl f Ar flags .Op Fl g Ar group +.Op Fl h Ar hash +.Op Fl l Ar linkflags +.Op Fl M Ar metalog .Op Fl m Ar mode .Op Fl N Ar dbdir .Op Fl o Ar owner +.Op Fl T Ar tags .Ar file1 ... fileN directory .Nm .Fl d -.Op Fl v +.Op Fl Uv +.Op Fl D Ar destdir .Op Fl g Ar group +.Op Fl M Ar metalog .Op Fl m Ar mode .Op Fl N Ar dbdir .Op Fl o Ar owner +.Op Fl T Ar tags .Ar directory ... .Sh DESCRIPTION The file(s) are copied -to the target file or directory. +(or linked if the +.Fl l +option is specified) to the target file or directory. If the destination is a directory, then the .Ar file is copied into @@ -108,6 +123,17 @@ The .Fl c option is only included for backwards compatibility. +.It Fl D Ar destdir +Specify the +.Ev DESTDIR +(top of the file hierarchy) that the items are installed in to. +If +.Fl M Ar metalog +is in use, a leading string of +.Dq Ar destdir +will be removed from the file names logged to the +.Ar metalog . +This option does not affect where the actual files are installed. .It Fl d Create directories. Missing parent directories are created as required. @@ -118,9 +144,64 @@ .It Fl g Specify a group. A numeric GID is allowed. +.It Fl h Ar hash +When copying, calculate the digest of the files with +.Ar hash +to store in the +.Fl M Ar metalog . +Supported digests: +.Bl -tag -width rmd160 -offset indent +.It Sy none +No hash. +This is the default. +.It Sy md5 +The MD5 cryptographic message digest. +.It Sy rmd160 +The RMD-160 cryptographic message digest. +.It Sy sha1 +The SHA-1 cryptographic message digest. +.It Sy sha256 +The 256-bits +.Tn SHA-2 +cryptographic message digest of the file. +.It Sy sha512 +The 512-bits +.Tn SHA-2 +cryptographic message digest of the file. +.El +.It Fl l Ar linkflags +Instead of copying the file make a link to the source. +The type of the link is determined by the +.Ar linkflags +argument. +Valid +.Ar linkflags +are: +.Ar a +(absolute), +.Ar r +(relative), +.Ar h +(hard), +.Ar s +(symbolic), +.Ar m +(mixed). +Absolute and relative have effect only for symbolic links. +Mixed links +are hard links for files on the same filesystem, symbolic otherwise. .It Fl M Disable all use of .Xr mmap 2 . +.It Fl M Ar metalog +Write the metadata associated with each item installed to +.Ar metalog +in an +.Xr mtree 8 +.Dq full path +specification line. +The metadata includes: the file name and file type, and depending upon +other options, the owner, group, file flags, modification time, and tags. .It Fl m Specify an alternate mode. The default mode is set to rwxr-xr-x (0755). @@ -171,6 +252,17 @@ See below for how .Nm can be instructed to use another program to strip binaries. +.It Fl T Ar tags +Specify the +.Xr mtree 8 +tags to write out for the file when using +.Fl M Ar metalog . +.It Fl U +Indicate that install is running unprivileged, and that it should not +try to change the owner, the group, or the file flags of the destination. +The information that would have been updated can be stored in a log +file with +.Fl M Ar metalog . .It Fl v Cause .Nm Index: usr.bin/xinstall/Makefile =================================================================== --- usr.bin/xinstall/Makefile (revision 245428) +++ usr.bin/xinstall/Makefile (working copy) @@ -10,7 +10,7 @@ CFLAGS+= -I${.CURDIR}/../../contrib/mtree CFLAGS+= -I${.CURDIR}/../../lib/libnetbsd -DPADD+= ${LIBUTIL} -LDADD+= -lutil +DPADD+= ${LIBUTIL} ${LIBMD} +LDADD+= -lutil -lmd .include