diff options
-rw-r--r-- | usr.bin/xinstall/install.1 | 47 | ||||
-rw-r--r-- | usr.bin/xinstall/xinstall.c | 296 |
2 files changed, 265 insertions, 78 deletions
diff --git a/usr.bin/xinstall/install.1 b/usr.bin/xinstall/install.1 index c35c7a5..84ec638 100644 --- a/usr.bin/xinstall/install.1 +++ b/usr.bin/xinstall/install.1 @@ -29,9 +29,10 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" @(#)install.1 8.1 (Berkeley) 6/6/93 +.\" From: @(#)install.1 8.1 (Berkeley) 6/6/93 +.\" $Id$ .\" -.Dd June 6, 1993 +.Dd October 9, 1995 .Dt INSTALL 1 .Os BSD 4.2 .Sh NAME @@ -46,7 +47,7 @@ .Op Fl o Ar owner .Ar file1 file2 .Nm install -.Op Fl cs +.Op Fl Ccdps .Op Fl f Ar flags .Op Fl g Ar group .Op Fl m Ar mode @@ -67,11 +68,29 @@ If the target file already exists, it is overwritten if permissions allow. .Pp .Bl -tag -width Ds +.It Fl C . +Copy the file, as if the +.Fl c +option is specifed, +except 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 flag turns off the default behavior of .Nm install where it deletes the original file after creating the target. +.It Fl d +Print debugging information. +If +.Fl d +is specified one or more times, +then print the renaming steps for +.Fl C . +If +.Fl d +is specified two or more times, +then warn about files that aren't installed with +.Fl C . .It Fl f Specify the target's file flags. (See @@ -87,6 +106,13 @@ The specified mode may be either an octal or symbolic value; see for a description of possible mode values. .It Fl o Specify an owner. +.It Fl p +Preserve the modification time. +Copy the file, as if the +.Fl C +(Compare and copy) option is specifed, +except if the target file doesn't already exist or is different, +then preserve the modification time of the file. .It Fl s .Nm Install exec's the command @@ -109,6 +135,17 @@ creates an empty file. .Pp Upon successful completion a value of 0 is returned. Otherwise, a value of 1 is returned. +.Sh FILES +.Bl -tag -width INS@XXXX -compact +.It Pa INS@XXXX +If the +.Fl C +or +.Fl p +option is used, then temporary files named INS@XXXX, +where XXXX is decided by +.Xr mkstemp 3 , +are created in the target directory. .Sh SEE ALSO .Xr chflags 1 , .Xr chgrp 1 , @@ -122,3 +159,7 @@ The .Nm install utility appeared in .Bx 4.2 . +.Sh BUGS +Temporary files may be left in the target directory if +.Nm install +exits abnormally. diff --git a/usr.bin/xinstall/xinstall.c b/usr.bin/xinstall/xinstall.c index 4c41fa8..eeae7da 100644 --- a/usr.bin/xinstall/xinstall.c +++ b/usr.bin/xinstall/xinstall.c @@ -32,21 +32,39 @@ */ #ifndef lint -static char copyright[] = +static const char copyright[] = "@(#) Copyright (c) 1987, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint -static char sccsid[] = "@(#)xinstall.c 8.1 (Berkeley) 7/21/93"; +/*static char sccsid[] = "From: @(#)xinstall.c 8.1 (Berkeley) 7/21/93";*/ +static const char rcsid[] = + "$Id$"; #endif /* not lint */ +/*- + * Todo: + * o for -C, compare original files except in -s case. + * o for -C, don't change anything if nothing needs be changed. In + * particular, don't toggle the immutable flags just to allow null + * attribute changes and don't clear the dump flag. (I think inode + * ctimes are not updated for null attribute changes, but this is a + * bug.) + * o independent of -C, if a copy must be made, then copy to a tmpfile, + * set all attributes except the immutable flags, then rename, then + * set the immutable flags. It's annoying that the immutable flags + * defeat the atomicicity of rename - it seems that there must be + * o a window where the target is not immutable. + */ + #include <sys/param.h> #include <sys/wait.h> #include <sys/mman.h> #include <sys/stat.h> #include <ctype.h> +#include <err.h> #include <errno.h> #include <fcntl.h> #include <grp.h> @@ -55,21 +73,26 @@ static char sccsid[] = "@(#)xinstall.c 8.1 (Berkeley) 7/21/93"; #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sysexits.h> #include <unistd.h> +#include <utime.h> #include "pathnames.h" struct passwd *pp; struct group *gp; -int docopy, dostrip; +int debug, docompare, docopy, dopreserve, dostrip; int mode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH; char *group, *owner, pathbuf[MAXPATHLEN]; +char pathbuf2[MAXPATHLEN]; #define DIRECTORY 0x01 /* Tell install it's a directory. */ #define SETFLAGS 0x02 /* Tell install to set flags. */ +#define NOCHANGEBITS (UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND) void copy __P((int, char *, int, char *, off_t)); -void err __P((const char *, ...)); +int compare __P((int, const char *, int, const char *, + const struct stat *, const struct stat *)); void install __P((char *, char *, u_long, u_int)); u_long string_to_flags __P((char **, u_long *, u_long *)); void strip __P((char *)); @@ -88,15 +111,21 @@ main(argc, argv) char *flags, *to_name; iflags = 0; - while ((ch = getopt(argc, argv, "cf:g:m:o:s")) != EOF) + while ((ch = getopt(argc, argv, "Ccdf:g:m:o:ps")) != EOF) switch((char)ch) { + case 'C': + docompare = docopy = 1; + break; case 'c': docopy = 1; break; + case 'd': + debug++; + break; case 'f': flags = optarg; if (string_to_flags(&flags, &fset, NULL)) - err("%s: invalid flag", flags); + errx(EX_USAGE, "%s: invalid flag", flags); iflags |= SETFLAGS; break; case 'g': @@ -104,12 +133,16 @@ main(argc, argv) break; case 'm': if (!(set = setmode(optarg))) - err("%s: invalid file mode", optarg); + errx(EX_USAGE, "invalid file mode: %s", + optarg); mode = getmode(set, 0); break; case 'o': owner = optarg; break; + case 'p': + docompare = docopy = dopreserve = 1; + break; case 's': dostrip = 1; break; @@ -124,9 +157,9 @@ main(argc, argv) /* get group and owner id's */ if (group && !(gp = getgrnam(group))) - err("unknown group %s", group); + errx(EX_NOUSER, "unknown group %s", group); if (owner && !(pp = getpwnam(owner))) - err("unknown user %s", owner); + errx(EX_NOUSER, "unknown user %s", owner); no_target = stat(to_name = argv[argc - 1], &to_sb); if (!no_target && (to_sb.st_mode & S_IFMT) == S_IFDIR) { @@ -141,22 +174,31 @@ main(argc, argv) if (!no_target) { if (stat(*argv, &from_sb)) - err("%s: %s", *argv, strerror(errno)); - if (!S_ISREG(to_sb.st_mode)) - err("%s: %s", to_name, strerror(EFTYPE)); + err(EX_OSERR, "%s", *argv); + if (!S_ISREG(to_sb.st_mode)) { + errno = EFTYPE; + err(EX_OSERR, "%s", to_name); + } if (to_sb.st_dev == from_sb.st_dev && to_sb.st_ino == from_sb.st_ino) - err("%s and %s are the same file", *argv, to_name); + errx(EX_USAGE, + "%s and %s are the same file", *argv, to_name); +/* + * XXX - It's not at all clear why this code was here, since it completely + * duplicates code install(). The version in install() handles the -C flag + * correctly, so we'll just disable this for now. + */ +#if 0 /* * Unlink now... avoid ETXTBSY errors later. Try and turn * off the append/immutable bits -- if we fail, go ahead, * it might work. */ -#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)); (void)unlink(to_name); +#endif } install(*argv, to_name, fset, iflags); exit(0); @@ -174,19 +216,25 @@ install(from_name, to_name, fset, flags) { struct stat from_sb, to_sb; int devnull, from_fd, to_fd, serrno; - char *p; + char *p, *old_to_name = 0; + + if (debug >= 2 && !docompare) + fprintf(stderr, "install: invoked without -C for %s to %s\n", + from_name, to_name); /* 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("%s: %s", from_name, strerror(errno)); - if (!S_ISREG(from_sb.st_mode)) - err("%s: %s", from_name, strerror(EFTYPE)); + err(EX_OSERR, "%s", from_name); + if (!S_ISREG(from_sb.st_mode)) { + errno = EFTYPE; + err(EX_OSERR, "%s", from_name); + } /* Build the target path. */ if (flags & DIRECTORY) { (void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s", to_name, - (p = rindex(from_name, '/')) ? ++p : from_name); + (p = strrchr(from_name, '/')) ? ++p : from_name); to_name = pathbuf; } devnull = 0; @@ -195,30 +243,118 @@ install(from_name, to_name, fset, flags) devnull = 1; } - /* - * Unlink now... avoid ETXTBSY errors later. Try and turn - * off the append/immutable bits -- if we fail, go ahead, - * it might work. - */ - if (stat(to_name, &to_sb) == 0 && - to_sb.st_flags & (NOCHANGEBITS)) - (void)chflags(to_name, to_sb.st_flags & ~(NOCHANGEBITS)); - (void)unlink(to_name); - - /* Create target. */ - if ((to_fd = open(to_name, - O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) - err("%s: %s", to_name, strerror(errno)); + if (docompare) { + old_to_name = to_name; + /* + * Make a new temporary file in the same file system + * (actually, in in the same directory) as the target so + * that the temporary file can be renamed to the target. + */ + snprintf(pathbuf2, sizeof pathbuf2, "%s", to_name); + p = strrchr(pathbuf2, '/'); + p = (p == NULL ? pathbuf2 : p + 1); + snprintf(p, &pathbuf2[sizeof pathbuf2] - p, "INS@XXXX"); + to_fd = mkstemp(pathbuf2); + if (to_fd < 0) + /* XXX should fall back to not comparing. */ + err(EX_OSERR, "mkstemp: %s for %s", pathbuf2, to_name); + to_name = pathbuf2; + } else { + /* + * Unlink now... avoid errors later. Try to turn off the + * append/immutable bits -- if we fail, go ahead, it might + * work. + */ + if (stat(to_name, &to_sb) == 0 && to_sb.st_flags & NOCHANGEBITS) + (void)chflags(to_name, to_sb.st_flags & ~NOCHANGEBITS); + unlink(to_name); + + /* Create target. */ + to_fd = open(to_name, + O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR); + if (to_fd < 0) + err(EX_OSERR, "%s", to_name); + } + if (!devnull) { if ((from_fd = open(from_name, O_RDONLY, 0)) < 0) { + serrno = errno; (void)unlink(to_name); - err("%s: %s", from_name, strerror(errno)); + errno = serrno; + err(EX_OSERR, "%s", from_name); } copy(from_fd, from_name, to_fd, to_name, from_sb.st_size); (void)close(from_fd); } + if (dostrip) strip(to_name); + + /* + * Unfortunately, because we strip the installed file and not the + * original one, it is impossible to do the comparison without + * first laboriously copying things over and then comparing. + * It may be possible to better optimize the !dostrip case, however. + * For further study. + */ + if (docompare) { + struct stat old_sb, new_sb, timestamp_sb; + int old_fd; + struct utimbuf utb; + + old_fd = open(old_to_name, O_RDONLY, 0); + if (old_fd < 0 && errno == ENOENT) + goto different; + if (old_fd < 0) + err(EX_OSERR, "%s", old_to_name); + fstat(old_fd, &old_sb); + if (old_sb.st_flags & NOCHANGEBITS) + (void)fchflags(old_fd, old_sb.st_flags & ~NOCHANGEBITS); + fstat(to_fd, &new_sb); + if (compare(old_fd, old_to_name, to_fd, to_name, &old_sb, + &new_sb)) { +different: + if (debug != 0) + fprintf(stderr, + "install: renaming for %s: %s to %s\n", + from_name, to_name, old_to_name); + if (dopreserve && stat(from_name, ×tamp_sb) == 0) { + utb.actime = from_sb.st_atime; + utb.modtime = from_sb.st_mtime; + (void)utime(to_name, &utb); + } +moveit: + if (rename(to_name, old_to_name) < 0) { + serrno = errno; + unlink(to_name); + unlink(old_to_name); + errno = serrno; + err(EX_OSERR, "rename: %s to %s", to_name, + old_to_name); + } + close(old_fd); + } else { + if (old_sb.st_nlink != 1) { + /* + * Replace the target, although it hasn't + * changed, to snap the extra links. But + * preserve the target file times. + */ + if (fstat(old_fd, ×tamp_sb) == 0) { + utb.actime = timestamp_sb.st_atime; + utb.modtime = timestamp_sb.st_mtime; + (void)utime(to_name, &utb); + } + goto moveit; + } + if (unlink(to_name) < 0) + err(EX_OSERR, "unlink: %s", to_name); + close(to_fd); + to_fd = old_fd; + } + to_name = old_to_name; + } + /* * Set owner, group, mode for target; do the chown first, * chown may lose the setuid bits. @@ -227,12 +363,14 @@ install(from_name, to_name, fset, flags) fchown(to_fd, owner ? pp->pw_uid : -1, group ? gp->gr_gid : -1)) { serrno = errno; (void)unlink(to_name); - err("%s: chown/chgrp: %s", to_name, strerror(serrno)); + errno = serrno; + err(EX_OSERR,"%s: chown/chgrp", to_name); } if (fchmod(to_fd, mode)) { serrno = errno; (void)unlink(to_name); - err("%s: chmod: %s", to_name, strerror(serrno)); + errno = serrno; + err(EX_OSERR, "%s: chmod", to_name); } /* @@ -243,12 +381,47 @@ install(from_name, to_name, fset, flags) flags & SETFLAGS ? fset : from_sb.st_flags & ~UF_NODUMP)) { serrno = errno; (void)unlink(to_name); - err("%s: chflags: %s", to_name, strerror(serrno)); + errno = serrno; + err(EX_OSERR, "%s: chflags", to_name); } (void)close(to_fd); if (!docopy && !devnull && unlink(from_name)) - err("%s: %s", from_name, strerror(errno)); + err(EX_OSERR, "%s", from_name); +} + +/* + * compare -- + * compare two files; non-zero means files differ + */ +int +compare(int from_fd, const char *from_name, int to_fd, const char *to_name, + const struct stat *from_sb, const struct stat *to_sb) +{ + char *p, *q; + int rv; + size_t tsize; + + if (from_sb->st_size != to_sb->st_size) + return 1; + + tsize = (size_t)from_sb->st_size; + + if (tsize <= 8 * 1024 * 1024) { + p = mmap(NULL, tsize, PROT_READ, 0, from_fd, (off_t)0); + if ((long)p == -1) + err(EX_OSERR, "mmap %s", from_name); + q = mmap(NULL, tsize, PROT_READ, 0, to_fd, (off_t)0); + if ((long)q == -1) + err(EX_OSERR, "mmap %s", to_name); + + rv = memcmp(p, q, tsize); + munmap(p, tsize); + munmap(q, tsize); + } else { + rv = 1; /* don't bother in this case */ + } + return rv; } /* @@ -273,21 +446,22 @@ copy(from_fd, from_name, to_fd, to_name, size) if (size <= 8 * 1048576) { if ((p = mmap(NULL, (size_t)size, PROT_READ, 0, from_fd, (off_t)0)) == (char *)-1) - err("%s: %s", from_name, strerror(errno)); + err(EX_OSERR, "mmap %s", from_name); if (write(to_fd, p, size) != size) - err("%s: %s", to_name, strerror(errno)); + err(EX_OSERR, "%s", to_name); } else { while ((nr = read(from_fd, buf, sizeof(buf))) > 0) if ((nw = write(to_fd, buf, nr)) != nr) { serrno = errno; (void)unlink(to_name); - err("%s: %s", - to_name, strerror(nw > 0 ? EIO : serrno)); + errno = nw > 0 ? EIO : serrno; + err(EX_OSERR, "%s", to_name); } if (nr != 0) { serrno = errno; (void)unlink(to_name); - err("%s: %s", from_name, strerror(serrno)); + errno = serrno; + err(EX_OSERR, "%s", from_name); } } } @@ -306,10 +480,11 @@ strip(to_name) case -1: serrno = errno; (void)unlink(to_name); - err("forks: %s", strerror(errno)); + errno = serrno; + err(EX_TEMPFAIL, "fork"); case 0: execl(_PATH_STRIP, "strip", to_name, NULL); - err("%s: %s", _PATH_STRIP, strerror(errno)); + err(EX_OSERR, "exec(" _PATH_STRIP ")"); default: if (wait(&status) == -1 || status) (void)unlink(to_name); @@ -324,35 +499,6 @@ void usage() { (void)fprintf(stderr, -"usage: install [-cs] [-f flags] [-g group] [-m mode] [-o owner] file1 file2;\n\tor file1 ... fileN directory\n"); - exit(1); -} - -#if __STDC__ -#include <stdarg.h> -#else -#include <varargs.h> -#endif - -void -#if __STDC__ -err(const char *fmt, ...) -#else -err(fmt, va_alist) - char *fmt; - va_dcl -#endif -{ - va_list ap; -#if __STDC__ - va_start(ap, fmt); -#else - va_start(ap); -#endif - (void)fprintf(stderr, "install: "); - (void)vfprintf(stderr, fmt, ap); - va_end(ap); - (void)fprintf(stderr, "\n"); +"usage: install [-Ccdps] [-f flags] [-g group] [-m mode] [-o owner] file1 file2;\n\tor file1 ... fileN directory\n"); exit(1); - /* NOTREACHED */ } |