From c047a4e458c93d335bbd7bb8780f9a16e5674966 Mon Sep 17 00:00:00 2001 From: Ishimoto Shinobu Date: Sun, 26 Jul 2020 13:50:52 +0300 Subject: [PATCH] add xinstall Signed-off-by: Ishimoto Shinobu --- .gitignore | 2 + configure.ac | 3 +- include/bsd.h | 23 +- src/Makefile.am | 2 +- src/libopenbsd/Makefile.am | 3 + src/libopenbsd/pwcache.c | 439 ++++++++++++++++++++ src/libopenbsd/setmode.c | 421 +++++++++++++++++++ src/libopenbsd/strtofflags.c | 151 +++++++ src/xinstall/Makefile.am | 6 + src/xinstall/install.1 | 215 ++++++++++ src/xinstall/pathnames.h | 36 ++ src/xinstall/xinstall.c | 785 +++++++++++++++++++++++++++++++++++ 12 files changed, 2083 insertions(+), 3 deletions(-) create mode 100644 src/libopenbsd/pwcache.c create mode 100644 src/libopenbsd/setmode.c create mode 100644 src/libopenbsd/strtofflags.c create mode 100644 src/xinstall/Makefile.am create mode 100644 src/xinstall/install.1 create mode 100644 src/xinstall/pathnames.h create mode 100644 src/xinstall/xinstall.c diff --git a/.gitignore b/.gitignore index 3bbc207..77c67c8 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ src/users/users src/ln/ln src/stty/stty src/fmt/fmt +src/xinstall/install src/cmp/Makefile src/diff/Makefile src/diff3/Makefile @@ -54,5 +55,6 @@ src/users/Makefile src/ln/Makefile src/fmt/Makefile src/stty/Makefile +src/xinstall/Makefile src/Makefile ylwrap diff --git a/configure.ac b/configure.ac index b829ef3..b95d676 100644 --- a/configure.ac +++ b/configure.ac @@ -25,5 +25,6 @@ AC_CONFIG_FILES([Makefile src/users/Makefile src/ln/Makefile src/stty/Makefile - src/fmt/Makefile]) + src/fmt/Makefile + src/xinstall/Makefile]) AC_OUTPUT diff --git a/include/bsd.h b/include/bsd.h index 619d0ba..3a71ecc 100644 --- a/include/bsd.h +++ b/include/bsd.h @@ -84,6 +84,23 @@ #define NMEADISC 7 /* NMEA0183 discipline */ /* TTY SHIT END */ +/* stat */ +#define S_BLKSIZE 512 /* block size used in the stat struct */ +#define UF_NODUMP 0x00000001 /* do not dump file */ +#define UF_IMMUTABLE 0x00000002 /* file may not be changed */ +#define UF_APPEND 0x00000004 /* writes to file may only append */ +#define UF_OPAQUE 0x00000008 /* directory is opaque wrt. union */ +#define SF_IMMUTABLE 0x00020000 /* file may not be changed */ +#define SF_APPEND 0x00040000 /* writes to file may only append */ +#define SF_ARCHIVED 0x00010000 /* file is archived */ +/* stat end */ + +#define EFTYPE 79 /* Inappropriate file type or format */ +#define MAXBSIZE (64 * 1024) +#define S_ISTXT S_ISVTX +#define _PW_BUF_LEN 1024 /* length of getpw*_r buffer */ +#define _GR_BUF_LEN (1024+200*sizeof(char*)) + typedef va_list __va_list; struct __sFILE; @@ -110,4 +127,8 @@ __dead void verrc(int, int, const char *, __va_list) __attribute__((__format__ (printf, 3, 0))); extern const char* getprogname(void); extern void setprogname(const char*); - +int strtofflags(char **, u_int32_t *, u_int32_t *); +mode_t getmode(const void *, mode_t); +void *setmode(const char *); +int gid_from_group(const char *, gid_t *); +int uid_from_user(const char *, uid_t *); diff --git a/src/Makefile.am b/src/Makefile.am index 8a06a59..377fd49 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1 +1 @@ -SUBDIRS = libopenbsd cmp diff diff3 sdiff doas indent pr users ln stty fmt +SUBDIRS = libopenbsd cmp diff diff3 sdiff doas indent pr users ln stty fmt xinstall diff --git a/src/libopenbsd/Makefile.am b/src/libopenbsd/Makefile.am index 16f75fb..4598c2a 100644 --- a/src/libopenbsd/Makefile.am +++ b/src/libopenbsd/Makefile.am @@ -6,11 +6,14 @@ libopenbsd_a_SOURCES = \ fgetln.c \ fparseln.c \ getprogname.c \ + pwcache.c \ readpassphrase.c \ reallocarray.c \ + setmode.c \ setprogname.c \ strlcat.c \ strlcpy.c \ + strtofflags.c \ strtonum.c \ verrc.c \ vwarnc.c \ diff --git a/src/libopenbsd/pwcache.c b/src/libopenbsd/pwcache.c new file mode 100644 index 0000000..302f70b --- /dev/null +++ b/src/libopenbsd/pwcache.c @@ -0,0 +1,439 @@ +/* $OpenBSD: pwcache.c,v 1.15 2018/09/22 02:47:23 millert Exp $ */ + +/*- + * Copyright (c) 1992 Keith Muller. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * 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. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * Constants and data structures used to implement group and password file + * caches. Name lengths have been chosen to be as large as those supported + * by the passwd and group files as well as the standard archive formats. + * CACHE SIZES MUST BE PRIME + */ +#define UNMLEN 32 /* >= user name found in any protocol */ +#define GNMLEN 32 /* >= group name found in any protocol */ +#define UID_SZ 317 /* size of uid to user_name cache */ +#define UNM_SZ 317 /* size of user_name to uid cache */ +#define GID_SZ 251 /* size of gid to group_name cache */ +#define GNM_SZ 251 /* size of group_name to gid cache */ +#define VALID 1 /* entry and name are valid */ +#define INVALID 2 /* entry valid, name NOT valid */ + +/* + * Node structures used in the user, group, uid, and gid caches. + */ + +typedef struct uidc { + int valid; /* is this a valid or a miss entry */ + char name[UNMLEN]; /* uid name */ + uid_t uid; /* cached uid */ +} UIDC; + +typedef struct gidc { + int valid; /* is this a valid or a miss entry */ + char name[GNMLEN]; /* gid name */ + gid_t gid; /* cached gid */ +} GIDC; + +/* + * Routines that control user, group, uid and gid caches. + * Traditional passwd/group cache routines perform quite poorly with + * archives. The chances of hitting a valid lookup with an archive is quite a + * bit worse than with files already resident on the file system. These misses + * create a MAJOR performance cost. To adress this problem, these routines + * cache both hits and misses. + */ + +static UIDC **uidtb; /* uid to name cache */ +static GIDC **gidtb; /* gid to name cache */ +static UIDC **usrtb; /* user name to uid cache */ +static GIDC **grptb; /* group name to gid cache */ + +static u_int +st_hash(const char *name, size_t len, int tabsz) +{ + u_int key = 0; + + assert(name != NULL); + + while (len--) { + key += *name++; + key = (key << 8) | (key >> 24); + } + + return key % tabsz; +} + +/* + * uidtb_start + * creates an an empty uidtb + * Return: + * 0 if ok, -1 otherwise + */ +static int +uidtb_start(void) +{ + static int fail = 0; + + if (uidtb != NULL) + return 0; + if (fail) + return -1; + if ((uidtb = calloc(UID_SZ, sizeof(UIDC *))) == NULL) { + ++fail; + return -1; + } + return 0; +} + +/* + * gidtb_start + * creates an an empty gidtb + * Return: + * 0 if ok, -1 otherwise + */ +static int +gidtb_start(void) +{ + static int fail = 0; + + if (gidtb != NULL) + return 0; + if (fail) + return -1; + if ((gidtb = calloc(GID_SZ, sizeof(GIDC *))) == NULL) { + ++fail; + return -1; + } + return 0; +} + +/* + * usrtb_start + * creates an an empty usrtb + * Return: + * 0 if ok, -1 otherwise + */ +static int +usrtb_start(void) +{ + static int fail = 0; + + if (usrtb != NULL) + return 0; + if (fail) + return -1; + if ((usrtb = calloc(UNM_SZ, sizeof(UIDC *))) == NULL) { + ++fail; + return -1; + } + return 0; +} + +/* + * grptb_start + * creates an an empty grptb + * Return: + * 0 if ok, -1 otherwise + */ +static int +grptb_start(void) +{ + static int fail = 0; + + if (grptb != NULL) + return 0; + if (fail) + return -1; + if ((grptb = calloc(GNM_SZ, sizeof(GIDC *))) == NULL) { + ++fail; + return -1; + } + return 0; +} + +/* + * user_from_uid() + * caches the name (if any) for the uid. If noname clear, we always + * return the stored name (if valid or invalid match). + * We use a simple hash table. + * Return: + * Pointer to stored name (or a empty string) + */ +const char * +user_from_uid(uid_t uid, int noname) +{ + struct passwd pwstore, *pw = NULL; + char pwbuf[_PW_BUF_LEN]; + UIDC **pptr, *ptr = NULL; + + if ((uidtb != NULL) || (uidtb_start() == 0)) { + /* + * see if we have this uid cached + */ + pptr = uidtb + (uid % UID_SZ); + ptr = *pptr; + + if ((ptr != NULL) && (ptr->valid > 0) && (ptr->uid == uid)) { + /* + * have an entry for this uid + */ + if (!noname || (ptr->valid == VALID)) + return ptr->name; + return NULL; + } + + if (ptr == NULL) + *pptr = ptr = malloc(sizeof(UIDC)); + } + + getpwuid_r(uid, &pwstore, pwbuf, sizeof(pwbuf), &pw); + if (pw == NULL) { + /* + * no match for this uid in the local password file + * a string that is the uid in numeric format + */ + if (ptr == NULL) + return NULL; + ptr->uid = uid; + (void)snprintf(ptr->name, UNMLEN, "%u", uid); + ptr->valid = INVALID; + if (noname) + return NULL; + } else { + /* + * there is an entry for this uid in the password file + */ + if (ptr == NULL) + return pw->pw_name; + ptr->uid = uid; + (void)strlcpy(ptr->name, pw->pw_name, sizeof(ptr->name)); + ptr->valid = VALID; + } + return ptr->name; +} + +/* + * group_from_gid() + * caches the name (if any) for the gid. If noname clear, we always + * return the stored name (if valid or invalid match). + * We use a simple hash table. + * Return: + * Pointer to stored name (or a empty string) + */ +const char * +group_from_gid(gid_t gid, int noname) +{ + struct group grstore, *gr = NULL; + char grbuf[_GR_BUF_LEN]; + GIDC **pptr, *ptr = NULL; + + if ((gidtb != NULL) || (gidtb_start() == 0)) { + /* + * see if we have this gid cached + */ + pptr = gidtb + (gid % GID_SZ); + ptr = *pptr; + + if ((ptr != NULL) && (ptr->valid > 0) && (ptr->gid == gid)) { + /* + * have an entry for this gid + */ + if (!noname || (ptr->valid == VALID)) + return ptr->name; + return NULL; + } + + if (ptr == NULL) + *pptr = ptr = malloc(sizeof(GIDC)); + } + + getgrgid_r(gid, &grstore, grbuf, sizeof(grbuf), &gr); + if (gr == NULL) { + /* + * no match for this gid in the local group file, put in + * a string that is the gid in numeric format + */ + if (ptr == NULL) + return NULL; + ptr->gid = gid; + (void)snprintf(ptr->name, GNMLEN, "%u", gid); + ptr->valid = INVALID; + if (noname) + return NULL; + } else { + /* + * there is an entry for this group in the group file + */ + if (ptr == NULL) + return gr->gr_name; + ptr->gid = gid; + (void)strlcpy(ptr->name, gr->gr_name, sizeof(ptr->name)); + ptr->valid = VALID; + } + return ptr->name; +} + +/* + * uid_from_user() + * caches the uid for a given user name. We use a simple hash table. + * Return: + * 0 if the user name is found (filling in uid), -1 otherwise + */ +int +uid_from_user(const char *name, uid_t *uid) +{ + struct passwd pwstore, *pw = NULL; + char pwbuf[_PW_BUF_LEN]; + UIDC **pptr, *ptr = NULL; + size_t namelen; + + /* + * return -1 for mangled names + */ + if (name == NULL || ((namelen = strlen(name)) == 0)) + return -1; + + if ((usrtb != NULL) || (usrtb_start() == 0)) { + /* + * look up in hash table, if found and valid return the uid, + * if found and invalid, return a -1 + */ + pptr = usrtb + st_hash(name, namelen, UNM_SZ); + ptr = *pptr; + + if ((ptr != NULL) && (ptr->valid > 0) && + strcmp(name, ptr->name) == 0) { + if (ptr->valid == INVALID) + return -1; + *uid = ptr->uid; + return 0; + } + + if (ptr == NULL) + *pptr = ptr = malloc(sizeof(UIDC)); + } + + /* + * no match, look it up, if no match store it as an invalid entry, + * or store the matching uid + */ + getpwnam_r(name, &pwstore, pwbuf, sizeof(pwbuf), &pw); + if (ptr == NULL) { + if (pw == NULL) + return -1; + *uid = pw->pw_uid; + return 0; + } + (void)strlcpy(ptr->name, name, sizeof(ptr->name)); + if (pw == NULL) { + ptr->valid = INVALID; + return -1; + } + ptr->valid = VALID; + *uid = ptr->uid = pw->pw_uid; + return 0; +} + +/* + * gid_from_group() + * caches the gid for a given group name. We use a simple hash table. + * Return: + * 0 if the group name is found (filling in gid), -1 otherwise + */ +int +gid_from_group(const char *name, gid_t *gid) +{ + struct group grstore, *gr = NULL; + char grbuf[_GR_BUF_LEN]; + GIDC **pptr, *ptr = NULL; + size_t namelen; + + /* + * return -1 for mangled names + */ + if (name == NULL || ((namelen = strlen(name)) == 0)) + return -1; + + if ((grptb != NULL) || (grptb_start() == 0)) { + /* + * look up in hash table, if found and valid return the uid, + * if found and invalid, return a -1 + */ + pptr = grptb + st_hash(name, namelen, GID_SZ); + ptr = *pptr; + + if ((ptr != NULL) && (ptr->valid > 0) && + strcmp(name, ptr->name) == 0) { + if (ptr->valid == INVALID) + return -1; + *gid = ptr->gid; + return 0; + } + + if (ptr == NULL) + *pptr = ptr = malloc(sizeof(GIDC)); + } + + /* + * no match, look it up, if no match store it as an invalid entry, + * or store the matching gid + */ + getgrnam_r(name, &grstore, grbuf, sizeof(grbuf), &gr); + if (ptr == NULL) { + if (gr == NULL) + return -1; + *gid = gr->gr_gid; + return 0; + } + + (void)strlcpy(ptr->name, name, sizeof(ptr->name)); + if (gr == NULL) { + ptr->valid = INVALID; + return -1; + } + ptr->valid = VALID; + *gid = ptr->gid = gr->gr_gid; + return 0; +} diff --git a/src/libopenbsd/setmode.c b/src/libopenbsd/setmode.c new file mode 100644 index 0000000..3245278 --- /dev/null +++ b/src/libopenbsd/setmode.c @@ -0,0 +1,421 @@ +/* $OpenBSD: setmode.c,v 1.22 2014/10/11 04:14:35 deraadt Exp $ */ +/* $NetBSD: setmode.c,v 1.15 1997/02/07 22:21:06 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. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#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 *, int, int, int, u_int); +static void compress_mode(BITCMD *); +#ifdef SETMODE_DEBUG +static void dumpmode(BITCMD *); +#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; + + 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: + return (newmode); + } +} + +#define ADDCMD(a, b, c, d) \ + if (set >= endset) { \ + BITCMD *newset; \ + setlen += SET_LEN_INCR; \ + newset = reallocarray(saveset, setlen, sizeof(BITCMD)); \ + if (newset == NULL) { \ + free(saveset); \ + return (NULL); \ + } \ + set = newset + (set - saveset); \ + saveset = newset; \ + endset = newset + (setlen - 2); \ + } \ + set = addcmd(set, (a), (b), (c), (d)) + +#define STANDARD_BITS (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO) + +void * +setmode(const char *p) +{ + char op, *ep; + BITCMD *set, *saveset, *endset; + sigset_t sigset, sigoset; + mode_t mask, perm, permXbits, who; + int equalopdone, setlen; + u_long perml; + + 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(&sigset); + (void)sigprocmask(SIG_BLOCK, &sigset, &sigoset); + (void)umask(mask = umask(0)); + mask = ~mask; + (void)sigprocmask(SIG_SETMASK, &sigoset, NULL); + + setlen = SET_LEN + 2; + + if ((set = calloc((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)) { + perml = strtoul(p, &ep, 8); + /* The test on perml will also catch overflow. */ + if (*ep != '\0' || (perml & ~(STANDARD_BITS|S_ISTXT))) { + free(saveset); + errno = ERANGE; + return (NULL); + } + perm = (mode_t)perml; + 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 != '=') { + free(saveset); + errno = EINVAL; + return (NULL); + } + 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 sticky. + */ + 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; + compress_mode(saveset); + return (saveset); +} + +static BITCMD * +addcmd(BITCMD *set, int op, int who, int oparg, u_int mask) +{ + 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); +} + +/* + * 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; + + 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++; + } + } +} diff --git a/src/libopenbsd/strtofflags.c b/src/libopenbsd/strtofflags.c new file mode 100644 index 0000000..509d7f3 --- /dev/null +++ b/src/libopenbsd/strtofflags.c @@ -0,0 +1,151 @@ +/* $OpenBSD: strtofflags.c,v 1.7 2015/08/20 21:49:29 deraadt Exp $ */ + +/*- + * Copyright (c) 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. + */ + +#include +#include + +#include +#include +#include +#include + +#include + +static const struct { + char *name; + u_int32_t flag; + int invert; +} mapping[] = { + /* shorter names per flag first, all prefixed by "no" */ + { "nosappnd", SF_APPEND, 0 }, + { "nosappend", SF_APPEND, 0 }, + { "noarch", SF_ARCHIVED, 0 }, + { "noarchived", SF_ARCHIVED, 0 }, + { "noschg", SF_IMMUTABLE, 0 }, + { "noschange", SF_IMMUTABLE, 0 }, + { "nosimmutable", SF_IMMUTABLE, 0 }, +#ifdef __FreeBSD__ + { "nosunlnk", SF_NOUNLINK, 0 }, + { "nosunlink", SF_NOUNLINK, 0 }, +#endif + { "nouappnd", UF_APPEND, 0 }, + { "nouappend", UF_APPEND, 0 }, + { "nouchg", UF_IMMUTABLE, 0 }, + { "nouchange", UF_IMMUTABLE, 0 }, + { "nouimmutable", UF_IMMUTABLE, 0 }, + { "nodump", UF_NODUMP, 1 }, + { "noopaque", UF_OPAQUE, 0 }, +#ifdef __FreeBSD__ + { "nouunlnk", UF_NOUNLINK, 0 }, + { "nouunlink", UF_NOUNLINK, 0 } +#endif +}; +#define longestflaglen 12 +#define nmappings (sizeof(mapping) / sizeof(mapping[0])) + +/* + * fflagstostr -- + * Convert file flags to a comma-separated string. If no flags + * are set, return the empty string. + */ +char * +fflagstostr(u_int32_t flags) +{ + char *string; + char *sp, *dp; + u_int32_t setflags; + int i; + + if ((string = calloc(nmappings, longestflaglen + 1)) == NULL) + return (NULL); + + setflags = flags; + dp = string; + for (i = 0; i < nmappings; i++) { + if (setflags & mapping[i].flag) { + if (dp > string) + *dp++ = ','; + for (sp = mapping[i].invert ? mapping[i].name : + mapping[i].name + 2; *sp; *dp++ = *sp++) ; + setflags &= ~mapping[i].flag; + } + } + *dp = '\0'; + return (string); +} + +/* + * strtofflags -- + * Take string of arguments and return file flags. Return 0 on + * success, 1 on failure. On failure, stringp is set to point + * to the offending token. + */ +int +strtofflags(char **stringp, u_int32_t *setp, u_int32_t *clrp) +{ + char *string, *p; + int i; + + if (setp) + *setp = 0; + if (clrp) + *clrp = 0; + string = *stringp; + while ((p = strsep(&string, "\t ,")) != NULL) { + *stringp = p; + if (*p == '\0') + continue; + for (i = 0; i < nmappings; i++) { + if (strcmp(p, mapping[i].name + 2) == 0) { + if (mapping[i].invert) { + if (clrp) + *clrp |= mapping[i].flag; + } else { + if (setp) + *setp |= mapping[i].flag; + } + break; + } else if (strcmp(p, mapping[i].name) == 0) { + if (mapping[i].invert) { + if (setp) + *setp |= mapping[i].flag; + } else { + if (clrp) + *clrp |= mapping[i].flag; + } + break; + } + } + if (i == nmappings) + return 1; + } + return 0; +} diff --git a/src/xinstall/Makefile.am b/src/xinstall/Makefile.am new file mode 100644 index 0000000..c27fc11 --- /dev/null +++ b/src/xinstall/Makefile.am @@ -0,0 +1,6 @@ +AM_CPPFLAGS = -I$(top_srcdir)/include + +bin_PROGRAMS = install +install_SOURCES = xinstall.c +install_LDADD = ../libopenbsd/libopenbsd.a +man1_MANS = install.1 diff --git a/src/xinstall/install.1 b/src/xinstall/install.1 new file mode 100644 index 0000000..7c89554 --- /dev/null +++ b/src/xinstall/install.1 @@ -0,0 +1,215 @@ +.\" $OpenBSD: install.1,v 1.31 2019/02/08 12:53:44 schwarze Exp $ +.\" $NetBSD: install.1,v 1.4 1994/11/14 04:57:17 jtc 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 $Mdocdate: February 8 2019 $ +.Dt INSTALL 1 +.Os +.Sh NAME +.Nm install +.Nd install binaries +.Sh SYNOPSIS +.Nm install +.Op Fl bCcDdFpSs +.Op Fl B Ar suffix +.Op Fl f Ar flags +.Op Fl g Ar group +.Op Fl m Ar mode +.Op Fl o Ar owner +.Ar source ... target ... +.Sh DESCRIPTION +The +.Ar source +file(s) are copied to the +.Ar target +file or directory. +If the +.Ar 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. +If the +.Fl d +option is given, +.Ar target +directories are created, and no files are copied. +.Pp +The options are as follows: +.Bl -tag -width "-B suffix" +.It Fl B Ar suffix +Use +.Ar suffix +as the backup suffix if +.Fl b +is given. +.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. +If the target file already exists and the files are the same, +then don't change the modification time of the target. +.It Fl c +Copy the file. +This is actually the default. +The +.Fl c +option is only included for backwards compatibility. +.It Fl D +Create all leading components of the target before installing into it. +.It Fl d +Create directories. +Missing parent directories are created as required. +This option cannot be used with the +.Fl B , b , C , c , +.Fl f , p , +or +.Fl s +options. +.It Fl F +Flush the file's contents to disk. +When copying a file, use the +.Xr fsync 2 +function to synchronize the installed file's contents with the +on-disk version. +.It Fl f Ar flags +Specify the target's file +.Ar flags . +(See +.Xr chflags 1 +for a list of possible flags and their meanings.) +.It Fl g Ar group +Specify a +.Ar group . +A numeric GID is allowed. +.It Fl m Ar mode +Specify an alternate +.Ar 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 o Ar owner +Specify an +.Ar owner . +A numeric UID is allowed. +.It Fl p +Preserve the modification time. +Copy the file, as if the +.Fl C +(compare and copy) option is specified, +except if the target file doesn't already exist or is different, +then preserve the modification time of the file. +.It Fl S +Safe copy. +This option has no effect and is supported only for compatibility. +When installing a file, a temporary file is created and written first +in the destination directory, then atomically renamed. +This avoids both race conditions and the destruction of existing +files in case of write failures. +.It Fl s +.Nm +exec's the command +.Pa /usr/bin/strip +to strip binaries so that install can be portable over a large +number of systems and binary types. +If the environment variable +.Ev STRIP +is set, it is used instead. +.El +.Pp +By default, +.Nm +preserves all file flags, with the exception of the +.Dq nodump +flag. +.Pp +The +.Nm +utility attempts to prevent moving a file onto itself. +.Pp +Installing +.Pa /dev/null +creates an empty file. +.Sh ENVIRONMENT +.Bl -tag -width "STRIP" +.It Ev STRIP +For an alternate +.Xr strip 1 +program to run. +Default is +.Pa /usr/bin/strip . +.El +.Sh FILES +.Bl -tag -width INS@XXXXXXXXXX -compact +.It Pa INS@XXXXXXXXXX +Temporary files created in the target directory by +.Xr mkstemp 3 . +.El +.Sh EXIT STATUS +.Ex -std install +.Sh SEE ALSO +.Xr chflags 1 , +.Xr chgrp 1 , +.Xr chmod 1 , +.Xr cp 1 , +.Xr mv 1 , +.Xr strip 1 , +.Xr chown 8 +.Sh HISTORY +The +.Nm +utility appeared in +.Bx 4.2 . +.Sh CAVEATS +The +.Fl C , +.Fl D , +.Fl F , +.Fl p , +and +.Fl S +flags are non-standard and should not be relied upon for portability. +.Pp +Temporary files may be left in the target directory if +.Nm +exits abnormally. diff --git a/src/xinstall/pathnames.h b/src/xinstall/pathnames.h new file mode 100644 index 0000000..3c576ac --- /dev/null +++ b/src/xinstall/pathnames.h @@ -0,0 +1,36 @@ +/* $OpenBSD: pathnames.h,v 1.3 2003/06/03 02:56:24 millert Exp $ */ +/* $NetBSD: pathnames.h,v 1.3 1994/11/14 04:57:18 jtc Exp $ */ + +/* + * Copyright (c) 1989, 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. + * + * @(#)pathnames.h 8.1 (Berkeley) 6/6/93 + * $NetBSD: pathnames.h,v 1.3 1994/11/14 04:57:18 jtc Exp $ + */ + +#define _PATH_STRIP "/usr/bin/strip" diff --git a/src/xinstall/xinstall.c b/src/xinstall/xinstall.c new file mode 100644 index 0000000..8f5c470 --- /dev/null +++ b/src/xinstall/xinstall.c @@ -0,0 +1,785 @@ +/* $OpenBSD: xinstall.c,v 1.74 2020/04/07 09:40:09 espie Exp $ */ +/* $NetBSD: xinstall.c,v 1.9 1995/12/20 10:25:17 jonathan 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. + */ + +#include /* MAXBSIZE */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "pathnames.h" + +#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) + +#define DIRECTORY 0x01 /* Tell install it's a directory. */ +#define SETFLAGS 0x02 /* Tell install to set flags. */ +#define USEFSYNC 0x04 /* Tell install to use fsync(2). */ +#define NOCHANGEBITS (UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND) +#define BACKUP_SUFFIX ".old" + +int dobackup, docompare, dodest, dodir, dopreserve, dostrip; +int mode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH; +char pathbuf[PATH_MAX], tempfile[PATH_MAX]; +char *suffix = BACKUP_SUFFIX; +uid_t uid = (uid_t)-1; +gid_t gid = (gid_t)-1; + +void copy(int, char *, int, char *, off_t, int); +int compare(int, const char *, off_t, int, const char *, off_t); +void install(char *, char *, u_long, u_int); +void install_dir(char *, int); +void strip(char *); +void usage(void); +int create_tempfile(char *, char *, size_t); +int file_write(int, char *, size_t, int *, int *, int); +void file_flush(int, int); + +int +main(int argc, char *argv[]) +{ + struct stat from_sb, to_sb; + void *set; + u_int32_t fset; + u_int iflags; + int ch, no_target; + char *flags, *to_name, *group = NULL, *owner = NULL; + const char *errstr; + + iflags = 0; + while ((ch = getopt(argc, argv, "B:bCcDdFf:g:m:o:pSs")) != -1) + switch(ch) { + case 'C': + docompare = 1; + break; + case 'B': + suffix = optarg; + /* fall through; -B implies -b */ + case 'b': + dobackup = 1; + break; + case 'c': + /* For backwards compatibility. */ + break; + case 'F': + iflags |= USEFSYNC; + break; + case 'f': + flags = optarg; + if (strtofflags(&flags, &fset, NULL)) + errx(1, "%s: invalid flag", flags); + iflags |= SETFLAGS; + break; + case 'g': + group = optarg; + break; + case 'm': + if (!(set = setmode(optarg))) + errx(1, "%s: invalid file mode", optarg); + mode = getmode(set, 0); + free(set); + break; + case 'o': + owner = optarg; + break; + case 'p': + docompare = dopreserve = 1; + break; + case 'S': + /* For backwards compatibility. */ + break; + case 's': + dostrip = 1; + break; + case 'D': + dodest = 1; + break; + case 'd': + dodir = 1; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + /* some options make no sense when creating directories */ + if ((docompare || dostrip) && dodir) + usage(); + + /* must have at least two arguments, except when creating directories */ + if (argc == 0 || (argc == 1 && !dodir)) + usage(); + + /* get group and owner id's */ + if (group != NULL && gid_from_group(group, &gid) == -1) { + gid = strtonum(group, 0, GID_MAX, &errstr); + if (errstr != NULL) + errx(1, "unknown group %s", group); + } + if (owner != NULL && uid_from_user(owner, &uid) == -1) { + uid = strtonum(owner, 0, UID_MAX, &errstr); + if (errstr != NULL) + errx(1, "unknown user %s", owner); + } + + if (dodir) { + for (; *argv != NULL; ++argv) + install_dir(*argv, mode); + exit(0); + /* NOTREACHED */ + } + + if (dodest) { + char *dest = dirname(argv[argc - 1]); + if (dest == NULL) + errx(1, "cannot determine dirname"); + /* + * When -D is passed, do not chmod the directory with the mode set for + * the target file. If more restrictive permissions are required then + * '-d -m' ought to be used instead. + */ + install_dir(dest, 0755); + } + + 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, fset, iflags | DIRECTORY); + exit(0); + /* NOTREACHED */ + } + + /* can't do file1 file2 directory/file */ + if (argc != 2) + errx(1, "Target: %s", argv[argc-1]); + + if (!no_target) { + if (stat(*argv, &from_sb)) + err(1, "%s", *argv); + if (!S_ISREG(to_sb.st_mode)) + errc(1, EFTYPE, "%s", 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); + } + install(*argv, to_name, fset, iflags); + exit(0); + /* NOTREACHED */ +} + +/* + * install -- + * build a path name and install the file + */ +void +install(char *from_name, char *to_name, u_long fset, u_int flags) +{ + struct stat from_sb, to_sb; + struct timespec ts[2]; + int devnull, from_fd, to_fd, serrno, files_match = 0; + char *p; + char *target_name = tempfile; + + (void)memset((void *)&from_sb, 0, sizeof(from_sb)); + (void)memset((void *)&to_sb, 0, sizeof(to_sb)); + + /* 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(1, "%s", from_name); + if (!S_ISREG(from_sb.st_mode)) + errc(1, EFTYPE, "%s", 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; + } + devnull = 0; + } else { + devnull = 1; + } + + if (stat(to_name, &to_sb) == 0) { + /* Only compare against regular files. */ + if (docompare && !S_ISREG(to_sb.st_mode)) { + docompare = 0; + warnc(EFTYPE, "%s", to_name); + } + } else if (docompare) { + /* File does not exist so silently ignore compare flag. */ + docompare = 0; + } + + if (!devnull) { + if ((from_fd = open(from_name, O_RDONLY, 0)) == -1) + err(1, "%s", from_name); + } + + to_fd = create_tempfile(to_name, tempfile, sizeof(tempfile)); + if (to_fd < 0) + err(1, "%s", tempfile); + + if (!devnull) + copy(from_fd, from_name, to_fd, tempfile, from_sb.st_size, + ((off_t)from_sb.st_blocks * S_BLKSIZE < from_sb.st_size)); + + if (dostrip) { + strip(tempfile); + + /* + * 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(tempfile, O_RDONLY, 0)) == -1) + err(1, "stripping %s", to_name); + } + + /* + * Compare the (possibly stripped) temp file to the target. + */ + if (docompare) { + int temp_fd = to_fd; + struct stat temp_sb; + + /* Re-open to_fd using the real target name. */ + if ((to_fd = open(to_name, O_RDONLY, 0)) == -1) + err(1, "%s", to_name); + + if (fstat(temp_fd, &temp_sb)) { + serrno = errno; + (void)unlink(tempfile); + errc(1, serrno, "%s", tempfile); + } + + if (compare(temp_fd, tempfile, temp_sb.st_size, to_fd, + to_name, to_sb.st_size) == 0) { + /* + * If target has more than one link we need to + * replace it in order to snap the extra links. + * Need to preserve target file times, though. + */ + if (to_sb.st_nlink != 1) { + ts[0] = to_sb.st_atim; + ts[1] = to_sb.st_mtim; + futimens(temp_fd, ts); + } else { + files_match = 1; + (void)unlink(tempfile); + target_name = to_name; + (void)close(temp_fd); + } + } + if (!files_match) { + (void)close(to_fd); + to_fd = temp_fd; + } + } + + /* + * Preserve the timestamp of the source file if necessary. + */ + if (dopreserve && !files_match) { + ts[0] = from_sb.st_atim; + ts[1] = from_sb.st_mtim; + futimens(to_fd, ts); + } + + /* + * Set owner, group, mode for target; do the chown first, + * chown may lose the setuid bits. + */ + if ((gid != (gid_t)-1 || uid != (uid_t)-1) && + fchown(to_fd, uid, gid)) { + serrno = errno; + if (target_name == tempfile) + (void)unlink(target_name); + errx(1, "%s: chown/chgrp: %s", target_name, strerror(serrno)); + } + if (fchmod(to_fd, mode)) { + serrno = errno; + if (target_name == tempfile) + (void)unlink(target_name); + errx(1, "%s: chmod: %s", target_name, strerror(serrno)); + } + + /* + * If provided a set of flags, set them, otherwise, preserve the + * flags, except for the dump flag. + */ + /* if (fchflags(to_fd, + flags & SETFLAGS ? fset : from_sb.st_flags & ~UF_NODUMP)) { + if (errno != EOPNOTSUPP || (from_sb.st_flags & ~UF_NODUMP) != 0) + warnx("%s: chflags: %s", target_name, strerror(errno)); + } */ + + if (flags & USEFSYNC) + fsync(to_fd); + (void)close(to_fd); + if (!devnull) + (void)close(from_fd); + + /* + * Move the new file into place if the files are different + * or were not compared. + */ + if (!files_match) { + /* Try to turn off the immutable bits. */ + /* if (to_sb.st_flags & (NOCHANGEBITS)) + (void)chflags(to_name, to_sb.st_flags & ~(NOCHANGEBITS)); */ + if (dobackup) { + char backup[PATH_MAX]; + (void)snprintf(backup, PATH_MAX, "%s%s", to_name, + suffix); + /* It is ok for the target file not to exist. */ + if (rename(to_name, backup) == -1 && errno != ENOENT) { + serrno = errno; + unlink(tempfile); + errx(1, "rename: %s to %s: %s", to_name, + backup, strerror(serrno)); + } + } + if (rename(tempfile, to_name) == -1 ) { + serrno = errno; + unlink(tempfile); + errx(1, "rename: %s to %s: %s", tempfile, + to_name, strerror(serrno)); + } + } +} + +/* + * copy -- + * copy from one file to another + */ +void +copy(int from_fd, char *from_name, int to_fd, char *to_name, off_t size, + int sparse) +{ + ssize_t nr, nw; + int serrno; + char *p, buf[MAXBSIZE]; + + if (size == 0) + return; + + /* Rewind file descriptors. */ + if (lseek(from_fd, (off_t)0, SEEK_SET) == (off_t)-1) + err(1, "lseek: %s", from_name); + if (lseek(to_fd, (off_t)0, SEEK_SET) == (off_t)-1) + err(1, "lseek: %s", to_name); + + /* + * 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. Sparse files need special treatment. + */ + if (!sparse && size <= 8 * 1048576) { + size_t siz; + + if ((p = mmap(NULL, (size_t)size, PROT_READ, MAP_PRIVATE, + from_fd, (off_t)0)) == MAP_FAILED) { + serrno = errno; + (void)unlink(to_name); + errc(1, serrno, "%s", from_name); + } + madvise(p, size, MADV_SEQUENTIAL); + siz = (size_t)size; + if ((nw = write(to_fd, p, siz)) != siz) { + serrno = errno; + (void)unlink(to_name); + errx(1, "%s: %s", + to_name, strerror(nw > 0 ? EIO : serrno)); + } + (void) munmap(p, (size_t)size); + } else { + int sz, rem, isem = 1; + struct stat sb; + + /* + * Pass the blocksize of the file being written to the write + * routine. if the size is zero, use the default S_BLKSIZE. + */ + if (fstat(to_fd, &sb) != 0 || sb.st_blksize == 0) + sz = S_BLKSIZE; + else + sz = sb.st_blksize; + rem = sz; + + while ((nr = read(from_fd, buf, sizeof(buf))) > 0) { + if (sparse) + nw = file_write(to_fd, buf, nr, &rem, &isem, sz); + else + nw = write(to_fd, buf, nr); + if (nw != nr) { + serrno = errno; + (void)unlink(to_name); + errx(1, "%s: %s", + to_name, strerror(nw > 0 ? EIO : serrno)); + } + } + if (sparse) + file_flush(to_fd, isem); + if (nr != 0) { + serrno = errno; + (void)unlink(to_name); + errc(1, serrno, "%s", from_name); + } + } +} + +/* + * compare -- + * compare two files; non-zero means files differ + */ +int +compare(int from_fd, const char *from_name, off_t from_len, int to_fd, + const char *to_name, off_t to_len) +{ + caddr_t p1, p2; + size_t length; + off_t from_off, to_off, remainder; + int dfound; + + if (from_len == 0 && from_len == to_len) + return (0); + + if (from_len != to_len) + return (1); + + /* + * Compare the two files being careful not to mmap + * more than 8M at a time. + */ + from_off = to_off = (off_t)0; + remainder = from_len; + do { + length = MINIMUM(remainder, 8 * 1048576); + remainder -= length; + + if ((p1 = mmap(NULL, length, PROT_READ, MAP_PRIVATE, + from_fd, from_off)) == MAP_FAILED) + err(1, "%s", from_name); + if ((p2 = mmap(NULL, length, PROT_READ, MAP_PRIVATE, + to_fd, to_off)) == MAP_FAILED) + err(1, "%s", to_name); + if (length) { + madvise(p1, length, MADV_SEQUENTIAL); + madvise(p2, length, MADV_SEQUENTIAL); + } + + dfound = memcmp(p1, p2, length); + + (void) munmap(p1, length); + (void) munmap(p2, length); + + from_off += length; + to_off += length; + + } while (!dfound && remainder > 0); + + return(dfound); +} + +/* + * strip -- + * use strip(1) to strip the target file + */ +void +strip(char *to_name) +{ + int serrno, status; + char * volatile path_strip; + pid_t pid; + + if (issetugid() || (path_strip = getenv("STRIP")) == NULL) + path_strip = _PATH_STRIP; + + switch ((pid = vfork())) { + case -1: + serrno = errno; + (void)unlink(to_name); + errc(1, serrno, "forks"); + case 0: + execl(path_strip, "strip", "--", to_name, (char *)NULL); + warn("%s", path_strip); + _exit(1); + default: + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) + break; + } + if (!WIFEXITED(status)) + (void)unlink(to_name); + } +} + +/* + * install_dir -- + * build directory hierarchy + */ +void +install_dir(char *path, int mode) +{ + char *p; + struct stat sb; + int ch; + + for (p = path;; ++p) + if (!*p || (p != path && *p == '/')) { + ch = *p; + *p = '\0'; + if (mkdir(path, 0777)) { + int mkdir_errno = errno; + if (stat(path, &sb)) { + /* Not there; use mkdir()s errno */ + errc(1, mkdir_errno, "%s", + path); + /* NOTREACHED */ + } + if (!S_ISDIR(sb.st_mode)) { + /* Is there, but isn't a directory */ + errc(1, ENOTDIR, "%s", path); + /* NOTREACHED */ + } + } + if (!(*p = ch)) + break; + } + + if (((gid != (gid_t)-1 || uid != (uid_t)-1) && chown(path, uid, gid)) || + chmod(path, mode)) { + warn("%s", path); + } +} + +/* + * usage -- + * print a usage message and die + */ +void +usage(void) +{ + (void)fprintf(stderr, "\ +usage: install [-bCcDdFpSs] [-B suffix] [-f flags] [-g group] [-m mode] [-o owner]\n source ... target ...\n"); + exit(1); + /* NOTREACHED */ +} + +/* + * create_tempfile -- + * create a temporary file based on path and open it + */ +int +create_tempfile(char *path, char *temp, size_t tsize) +{ + char *p; + + strlcpy(temp, path, tsize); + if ((p = strrchr(temp, '/')) != NULL) + p++; + else + p = temp; + *p = '\0'; + strlcat(p, "INS@XXXXXXXXXX", tsize); + + return(mkstemp(temp)); +} + +/* + * file_write() + * Write/copy a file (during copy or archive extract). This routine knows + * how to copy files with lseek holes in it. (Which are read as file + * blocks containing all 0's but do not have any file blocks associated + * with the data). Typical examples of these are files created by dbm + * variants (.pag files). While the file size of these files are huge, the + * actual storage is quite small (the files are sparse). The problem is + * the holes read as all zeros so are probably stored on the archive that + * way (there is no way to determine if the file block is really a hole, + * we only know that a file block of all zero's can be a hole). + * At this writing, no major archive format knows how to archive files + * with holes. However, on extraction (or during copy, -rw) we have to + * deal with these files. Without detecting the holes, the files can + * consume a lot of file space if just written to disk. This replacement + * for write when passed the basic allocation size of a file system block, + * uses lseek whenever it detects the input data is all 0 within that + * file block. In more detail, the strategy is as follows: + * While the input is all zero keep doing an lseek. Keep track of when we + * pass over file block boundaries. Only write when we hit a non zero + * input. once we have written a file block, we continue to write it to + * the end (we stop looking at the input). When we reach the start of the + * next file block, start checking for zero blocks again. Working on file + * block boundaries significantly reduces the overhead when copying files + * that are NOT very sparse. This overhead (when compared to a write) is + * almost below the measurement resolution on many systems. Without it, + * files with holes cannot be safely copied. It does has a side effect as + * it can put holes into files that did not have them before, but that is + * not a problem since the file contents are unchanged (in fact it saves + * file space). (Except on paging files for diskless clients. But since we + * cannot determine one of those file from here, we ignore them). If this + * ever ends up on a system where CTG files are supported and the holes + * are not desired, just do a conditional test in those routines that + * call file_write() and have it call write() instead. BEFORE CLOSING THE + * FILE, make sure to call file_flush() when the last write finishes with + * an empty block. A lot of file systems will not create an lseek hole at + * the end. In this case we drop a single 0 at the end to force the + * trailing 0's in the file. + * ---Parameters--- + * rem: how many bytes left in this file system block + * isempt: have we written to the file block yet (is it empty) + * sz: basic file block allocation size + * cnt: number of bytes on this write + * str: buffer to write + * Return: + * number of bytes written, -1 on write (or lseek) error. + */ + +int +file_write(int fd, char *str, size_t cnt, int *rem, int *isempt, int sz) +{ + char *pt; + char *end; + size_t wcnt; + char *st = str; + + /* + * while we have data to process + */ + while (cnt) { + if (!*rem) { + /* + * We are now at the start of file system block again + * (or what we think one is...). start looking for + * empty blocks again + */ + *isempt = 1; + *rem = sz; + } + + /* + * only examine up to the end of the current file block or + * remaining characters to write, whatever is smaller + */ + wcnt = MINIMUM(cnt, *rem); + cnt -= wcnt; + *rem -= wcnt; + if (*isempt) { + /* + * have not written to this block yet, so we keep + * looking for zero's + */ + pt = st; + end = st + wcnt; + + /* + * look for a zero filled buffer + */ + while ((pt < end) && (*pt == '\0')) + ++pt; + + if (pt == end) { + /* + * skip, buf is empty so far + */ + if (lseek(fd, (off_t)wcnt, SEEK_CUR) == -1) { + warn("lseek"); + return(-1); + } + st = pt; + continue; + } + /* + * drat, the buf is not zero filled + */ + *isempt = 0; + } + + /* + * have non-zero data in this file system block, have to write + */ + if (write(fd, st, wcnt) != wcnt) { + warn("write"); + return(-1); + } + st += wcnt; + } + return(st - str); +} + +/* + * file_flush() + * when the last file block in a file is zero, many file systems will not + * let us create a hole at the end. To get the last block with zeros, we + * write the last BYTE with a zero (back up one byte and write a zero). + */ +void +file_flush(int fd, int isempt) +{ + static char blnk[] = "\0"; + + /* + * silly test, but make sure we are only called when the last block is + * filled with all zeros. + */ + if (!isempt) + return; + + /* + * move back one byte and write a zero + */ + if (lseek(fd, (off_t)-1, SEEK_CUR) == -1) { + warn("Failed seek on file"); + return; + } + + if (write(fd, blnk, 1) == -1) + warn("Failed write to file"); + return; +}