diff options
author | jperkin <jperkin> | 2013-08-28 11:42:36 +0000 |
---|---|---|
committer | jperkin <jperkin> | 2013-08-28 11:42:36 +0000 |
commit | 125842e8239582561cd743e690b51e0849f08ebf (patch) | |
tree | b187655740a8e013f1889b9720a7bfe110983bd8 /sysutils/bsdinstall | |
parent | 2750b2ef8950d4677cfde561e699b2f4b298b2e3 (diff) | |
download | pkgsrc-125842e8239582561cd743e690b51e0849f08ebf.tar.gz |
Add bsdinstall-20130828 to pkgsrc.
This is a portable version of NetBSD's install(1) program, and is intended
to be used as a replacement for the install-sh script on platforms which do
not have a native BSD install program. The install-sh script does not
support file names with spaces, and this version should be more robust.
This portable version does not include mtree, futimes and chflags support.
Tested in a full SmartOS bulk build with no apparent issues.
Diffstat (limited to 'sysutils/bsdinstall')
-rw-r--r-- | sysutils/bsdinstall/DESCR | 2 | ||||
-rw-r--r-- | sysutils/bsdinstall/Makefile | 24 | ||||
-rw-r--r-- | sysutils/bsdinstall/PLIST | 3 | ||||
-rw-r--r-- | sysutils/bsdinstall/files/Makefile | 15 | ||||
-rw-r--r-- | sysutils/bsdinstall/files/bsdinstall.1 | 339 | ||||
-rw-r--r-- | sysutils/bsdinstall/files/bsdinstall.c | 1302 | ||||
-rw-r--r-- | sysutils/bsdinstall/files/setmode.c | 498 |
7 files changed, 2183 insertions, 0 deletions
diff --git a/sysutils/bsdinstall/DESCR b/sysutils/bsdinstall/DESCR new file mode 100644 index 00000000000..1b98f4524b3 --- /dev/null +++ b/sysutils/bsdinstall/DESCR @@ -0,0 +1,2 @@ +This package provides the BSD install(1) program for compatability to +systems which do not have a native version. diff --git a/sysutils/bsdinstall/Makefile b/sysutils/bsdinstall/Makefile new file mode 100644 index 00000000000..cdfffdb4680 --- /dev/null +++ b/sysutils/bsdinstall/Makefile @@ -0,0 +1,24 @@ +# $NetBSD: Makefile,v 1.1 2013/08/28 11:42:36 jperkin Exp $ + +DISTNAME= bsdinstall-20130828 +CATEGORIES= sysutils +MASTER_SITES= # empty +DISTFILES= # empty + +MAINTAINER= pkgsrc-users@NetBSD.org +COMMENT= Portable version of the BSD install(1) program + +BOOTSTRAP_PKG= yes + +USE_BSD_MAKEFILE= yes +USE_FEATURES= nbcompat + +INSTALLATION_DIRS= bin ${PKGMANDIR}/man1 + +do-extract: + @${CP} -R ${FILESDIR} ${WRKSRC} + +CPPFLAGS+= -D_PATH_DEVNULL=\"/dev/null\" +CPPFLAGS+= -DTARGET_STRIP=\"${TOOLS_PLATFORM.strip:Q}\" + +.include "../../mk/bsd.pkg.mk" diff --git a/sysutils/bsdinstall/PLIST b/sysutils/bsdinstall/PLIST new file mode 100644 index 00000000000..ef379954791 --- /dev/null +++ b/sysutils/bsdinstall/PLIST @@ -0,0 +1,3 @@ +@comment $NetBSD: PLIST,v 1.1 2013/08/28 11:42:36 jperkin Exp $ +bin/bsdinstall +man/man1/bsdinstall.1 diff --git a/sysutils/bsdinstall/files/Makefile b/sysutils/bsdinstall/files/Makefile new file mode 100644 index 00000000000..ef283d2d2ca --- /dev/null +++ b/sysutils/bsdinstall/files/Makefile @@ -0,0 +1,15 @@ +# $NetBSD: Makefile,v 1.1 2013/08/28 11:42:36 jperkin Exp $ +# NetBSD: Makefile,v 1.21 2006/12/16 12:59:17 bouyer Exp +# @(#)Makefile 8.1 (Berkeley) 6/6/93 + +.include <bsd.own.mk> + +PROG= bsdinstall +SRCS= bsdinstall.c setmode.c +MAN= bsdinstall.1 + +LDADD+= -lnbcompat + +COPTS.xinstall.c += -Wno-format-nonliteral + +.include <bsd.prog.mk> diff --git a/sysutils/bsdinstall/files/bsdinstall.1 b/sysutils/bsdinstall/files/bsdinstall.1 new file mode 100644 index 00000000000..02e18863cbb --- /dev/null +++ b/sysutils/bsdinstall/files/bsdinstall.1 @@ -0,0 +1,339 @@ +.\" $NetBSD: bsdinstall.1,v 1.1 2013/08/28 11:42:36 jperkin Exp $ +.\" NetBSD: install.1,v 1.46 2012/03/22 07:58:20 wiz Exp +.\" +.\" Copyright (c) 1987, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)install.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd May 1, 2009 +.Dt BSDINSTALL 1 +.Os +.Sh NAME +.Nm bsdinstall +.Nd install binaries +.Sh SYNOPSIS +.Nm +.Op Fl bcprsU +.Op Fl a Ar command +.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 S Ar stripflag +.Op Fl T Ar tags +.Ar file1 file2 +.Nm +.Op Fl bcprsU +.Op Fl a Ar command +.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 S Ar stripflag +.Op Fl T Ar tags +.Ar file1 ...\& +.Ar fileN directory +.Nm +.Fl d +.Op Fl pU +.Op Fl a Ar command +.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 +(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 +.Ar directory +with its original filename. +If the target file already exists, it is +either renamed to +.Ar file.old +if the +.Fl b +option is given +or overwritten +if permissions allow; an alternate backup suffix may be specified via the +.Fl B +option's argument. +.Pp +.Bl -tag -width XsXXstripflagsXX +.It Fl a Ar command +Run +.Ar command +on the target after installation and stripping +.Pq Fl s , +but before +ownership, permissions or timestamps are set and before renaming +.Pq Fl r +occurs. +.Ar command +is invoked via the +.Xr sh 1 +shell, allowing a single +.Fl a +argument be to specified to +.Nm +which the shell can then tokenize. +.It Fl B Ar suffix +Use +.Ar suffix +as the backup suffix if +.Fl b +is given. +If +.Ar suffix +contains a '%' sign, a numbered backup will be performed, and the +%-pattern will be expanded using +.Xr sprintf 3 , +given an integer counter as the backup number. +The counter used starts from 0, and the first available name resulting +from the expansion is used. +.It Fl b +Backup any existing files before overwriting them by renaming +them to +.Ar file.old . See +.Fl B +for specifying a different backup suffix. +.It Fl c +Copy the file. +This is the default behavior; the flag is maintained for backwards +compatibility only. +.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. +.It Fl f Ar flags +Specify the target's file flags. +(See +.Xr chflags 1 +for a list of possible flags and their meanings.) +.It Fl g Ar group +Specify a group. +.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 sha384 +The 384-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 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 Ar mode +Specify an alternative mode. +The default mode is set to rwxr-xr-x (0755). +The specified mode may be either an octal or symbolic value; see +.Xr chmod 1 +for a description of possible mode values. +.It Fl N Ar dbdir +Use the user database text file +.Pa master.passwd +and group database text file +.Pa group +from +.Ar dbdir , +rather than using the results from the system's +.Xr getpwnam 3 +and +.Xr getgrnam 3 +(and related) library calls. +.It Fl o Ar owner +Specify an owner. +.It Fl p +Preserve the source files access and modification times. +.It Fl r +Install to a temporary file and then rename the file to its final destination +name. +This can be used for precious files, to avoid truncation of the original +when error conditions (filesystem full etc.) occur. +.It Fl S Ar stripflags +.Nm +passes +.Ar stripflags +as option arguments to +.Xr strip 1 . +When +.Fl S +is used, +.Xr strip 1 +is invoked via the +.Xr sh 1 +shell, allowing a single +.Fl S +argument be to specified to +.Nm +which the shell can then tokenize. +Normally, +.Nm +invokes +.Xr strip 1 +directly. +This flag implies +.Fl s . +.It Fl s +.Nm +exec's the command +.Xr strip 1 +to strip binaries so that bsdinstall can be portable over a large +number of systems and binary types. +If the environment variable +.Ev STRIP +is set, it is used as the +.Xr strip 1 +program. +.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 bsdinstall 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 . +.El +.Pp +By default, +.Nm +preserves all file flags, with the exception of the ``nodump'' flag. +.Pp +The +.Nm +utility attempts to prevent copying a file onto itself. +.Pp +Installing +.Pa /dev/null +creates an empty file. +.Sh ENVIRONMENT +.Bl -tag -width Fl +.It Ev STRIP +The program used to strip installed binaries when the +.Fl s +option is used. +If unspecified, +.Pa /usr/bin/strip +is used. +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr chflags 1 , +.Xr chgrp 1 , +.Xr chmod 1 , +.Xr cp 1 , +.Xr mv 1 , +.Xr strip 1 , +.Xr chown 8 , +.Xr mtree 8 +.Sh HISTORY +The +.Nm +utility appeared in +.Bx 4.2 . diff --git a/sysutils/bsdinstall/files/bsdinstall.c b/sysutils/bsdinstall/files/bsdinstall.c new file mode 100644 index 00000000000..e1fdd7c662a --- /dev/null +++ b/sysutils/bsdinstall/files/bsdinstall.c @@ -0,0 +1,1302 @@ +/* $NetBSD: bsdinstall.c,v 1.1 2013/08/28 11:42:36 jperkin Exp $ */ +/* NetBSD: xinstall.c,v 1.114 2009/11/12 10:10:49 tron Exp */ + +/* + * Copyright (c) 1987, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(HAVE_NBCOMPAT_H) +#include <nbcompat/config.h> +#include <nbcompat/cdefs.h> +#endif + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#else +#define HAVE_FUTIMES 0 +#define HAVE_STRUCT_STAT_ST_FLAGS 0 +#endif + +#if !defined(HAVE_NBCOMPAT_H) +#include <sys/cdefs.h> +#endif +#if defined(__COPYRIGHT) && !defined(lint) +__COPYRIGHT("@(#) Copyright (c) 1987, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#if defined(__RCSID) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)xinstall.c 8.1 (Berkeley) 7/21/93"; +#else +__RCSID("NetBSD: xinstall.c,v 1.114 2009/11/12 10:10:49 tron Exp"); +#endif +#endif /* not lint */ + +#define __MKTEMP_OK__ /* All uses of mktemp have been checked */ +#include <sys/param.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/time.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <grp.h> +#include <libgen.h> +#if defined(HAVE_NBCOMPAT_H) +#include <nbcompat/paths.h> +#else +#include <paths.h> +#endif +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#if defined(HAVE_NBCOMPAT_H) +#include <nbcompat/util.h> +#include <nbcompat/vis.h> +#else +#include <util.h> +#include <vis.h> +#endif + +#if defined(HAVE_NBCOMPAT_H) +#include <nbcompat/md5.h> +#include <nbcompat/rmd160.h> +#include <nbcompat/sha1.h> +#include <nbcompat/sha2.h> +#else +#include <md5.h> +#include <rmd160.h> +#include <sha1.h> +#include <sha2.h> +#endif + +#ifdef SUPPORT_MTREE +#include "mtree.h" +#endif + +#define STRIP_ARGS_MAX 32 +#define BACKUP_SUFFIX ".old" + +extern void *setmode(const char *p); + +static int dobackup, dodir, dostrip, dolink, dopreserve, dorename, dounpriv; +static int haveopt_f, haveopt_g, haveopt_m, haveopt_o; +static int numberedbackup; +static int mode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH; +static char pathbuf[MAXPATHLEN]; +static uid_t uid = -1; +static gid_t gid = -1; +static char *group, *owner, *fflags, *tags; +static FILE *metafp; +static char *metafile; +static u_long fileflags; +static char *stripArgs; +static char *afterinstallcmd; +static const char *suffix = BACKUP_SUFFIX; +static char *destdir; + +enum { + DIGEST_NONE = 0, + DIGEST_MD5, + DIGEST_RMD160, + DIGEST_SHA1, + DIGEST_SHA256, + DIGEST_SHA384, + DIGEST_SHA512, +} digesttype = DIGEST_NONE; + +static char *digest; + +#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 HASUID 0x04 /* Tell install the uid was given */ +#define HASGID 0x08 /* Tell install the gid was given */ + +static void afterinstall(const char *, const char *, int); +static void backup(const char *); +static char *copy(int, char *, int, char *, off_t); +static int do_link(char *, char *); +static void do_symlink(char *, char *); +static void install(char *, char *, u_int); +static void install_dir(char *, u_int); +static void makelink(char *, char *); +static void metadata_log(const char *, const char *, struct timeval *, + const char *, const char *, off_t); +static int parseid(char *, id_t *); +static void strip(char *); +static void usage(void); +static char *xbasename(char *); +static char *xdirname(char *); + +int +main(int argc, char *argv[]) +{ + struct stat from_sb, to_sb; + void *set; + u_int iflags; + int ch, no_target; + char *p, *to_name; + + setprogname(argv[0]); + + iflags = 0; + while ((ch = getopt(argc, argv, "a:cbB:dD:f:g:h:l:m:M:N:o:prsS:T:U")) + != -1) + switch((char)ch) { + case 'a': + afterinstallcmd = strdup(optarg); + if (afterinstallcmd == NULL) + errx(1, "%s", strerror(ENOMEM)); + break; + case 'B': + suffix = optarg; + numberedbackup = 0; + { + /* Check if given suffix really generates + different suffixes - catch e.g. ".%" */ + char suffix_expanded0[FILENAME_MAX], + suffix_expanded1[FILENAME_MAX]; + (void)snprintf(suffix_expanded0, FILENAME_MAX, + suffix, 0); + (void)snprintf(suffix_expanded1, FILENAME_MAX, + suffix, 1); + if (strcmp(suffix_expanded0, suffix_expanded1) + != 0) + numberedbackup = 1; + } + /* fall through; -B implies -b */ + /*FALLTHROUGH*/ + case 'b': + dobackup = 1; + break; + case 'c': + /* ignored; was "docopy" which is now the default. */ + break; + case 'd': + dodir = 1; + break; + case 'D': + destdir = optarg; + break; +#if SUPPORT_FLAGS + case 'f': + haveopt_f = 1; + fflags = optarg; + break; +#endif + 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': + haveopt_m = 1; + if (!(set = setmode(optarg))) + err(1, "Cannot set file mode `%s'", optarg); + mode = getmode(set, 0); + free(set); + break; +#ifdef SUPPORT_MTREE + case 'M': + metafile = optarg; + break; + case 'N': + if (! setup_getid(optarg)) + errx(1, + "Unable to use user and group databases in `%s'", + optarg); + break; +#endif + case 'o': + haveopt_o = 1; + owner = optarg; + break; + case 'p': + dopreserve = 1; + break; + case 'r': + dorename = 1; + break; + case 'S': + stripArgs = strdup(optarg); + if (stripArgs == NULL) + errx(1, "%s", strerror(ENOMEM)); + /* fall through; -S implies -s */ + /*FALLTHROUGH*/ + case 's': + dostrip = 1; + break; + case 'T': + tags = optarg; + break; + case 'U': + dounpriv = 1; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + /* strip and link options make no sense when creating directories */ + if ((dostrip || dolink) && dodir) + usage(); + + /* strip and flags make no sense with links */ + if ((dostrip || fflags) && dolink) + usage(); + + /* must have at least two arguments, except when creating directories */ + if (argc < 2 && !dodir) + usage(); + + if (digest) { + 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_RMD160; + } else if (strcmp(digest, "sha1") == 0) { + digesttype = DIGEST_SHA1; + } else if (strcmp(digest, "sha256") == 0) { + digesttype = DIGEST_SHA256; + } else if (strcmp(digest, "sha384") == 0) { + digesttype = DIGEST_SHA384; + } else if (strcmp(digest, "sha512") == 0) { + digesttype = DIGEST_SHA512; + } else { + warnx("unknown digest `%s'", digest); + usage(); + } + } + + /* get group and owner id's */ + if (group && !dounpriv) { + if (gid_from_group(group, &gid) == -1) { + id_t id; + if (!parseid(group, &id)) + errx(1, "unknown group %s", group); + gid = id; + } + iflags |= HASGID; + } + if (owner && !dounpriv) { + if (uid_from_user(owner, &uid) == -1) { + id_t id; + if (!parseid(owner, &id)) + errx(1, "unknown user %s", owner); + uid = id; + } + iflags |= HASUID; + } + +#if SUPPORT_FLAGS + if (fflags && !dounpriv) { + if (string_to_flags(&fflags, &fileflags, NULL)) + errx(1, "%s: invalid flag", fflags); + /* restore fflags since string_to_flags() changed it */ + fflags = flags_to_string(fileflags, "-"); + iflags |= SETFLAGS; + } +#endif + + if (metafile) { + if ((metafp = fopen(metafile, "a")) == NULL) + warn("open %s", metafile); + } else + digesttype = DIGEST_NONE; + + if (dodir) { + for (; *argv != NULL; ++argv) + install_dir(*argv, iflags); + exit (0); + } + + no_target = stat(to_name = argv[argc - 1], &to_sb); + if (!no_target && S_ISDIR(to_sb.st_mode)) { + for (; *argv != to_name; ++argv) + install(*argv, to_name, iflags | DIRECTORY); + exit(0); + } + + /* can't do file1 file2 directory/file */ + if (argc != 2) { + errx(EXIT_FAILURE, "the last argument (%s) " + "must name an existing directory", argv[argc - 1]); + /* NOTREACHED */ + } + + if (!no_target) { + /* makelink() handles checks for links */ + if (!dolink) { + if (stat(*argv, &from_sb)) + err(1, "%s: stat", *argv); + if (!S_ISREG(to_sb.st_mode)) + errx(1, "%s: not a regular file", to_name); + if (to_sb.st_dev == from_sb.st_dev && + to_sb.st_ino == from_sb.st_ino) + errx(1, "%s and %s are the same file", *argv, + to_name); + } + /* + * Unlink now... avoid ETXTBSY errors later. Try and turn + * off the append/immutable bits -- if we fail, go ahead, + * it might work. + */ +#if SUPPORT_FLAGS +#define NOCHANGEBITS (UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND) + if (to_sb.st_flags & NOCHANGEBITS) + (void)chflags(to_name, + to_sb.st_flags & ~(NOCHANGEBITS)); +#endif + if (dobackup) + backup(to_name); + else if (!dorename) + (void)unlink(to_name); + } + install(*argv, to_name, iflags); + exit(0); +} + +/* + * parseid -- + * parse uid or gid from arg into id, returning non-zero if successful + */ +static int +parseid(char *name, id_t *id) +{ + char *ep; + + errno = 0; + *id = (id_t)strtoul(name, &ep, 10); + if (errno || *ep != '\0') + return (0); + return (1); +} + +/* + * do_link -- + * make a hard link, obeying dorename if set + * return -1 on failure + */ +static int +do_link(char *from_name, char *to_name) +{ + char tmpl[MAXPATHLEN]; + int ret; + + if (dorename) { + (void)snprintf(tmpl, sizeof(tmpl), "%s.inst.XXXXXX", to_name); + /* This usage is safe. */ + if (mktemp(tmpl) == NULL) + err(1, "%s: mktemp", tmpl); + ret = link(from_name, tmpl); + if (ret == 0) { + 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(char *from_name, char *to_name) +{ + char tmpl[MAXPATHLEN]; + + if (dorename) { + (void)snprintf(tmpl, sizeof(tmpl), "%s.inst.XXXXXX", to_name); + /* This usage is safe. */ + if (mktemp(tmpl) == NULL) + err(1, "%s: mktemp", tmpl); + + if (symlink(from_name, tmpl) == -1) + err(1, "symlink %s -> %s", from_name, tmpl); + if (rename(tmpl, to_name) == -1) { + /* remove temporary link before exiting */ + (void)unlink(tmpl); + err(1, "%s: rename", to_name); + } + } else { + if (symlink(from_name, to_name) == -1) + err(1, "symlink %s -> %s", from_name, to_name); + } +} + +/* + * makelink -- + * make a link from source to destination + */ +static void +makelink(char *from_name, char *to_name) +{ + 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) == -1) { + if ((dolink & LN_HARD) || errno != EXDEV) + err(1, "link %s -> %s", from_name, to_name); + } else { + if (stat(to_name, &to_sb)) + err(1, "%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; + char *oowner, *ogroup, *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; + switch (digesttype) { + case DIGEST_MD5: + dres = MD5File(from_name, NULL); + break; + case DIGEST_RMD160: + dres = RMD160File(from_name, NULL); + break; + case DIGEST_SHA1: + dres = SHA1File(from_name, NULL); + break; + case DIGEST_SHA256: + dres = SHA256_File(from_name, NULL); + break; + case DIGEST_SHA384: + dres = SHA384_File(from_name, NULL); + break; + case DIGEST_SHA512: + dres = SHA512_File(from_name, NULL); + break; + default: + dres = NULL; + } + 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(1, "%s: realpath", from_name); + do_symlink(src, to_name); + /* 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(1, "%s: realpath", from_name); + + /* + * The last component of to_name may be a symlink, + * so use realpath to resolve only the directory. + */ + cp = xdirname(to_name); + if (realpath(cp, dst) == NULL) + err(1, "%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 = xbasename(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); + /* 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); + /* 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 + */ +static void +install(char *from_name, char *to_name, u_int flags) +{ + struct stat from_sb; + struct stat to_sb; + struct timeval tv[2]; + off_t size; + int devnull, from_fd, to_fd, serrno, tmpmode; + char *p, tmpl[MAXPATHLEN], *oto_name, *digestresult; + + size = -1; + if (!dolink) { + /* ensure that from_sb & tv are sane if !dolink */ + if (stat(from_name, &from_sb)) + err(1, "%s: stat", from_name); + size = from_sb.st_size; +#if BSD4_4 && !HAVE_NBTOOL_CONFIG_H + TIMESPEC_TO_TIMEVAL(&tv[0], &from_sb.st_atimespec); + TIMESPEC_TO_TIMEVAL(&tv[1], &from_sb.st_mtimespec); +#else + tv[0].tv_sec = from_sb.st_atime; + tv[0].tv_usec = 0; + tv[1].tv_sec = from_sb.st_mtime; + tv[1].tv_usec = 0; +#endif + } + + if (flags & DIRECTORY || strcmp(from_name, _PATH_DEVNULL) != 0) { + devnull = 0; + if (!dolink) { + if (!S_ISREG(from_sb.st_mode)) + errx(1, "%s: not a regular file", from_name); + } + /* Build the target path. */ + if (flags & DIRECTORY) { + (void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s", + to_name, + (p = strrchr(from_name, '/')) ? ++p : from_name); + to_name = pathbuf; + } + } else { + devnull = 1; + size = 0; +#if HAVE_STRUCT_STAT_ST_FLAGS + from_sb.st_flags = 0; /* XXX */ +#endif + } + + /* + * Unlink now... avoid ETXTBSY errors later. Try and turn + * off the append/immutable bits -- if we fail, go ahead, + * it might work. + */ +#if SUPPORT_FLAGS + if (stat(to_name, &to_sb) == 0 && + to_sb.st_flags & (NOCHANGEBITS)) + (void)chflags(to_name, to_sb.st_flags & ~(NOCHANGEBITS)); +#endif + if (dorename) { + (void)snprintf(tmpl, sizeof(tmpl), "%s.inst.XXXXXX", to_name); + oto_name = to_name; + to_name = tmpl; + } else { + oto_name = NULL; /* pacify gcc */ + if (dobackup) + backup(to_name); + else + (void)unlink(to_name); + } + + if (dolink) { + makelink(from_name, dorename ? oto_name : to_name); + return; + } + + /* Create target. */ + if (dorename) { + if ((to_fd = mkstemp(to_name)) == -1) + err(1, "%s: mkstemp", to_name); + } else { + if ((to_fd = open(to_name, + O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) + err(1, "%s: open", to_name); + } + digestresult = NULL; + if (!devnull) { + if ((from_fd = open(from_name, O_RDONLY, 0)) < 0) { + (void)unlink(to_name); + err(1, "%s: open", from_name); + } + digestresult = + copy(from_fd, from_name, to_fd, to_name, from_sb.st_size); + (void)close(from_fd); + } + + if (dostrip) { + strip(to_name); + + /* + * Re-open our fd on the target, in case we used a strip + * that does not work in-place -- like gnu binutils strip. + */ + close(to_fd); + if ((to_fd = open(to_name, O_RDONLY, S_IRUSR | S_IWUSR)) < 0) + err(1, "stripping %s", to_name); + + /* + * Recalculate size and digestresult after stripping. + */ + if (fstat(to_fd, &to_sb) != 0) + err(1, "%s: fstat", to_name); + size = to_sb.st_size; + digestresult = + copy(to_fd, to_name, -1, NULL, size); + + } + + if (afterinstallcmd != NULL) { + afterinstall(afterinstallcmd, to_name, 1); + + /* + * Re-open our fd on the target, in case we used an + * after-install command that does not work in-place + */ + close(to_fd); + if ((to_fd = open(to_name, O_RDONLY, S_IRUSR | S_IWUSR)) < 0) + err(1, "running after install command on %s", to_name); + } + + /* + * Set owner, group, mode for target; do the chown first, + * chown may lose the setuid bits. + */ + if (!dounpriv && + (flags & (HASUID | HASGID)) && fchown(to_fd, uid, gid) == -1) { + serrno = errno; + (void)unlink(to_name); + errx(1, "%s: chown/chgrp: %s", to_name, strerror(serrno)); + } + tmpmode = mode; + if (dounpriv) + tmpmode &= S_IRWXU|S_IRWXG|S_IRWXO; + if (fchmod(to_fd, tmpmode) == -1) { + serrno = errno; + (void)unlink(to_name); + errx(1, "%s: chmod: %s", to_name, strerror(serrno)); + } + + /* + * Preserve the date of the source file. + */ + if (dopreserve) { +#if HAVE_FUTIMES + if (futimes(to_fd, tv) == -1) + warn("%s: futimes", to_name); +#else + if (utimes(to_name, tv) == -1) + warn("%s: utimes", to_name); +#endif + } + + (void)close(to_fd); + + if (dorename) { + if (rename(to_name, oto_name) == -1) + err(1, "%s: rename", to_name); + to_name = oto_name; + } + + /* + * If provided a set of flags, set them, otherwise, preserve the + * flags, except for the dump flag. + */ +#if SUPPORT_FLAGS + if (!dounpriv && chflags(to_name, + flags & SETFLAGS ? fileflags : from_sb.st_flags & ~UF_NODUMP) == -1) + { + if (errno != EOPNOTSUPP || (from_sb.st_flags & ~UF_NODUMP) != 0) + warn("%s: chflags", to_name); + } +#endif + + metadata_log(to_name, "file", tv, NULL, digestresult, size); + free(digestresult); +} + +/* + * copy -- + * copy from one file to another, returning a digest. + * + * If to_fd < 0, just calculate a digest, don't copy. + */ +static char * +copy(int from_fd, char *from_name, int to_fd, char *to_name, off_t size) +{ + ssize_t nr, nw; + int serrno; + u_char *p; + u_char buf[MAXBSIZE]; + MD5_CTX ctxMD5; + RMD160_CTX ctxRMD160; + SHA1_CTX ctxSHA1; + SHA256_CTX ctxSHA256; + SHA384_CTX ctxSHA384; + SHA512_CTX ctxSHA512; + + switch (digesttype) { + case DIGEST_MD5: + MD5Init(&ctxMD5); + break; + case DIGEST_RMD160: + RMD160Init(&ctxRMD160); + break; + case DIGEST_SHA1: + SHA1Init(&ctxSHA1); + break; + case DIGEST_SHA256: + SHA256_Init(&ctxSHA256); + break; + case DIGEST_SHA384: + SHA384_Init(&ctxSHA384); + break; + case DIGEST_SHA512: + SHA512_Init(&ctxSHA512); + break; + case DIGEST_NONE: + if (to_fd < 0) + return NULL; /* no need to do anything */ + default: + break; + } + /* + * There's no reason to do anything other than close the file + * now if it's empty, so let's not bother. + */ + if (size > 0) { + + /* + * 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 wins some CPU back. + */ + + if (size <= 8 * 1048576) { + if ((p = mmap(NULL, (size_t)size, PROT_READ, + MAP_SHARED, from_fd, (off_t)0)) + == MAP_FAILED) { + goto mmap_failed; + } +#if defined(MADV_SEQUENTIAL) && !defined(__APPLE__) + if (madvise(p, (size_t)size, MADV_SEQUENTIAL) == -1 + && errno != EOPNOTSUPP) + warnx("madvise: %s", strerror(errno)); +#endif + + if (to_fd >= 0 && write(to_fd, p, size) != size) { + serrno = errno; + (void)unlink(to_name); + errx(1, "%s: write: %s", + to_name, strerror(serrno)); + } + switch (digesttype) { + case DIGEST_MD5: + MD5Update(&ctxMD5, p, size); + break; + case DIGEST_RMD160: + RMD160Update(&ctxRMD160, p, size); + break; + case DIGEST_SHA1: + SHA1Update(&ctxSHA1, p, size); + break; + case DIGEST_SHA256: + SHA256_Update(&ctxSHA256, p, size); + break; + case DIGEST_SHA384: + SHA384_Update(&ctxSHA384, p, size); + break; + case DIGEST_SHA512: + SHA512_Update(&ctxSHA512, p, size); + break; + default: + break; + } + (void)munmap(p, size); + } else { + mmap_failed: + while ((nr = read(from_fd, buf, sizeof(buf))) > 0) { + if (to_fd >= 0 && + (nw = write(to_fd, buf, nr)) != nr) { + serrno = errno; + (void)unlink(to_name); + errx(1, "%s: write: %s", to_name, + strerror(nw > 0 ? EIO : serrno)); + } + switch (digesttype) { + case DIGEST_MD5: + MD5Update(&ctxMD5, buf, nr); + break; + case DIGEST_RMD160: + RMD160Update(&ctxRMD160, buf, nr); + break; + case DIGEST_SHA1: + SHA1Update(&ctxSHA1, buf, nr); + break; + case DIGEST_SHA256: + SHA256_Update(&ctxSHA256, buf, nr); + break; + case DIGEST_SHA384: + SHA384_Update(&ctxSHA384, buf, nr); + break; + case DIGEST_SHA512: + SHA512_Update(&ctxSHA512, buf, nr); + break; + default: + break; + } + } + if (nr != 0) { + serrno = errno; + (void)unlink(to_name); + errx(1, "%s: read: %s", from_name, strerror(serrno)); + } + } + } + switch (digesttype) { + case DIGEST_MD5: + return MD5End(&ctxMD5, NULL); + case DIGEST_RMD160: + return RMD160End(&ctxRMD160, NULL); + case DIGEST_SHA1: + return SHA1End(&ctxSHA1, NULL); + case DIGEST_SHA256: + return SHA256_End(&ctxSHA256, NULL); + case DIGEST_SHA384: + return SHA384_End(&ctxSHA384, NULL); + case DIGEST_SHA512: + return SHA512_End(&ctxSHA512, NULL); + default: + return NULL; + } +} + +/* + * strip -- + * use strip(1) to strip the target file + */ +static void +strip(char *to_name) +{ + static const char exec_failure[] = ": exec of strip failed: "; + int serrno, status; + const char * volatile stripprog, *progname; + char *cmd; + + if ((stripprog = getenv("STRIP")) == NULL || *stripprog == '\0') { +#ifdef TARGET_STRIP + stripprog = TARGET_STRIP; +#else + stripprog = _PATH_STRIP; +#endif + } + + cmd = NULL; + + if (stripArgs) { + /* + * Build up a command line and let /bin/sh + * parse the arguments. + */ + int ret = asprintf(&cmd, "%s %s %s", stripprog, stripArgs, + to_name); + + if (ret == -1 || cmd == NULL) + err(1, "asprintf failed"); + } + + switch (vfork()) { + case -1: + serrno = errno; + (void)unlink(to_name); + errx(1, "vfork: %s", strerror(serrno)); + /*NOTREACHED*/ + case 0: + + if (stripArgs) + execl(_PATH_BSHELL, "sh", "-c", cmd, NULL); + else + execlp(stripprog, "strip", to_name, NULL); + + progname = getprogname(); + write(STDERR_FILENO, progname, strlen(progname)); + write(STDERR_FILENO, exec_failure, strlen(exec_failure)); + write(STDERR_FILENO, stripprog, strlen(stripprog)); + write(STDERR_FILENO, "\n", 1); + _exit(1); + /*NOTREACHED*/ + default: + if (wait(&status) == -1 || status) + (void)unlink(to_name); + } + + free(cmd); +} + +/* + * afterinstall -- + * run provided command on the target file or directory after it's been + * installed and stripped, but before permissions are set or it's renamed + */ +static void +afterinstall(const char *command, const char *to_name, int errunlink) +{ + int serrno, status; + char *cmd; + + switch (vfork()) { + case -1: + serrno = errno; + if (errunlink) + (void)unlink(to_name); + errx(1, "vfork: %s", strerror(serrno)); + /*NOTREACHED*/ + case 0: + /* + * build up a command line and let /bin/sh + * parse the arguments + */ + cmd = (char*)malloc(sizeof(char)* + (2+strlen(command)+ + strlen(to_name))); + + if (cmd == NULL) + errx(1, "%s", strerror(ENOMEM)); + + sprintf(cmd, "%s %s", command, to_name); + + execl(_PATH_BSHELL, "sh", "-c", cmd, NULL); + + warn("%s: exec of after install command", command); + _exit(1); + /*NOTREACHED*/ + default: + if ((wait(&status) == -1 || status) && errunlink) + (void)unlink(to_name); + } +} + +/* + * backup -- + * backup file "to_name" to to_name.suffix + * if suffix contains a "%", it's taken as a printf(3) pattern + * used for a numbered backup. + */ +static void +backup(const char *to_name) +{ + char bname[FILENAME_MAX]; + + if (numberedbackup) { + /* Do numbered backup */ + int cnt; + char suffix_expanded[FILENAME_MAX]; + + cnt=0; + do { + (void)snprintf(suffix_expanded, FILENAME_MAX, suffix, + cnt); + (void)snprintf(bname, FILENAME_MAX, "%s%s", to_name, + suffix_expanded); + cnt++; + } while (access(bname, F_OK) == 0); + } else { + /* Do simple backup */ + (void)snprintf(bname, FILENAME_MAX, "%s%s", to_name, suffix); + } + + (void)rename(to_name, bname); +} + +/* + * install_dir -- + * build directory hierarchy + */ +static void +install_dir(char *path, u_int flags) +{ + char *p; + struct stat sb; + int ch; + + for (p = path;; ++p) + if (!*p || (p != path && *p == '/')) { + ch = *p; + *p = '\0'; + if (mkdir(path, 0777) < 0) { + /* + * Can't create; path exists or no perms. + * stat() path to determine what's there now. + */ + int sverrno; + sverrno = errno; + if (stat(path, &sb) < 0) { + /* Not there; use mkdir()s error */ + errno = sverrno; + err(1, "%s: mkdir", path); + } + if (!S_ISDIR(sb.st_mode)) { + errx(1, + "%s exists but is not a directory", + path); + } + } + if (!(*p = ch)) + break; + } + + if (afterinstallcmd != NULL) + afterinstall(afterinstallcmd, path, 0); + + if (!dounpriv && ( + ((flags & (HASUID | HASGID)) && chown(path, uid, gid) == -1) + || chmod(path, mode) == -1 )) { + warn("%s: chown/chmod", 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); +} + +/* + * xbasename -- + * libc basename(3) that returns a pointer to a static buffer + * instead of overwriting that passed-in string. + */ +static char * +xbasename(char *path) +{ + static char tmp[MAXPATHLEN]; + + (void)strlcpy(tmp, path, sizeof(tmp)); + return (basename(tmp)); +} + +/* + * xdirname -- + * libc dirname(3) that returns a pointer to a static buffer + * instead of overwriting that passed-in string. + */ +static char * +xdirname(char *path) +{ + static char tmp[MAXPATHLEN]; + + (void)strlcpy(tmp, path, sizeof(tmp)); + return (dirname(tmp)); +} + +/* + * usage -- + * print a usage message and die + */ +static void +usage(void) +{ + const char *prog; + + prog = getprogname(); + + (void)fprintf(stderr, +"usage: %s [-Ubcprs] [-M log] [-D dest] [-T tags] [-B suffix]\n" +" [-a aftercmd] [-f flags] [-m mode] [-N dbdir] [-o owner] [-g group] \n" +" [-l linkflags] [-h hash] [-S stripflags] file1 file2\n" +" %s [-Ubcprs] [-M log] [-D dest] [-T tags] [-B suffix]\n" +" [-a aftercmd] [-f flags] [-m mode] [-N dbdir] [-o owner] [-g group]\n" +" [-l linkflags] [-h hash] [-S stripflags] file1 ... fileN directory\n" +" %s -d [-Up] [-M log] [-D dest] [-T tags] [-a aftercmd] [-m mode]\n" +" [-N dbdir] [-o owner] [-g group] directory ...\n", + prog, prog, prog); + exit(1); +} diff --git a/sysutils/bsdinstall/files/setmode.c b/sysutils/bsdinstall/files/setmode.c new file mode 100644 index 00000000000..8aa1ba03a74 --- /dev/null +++ b/sysutils/bsdinstall/files/setmode.c @@ -0,0 +1,498 @@ +/* $NetBSD: setmode.c,v 1.1 2013/08/28 11:42:36 jperkin Exp $ */ +/* NetBSD: setmode.c,v 1.33 2012/03/21 14:28:32 christos Exp */ + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Dave Borman at Cray Research, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(HAVE_NBCOMPAT_H) +#include <nbcompat/config.h> +#include <nbcompat/cdefs.h> +#else +#include <sys/cdefs.h> +#endif +#if defined(LIBC_SCCS) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)setmode.c 8.2 (Berkeley) 3/25/94"; +#else +__RCSID("NetBSD: setmode.c,v 1.33 2012/03/21 14:28:32 christos Exp"); +#endif +#endif /* LIBC_SCCS and not lint */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <signal.h> +#include <stdlib.h> +#include <limits.h> +#include <unistd.h> + +#ifdef SETMODE_DEBUG +#include <stdio.h> +#endif + +#ifdef __weak_alias +__weak_alias(getmode,_getmode) +__weak_alias(setmode,_setmode) +#endif + +#define SET_LEN 6 /* initial # of bitcmd struct to malloc */ +#define SET_LEN_INCR 4 /* # of bitcmd structs to add as needed */ + +typedef struct bitcmd { + char cmd; + char cmd2; + mode_t bits; +} BITCMD; + +#define CMD2_CLR 0x01 +#define CMD2_SET 0x02 +#define CMD2_GBITS 0x04 +#define CMD2_OBITS 0x08 +#define CMD2_UBITS 0x10 + +static BITCMD *addcmd(BITCMD *, mode_t, mode_t, mode_t, mode_t); +static void compress_mode(BITCMD *); +#ifdef SETMODE_DEBUG +static void dumpmode(BITCMD *); +#endif + +#ifndef S_ISTXT +#define S_ISTXT S_ISVTX +#endif + +#ifndef _DIAGASSERT +#define _DIAGASSERT assert +#endif + +/* + * Given the old mode and an array of bitcmd structures, apply the operations + * described in the bitcmd structures to the old mode, and return the new mode. + * Note that there is no '=' command; a strict assignment is just a '-' (clear + * bits) followed by a '+' (set bits). + */ +mode_t +getmode(const void *bbox, mode_t omode) +{ + const BITCMD *set; + mode_t clrval, newmode, value; + + _DIAGASSERT(bbox != NULL); + + set = (const BITCMD *)bbox; + newmode = omode; + for (value = 0;; set++) + switch(set->cmd) { + /* + * When copying the user, group or other bits around, we "know" + * where the bits are in the mode so that we can do shifts to + * copy them around. If we don't use shifts, it gets real + * grundgy with lots of single bit checks and bit sets. + */ + case 'u': + value = (newmode & S_IRWXU) >> 6; + goto common; + + case 'g': + value = (newmode & S_IRWXG) >> 3; + goto common; + + case 'o': + value = newmode & S_IRWXO; +common: if (set->cmd2 & CMD2_CLR) { + clrval = + (set->cmd2 & CMD2_SET) ? S_IRWXO : value; + if (set->cmd2 & CMD2_UBITS) + newmode &= ~((clrval<<6) & set->bits); + if (set->cmd2 & CMD2_GBITS) + newmode &= ~((clrval<<3) & set->bits); + if (set->cmd2 & CMD2_OBITS) + newmode &= ~(clrval & set->bits); + } + if (set->cmd2 & CMD2_SET) { + if (set->cmd2 & CMD2_UBITS) + newmode |= (value<<6) & set->bits; + if (set->cmd2 & CMD2_GBITS) + newmode |= (value<<3) & set->bits; + if (set->cmd2 & CMD2_OBITS) + newmode |= value & set->bits; + } + break; + + case '+': + newmode |= set->bits; + break; + + case '-': + newmode &= ~set->bits; + break; + + case 'X': + if (omode & (S_IFDIR|S_IXUSR|S_IXGRP|S_IXOTH)) + newmode |= set->bits; + break; + + case '\0': + default: +#ifdef SETMODE_DEBUG + (void)printf("getmode:%04o -> %04o\n", omode, newmode); +#endif + return (newmode); + } +} + +#define ADDCMD(a, b, c, d) do { \ + if (set >= endset) { \ + BITCMD *newset; \ + setlen += SET_LEN_INCR; \ + newset = realloc(saveset, sizeof(BITCMD) * setlen); \ + if (newset == NULL) \ + goto out; \ + set = newset + (set - saveset); \ + saveset = newset; \ + endset = newset + (setlen - 2); \ + } \ + set = addcmd(set, (mode_t)(a), (mode_t)(b), (mode_t)(c), (d)); \ +} while (/*CONSTCOND*/0) + +#define STANDARD_BITS (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO) + +void * +setmode(const char *p) +{ + int serrno; + char op, *ep; + BITCMD *set, *saveset, *endset; + sigset_t signset, sigoset; + mode_t mask, perm, permXbits, who; + long lval; + int equalopdone = 0; /* pacify gcc */ + int setlen; + + if (!*p) { + errno = EINVAL; + return NULL; + } + + /* + * Get a copy of the mask for the permissions that are mask relative. + * Flip the bits, we want what's not set. Since it's possible that + * the caller is opening files inside a signal handler, protect them + * as best we can. + */ + sigfillset(&signset); + (void)sigprocmask(SIG_BLOCK, &signset, &sigoset); + (void)umask(mask = umask(0)); + mask = ~mask; + (void)sigprocmask(SIG_SETMASK, &sigoset, NULL); + + setlen = SET_LEN + 2; + + if ((set = malloc((u_int)(sizeof(BITCMD) * setlen))) == NULL) + return (NULL); + saveset = set; + endset = set + (setlen - 2); + + /* + * If an absolute number, get it and return; disallow non-octal digits + * or illegal bits. + */ + if (isdigit((unsigned char)*p)) { + errno = 0; + lval = strtol(p, &ep, 8); + if (*ep) { + errno = EINVAL; + goto out; + } + if (errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) + goto out; + if (lval & ~(STANDARD_BITS|S_ISTXT)) { + errno = EINVAL; + goto out; + } + perm = (mode_t)lval; + ADDCMD('=', (STANDARD_BITS|S_ISTXT), perm, mask); + set->cmd = 0; + return (saveset); + } + + /* + * Build list of structures to set/clear/copy bits as described by + * each clause of the symbolic mode. + */ + for (;;) { + /* First, find out which bits might be modified. */ + for (who = 0;; ++p) { + switch (*p) { + case 'a': + who |= STANDARD_BITS; + break; + case 'u': + who |= S_ISUID|S_IRWXU; + break; + case 'g': + who |= S_ISGID|S_IRWXG; + break; + case 'o': + who |= S_IRWXO; + break; + default: + goto getop; + } + } + +getop: if ((op = *p++) != '+' && op != '-' && op != '=') { + errno = EINVAL; + goto out; + } + if (op == '=') + equalopdone = 0; + + who &= ~S_ISTXT; + for (perm = 0, permXbits = 0;; ++p) { + switch (*p) { + case 'r': + perm |= S_IRUSR|S_IRGRP|S_IROTH; + break; + case 's': + /* + * If specific bits where requested and + * only "other" bits ignore set-id. + */ + if (who == 0 || (who & ~S_IRWXO)) + perm |= S_ISUID|S_ISGID; + break; + case 't': + /* + * If specific bits where requested and + * only "other" bits ignore set-id. + */ + if (who == 0 || (who & ~S_IRWXO)) { + who |= S_ISTXT; + perm |= S_ISTXT; + } + break; + case 'w': + perm |= S_IWUSR|S_IWGRP|S_IWOTH; + break; + case 'X': + permXbits = S_IXUSR|S_IXGRP|S_IXOTH; + break; + case 'x': + perm |= S_IXUSR|S_IXGRP|S_IXOTH; + break; + case 'u': + case 'g': + case 'o': + /* + * When ever we hit 'u', 'g', or 'o', we have + * to flush out any partial mode that we have, + * and then do the copying of the mode bits. + */ + if (perm) { + ADDCMD(op, who, perm, mask); + perm = 0; + } + if (op == '=') + equalopdone = 1; + if (op == '+' && permXbits) { + ADDCMD('X', who, permXbits, mask); + permXbits = 0; + } + ADDCMD(*p, who, op, mask); + break; + + default: + /* + * Add any permissions that we haven't already + * done. + */ + if (perm || (op == '=' && !equalopdone)) { + if (op == '=') + equalopdone = 1; + ADDCMD(op, who, perm, mask); + perm = 0; + } + if (permXbits) { + ADDCMD('X', who, permXbits, mask); + permXbits = 0; + } + goto apply; + } + } + +apply: if (!*p) + break; + if (*p != ',') + goto getop; + ++p; + } + set->cmd = 0; +#ifdef SETMODE_DEBUG + (void)printf("Before compress_mode()\n"); + dumpmode(saveset); +#endif + compress_mode(saveset); +#ifdef SETMODE_DEBUG + (void)printf("After compress_mode()\n"); + dumpmode(saveset); +#endif + return (saveset); +out: + serrno = errno; + free(saveset); + errno = serrno; + return NULL; +} + +static BITCMD * +addcmd(BITCMD *set, mode_t op, mode_t who, mode_t oparg, mode_t mask) +{ + + _DIAGASSERT(set != NULL); + + switch (op) { + case '=': + set->cmd = '-'; + set->bits = who ? who : STANDARD_BITS; + set++; + + op = '+'; + /* FALLTHROUGH */ + case '+': + case '-': + case 'X': + set->cmd = op; + set->bits = (who ? who : mask) & oparg; + break; + + case 'u': + case 'g': + case 'o': + set->cmd = op; + if (who) { + set->cmd2 = ((who & S_IRUSR) ? CMD2_UBITS : 0) | + ((who & S_IRGRP) ? CMD2_GBITS : 0) | + ((who & S_IROTH) ? CMD2_OBITS : 0); + set->bits = (mode_t)~0; + } else { + set->cmd2 = CMD2_UBITS | CMD2_GBITS | CMD2_OBITS; + set->bits = mask; + } + + if (oparg == '+') + set->cmd2 |= CMD2_SET; + else if (oparg == '-') + set->cmd2 |= CMD2_CLR; + else if (oparg == '=') + set->cmd2 |= CMD2_SET|CMD2_CLR; + break; + } + return (set + 1); +} + +#ifdef SETMODE_DEBUG +static void +dumpmode(BITCMD *set) +{ + + _DIAGASSERT(set != NULL); + + for (; set->cmd; ++set) + (void)printf("cmd: '%c' bits %04o%s%s%s%s%s%s\n", + set->cmd, set->bits, set->cmd2 ? " cmd2:" : "", + set->cmd2 & CMD2_CLR ? " CLR" : "", + set->cmd2 & CMD2_SET ? " SET" : "", + set->cmd2 & CMD2_UBITS ? " UBITS" : "", + set->cmd2 & CMD2_GBITS ? " GBITS" : "", + set->cmd2 & CMD2_OBITS ? " OBITS" : ""); +} +#endif + +/* + * Given an array of bitcmd structures, compress by compacting consecutive + * '+', '-' and 'X' commands into at most 3 commands, one of each. The 'u', + * 'g' and 'o' commands continue to be separate. They could probably be + * compacted, but it's not worth the effort. + */ +static void +compress_mode(BITCMD *set) +{ + BITCMD *nset; + int setbits, clrbits, Xbits, op; + + _DIAGASSERT(set != NULL); + + for (nset = set;;) { + /* Copy over any 'u', 'g' and 'o' commands. */ + while ((op = nset->cmd) != '+' && op != '-' && op != 'X') { + *set++ = *nset++; + if (!op) + return; + } + + for (setbits = clrbits = Xbits = 0;; nset++) { + if ((op = nset->cmd) == '-') { + clrbits |= nset->bits; + setbits &= ~nset->bits; + Xbits &= ~nset->bits; + } else if (op == '+') { + setbits |= nset->bits; + clrbits &= ~nset->bits; + Xbits &= ~nset->bits; + } else if (op == 'X') + Xbits |= nset->bits & ~setbits; + else + break; + } + if (clrbits) { + set->cmd = '-'; + set->cmd2 = 0; + set->bits = clrbits; + set++; + } + if (setbits) { + set->cmd = '+'; + set->cmd2 = 0; + set->bits = setbits; + set++; + } + if (Xbits) { + set->cmd = 'X'; + set->cmd2 = 0; + set->bits = Xbits; + set++; + } + } +} |