Introduce six new options from NetBSD:

* -M <metalog>   Log metadata in mtree format.
 * -D <destdir>   Log paths relative to <destdir>.
 * -h <hash>      Log digest of type <hash>.
 * -T <tags>      Specify which mtree tags to log.
 * -l <linkflag>  Create hard or symbolic links (allows logging).
 * -U             Install without root privileges (owner, group, mode,
                  and flags can be logged via -M

NOTE: In the interest of compatibility with NetBSD and because it is the
obvious letter, the nearly useless -M option (disable mmap) has been
repurposed.

Sponsored by:	DARPA, AFRL
Obtained from:	NetBSD
Reviewed by:	bz
This commit is contained in:
Brooks Davis 2013-01-18 20:57:50 +00:00
parent e4558aacfc
commit f2b19f9ece
4 changed files with 742 additions and 53 deletions

View File

@ -26,6 +26,15 @@ NOTE TO PEOPLE WHO THINK THAT FreeBSD 10.x IS SLOW:
disable the most expensive debugging functionality run
"ln -s 'abort:false,junk:false' /etc/malloc.conf".)
20130118:
The install(1) option -M has changed meaning and now takes an
argument that is a file or path to append logs to. In the
unlikely event that -M was the last option on the command line
and the command line contained at least two files and a target
directory the first file will have logs appended to it. The -M
option served little practical purpose in the last decade so it's
used expected to be extremely rare.
20121223:
After switching to Clang as the default compiler some users of ZFS
on i386 systems started to experience stack overflow kernel panics.

View File

@ -10,7 +10,7 @@ MAN= install.1
CFLAGS+= -I${.CURDIR}/../../contrib/mtree
CFLAGS+= -I${.CURDIR}/../../lib/libnetbsd
DPADD+= ${LIBUTIL}
LDADD+= -lutil
DPADD+= ${LIBUTIL} ${LIBMD}
LDADD+= -lutil -lmd
.include <bsd.prog.mk>

View File

@ -28,7 +28,7 @@
.\" From: @(#)install.1 8.1 (Berkeley) 6/6/93
.\" $FreeBSD$
.\"
.Dd March 6, 2006
.Dd January 18, 2013
.Dt INSTALL 1
.Os
.Sh NAME
@ -36,34 +36,50 @@
.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 h Ar hash
.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 +124,17 @@ This is actually the default.
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 +145,67 @@ for a list of possible flags and their meanings.
.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 .
When
.Fl d
is given no hash is emitted.
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 +256,17 @@ number of systems and binary types.
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
@ -255,6 +351,16 @@ The
utility appeared in
.Bx 4.2 .
.Sh BUGS
The meaning of the
.Fl M
option has changed as of
.Fx 10
and it now takes an argument.
Command lines that used the old
.Fl M
will get an error or in rare cases will append logs to the first of
multiple source files rather than installing it.
.Pp
Temporary files may be left in the target directory if
.Nm
exits abnormally.

View File

@ -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 @@ __FBSDID("$FreeBSD$");
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <libgen.h>
#include <md5.h>
#include <paths.h>
#include <pwd.h>
#include <ripemd.h>
#include <sha.h>
#include <sha256.h>
#include <sha512.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#include <vis.h>
#include "mtree.h"
@ -71,24 +79,62 @@ __FBSDID("$FreeBSD$");
#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 enum {
DIGEST_NONE = 0,
DIGEST_MD5,
DIGEST_RIPEMD160,
DIGEST_SHA1,
DIGEST_SHA256,
DIGEST_SHA512,
} digesttype = DIGEST_NONE;
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);
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 @@ main(int argc, char *argv[])
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 @@ main(int argc, char *argv[])
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 != '\0'; 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 @@ main(int argc, char *argv[])
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 @@ main(int argc, char *argv[])
case 's':
dostrip = 1;
break;
case 'T':
tags = optarg;
break;
case 'U':
dounpriv = 1;
break;
case 'v':
verbose = 1;
break;
@ -185,14 +273,33 @@ main(int argc, char *argv[])
if (argc == 0 || (argc == 1 && !dodir))
usage();
if (digest != NULL) {
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 +307,7 @@ main(int argc, char *argv[])
} 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 +317,18 @@ main(int argc, char *argv[])
} 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 +355,7 @@ main(int argc, char *argv[])
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 +372,96 @@ main(int argc, char *argv[])
/* 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
@ -268,6 +477,224 @@ parseid(const char *name, id_t *id)
return (1);
}
/*
* 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: Link 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 +707,7 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
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 +715,13 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
/* 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,7 +735,23 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
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)) {
@ -329,7 +775,7 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
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 +797,10 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
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 +834,8 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
}
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 +855,9 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
}
}
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 +921,16 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
* 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 +939,16 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
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 +957,7 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
* 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 +976,9 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
(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 +987,37 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags)
*/
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 +1043,7 @@ compare(int from_fd, const char *from_name __unused, size_t from_len,
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 +1051,13 @@ compare(int from_fd, const char *from_name __unused, size_t from_len,
} 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 +1128,7 @@ create_newfile(const char *path, int target, struct stat *sbp)
* 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 +1136,7 @@ copy(int from_fd, const char *from_name, int to_fd, const char *to_name,
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 +1144,8 @@ copy(int from_fd, const char *from_name, int to_fd, const char *to_name,
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 +1168,12 @@ copy(int from_fd, const char *from_name, int to_fd, const char *to_name,
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 +1187,8 @@ copy(int from_fd, const char *from_name, int to_fd, const char *to_name,
err(EX_OSERR, "%s", to_name);
}
}
digest_update(&ctx, buf, nr);
}
if (nr != 0) {
serrno = errno;
(void)unlink(to_name);
@ -714,6 +1196,7 @@ copy(int from_fd, const char *from_name, int to_fd, const char *to_name,
err(EX_OSERR, "%s", from_name);
}
}
return (digest_end(&ctx, NULL));
}
/*
@ -776,10 +1259,96 @@ install_dir(char *path)
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;
/* Buffer for strsvis(3). */
buf = (char *)malloc(4 * strlen(path) + 1);
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;
}
/* Remove destdir. */
p = path;
if (destdir) {
destlen = strlen(destdir);
if (strncmp(p, destdir, destlen) == 0 &&
(p[destlen] == '/' || p[destlen] == '\0'))
p += destlen;
}
while (*p && *p == '/')
p++;
strsvis(buf, p, VIS_OCTAL, extra);
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);
/* Flush line. */
fflush(metafp);
/* 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);
}
/*
@ -790,11 +1359,16 @@ static void
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 +1388,7 @@ trymmap(int fd)
#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)