=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/xinstall/xinstall.c,v retrieving revision 1.3 retrieving revision 1.4 diff -c -r1.3 -r1.4 *** src/usr.bin/xinstall/xinstall.c 1996/06/26 05:44:05 1.3 --- src/usr.bin/xinstall/xinstall.c 1996/08/05 01:18:42 1.4 *************** *** 1,4 **** ! /* $OpenBSD: xinstall.c,v 1.3 1996/06/26 05:44:05 deraadt Exp $ */ /* $NetBSD: xinstall.c,v 1.9 1995/12/20 10:25:17 jonathan Exp $ */ /* --- 1,4 ---- ! /* $OpenBSD: xinstall.c,v 1.4 1996/08/05 01:18:42 millert Exp $ */ /* $NetBSD: xinstall.c,v 1.9 1995/12/20 10:25:17 jonathan Exp $ */ /* *************** *** 44,50 **** #if 0 static char sccsid[] = "@(#)xinstall.c 8.1 (Berkeley) 7/21/93"; #endif ! static char rcsid[] = "$OpenBSD: xinstall.c,v 1.3 1996/06/26 05:44:05 deraadt Exp $"; #endif /* not lint */ #include --- 44,50 ---- #if 0 static char sccsid[] = "@(#)xinstall.c 8.1 (Berkeley) 7/21/93"; #endif ! static char rcsid[] = "$OpenBSD: xinstall.c,v 1.4 1996/08/05 01:18:42 millert Exp $"; #endif /* not lint */ #include *************** *** 53,58 **** --- 53,59 ---- #include #include + #include #include #include #include *************** *** 62,88 **** #include #include #include ! #include #include "pathnames.h" struct passwd *pp; struct group *gp; ! int docopy, dodir, dostrip; int mode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH; ! char pathbuf[MAXPATHLEN]; uid_t uid; gid_t gid; #define DIRECTORY 0x01 /* Tell install it's a directory. */ #define SETFLAGS 0x02 /* Tell install to set flags. */ void copy __P((int, char *, int, char *, off_t)); void install __P((char *, char *, u_long, u_int)); void install_dir __P((char *)); u_long string_to_flags __P((char **, u_long *, u_long *)); void strip __P((char *)); void usage __P((void)); int main(argc, argv) --- 63,94 ---- #include #include #include ! #include ! #include #include "pathnames.h" struct passwd *pp; struct group *gp; ! int docompare, dodir, dopreserve, dostrip, safecopy; int mode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH; ! char pathbuf[MAXPATHLEN], tempfile[MAXPATHLEN]; uid_t uid; gid_t gid; #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)); + int compare __P((int, const char *, size_t, int, const char *, size_t)); void install __P((char *, char *, u_long, u_int)); void install_dir __P((char *)); u_long string_to_flags __P((char **, u_long *, u_long *)); void strip __P((char *)); void usage __P((void)); + int create_newfile __P((char *, struct stat *)); + int create_tempfile __P((char *, char *, size_t)); int main(argc, argv) *************** *** 97,111 **** char *flags, *to_name, *group = NULL, *owner = NULL; iflags = 0; ! while ((ch = getopt(argc, argv, "cf:g:m:o:sd")) != EOF) switch((char)ch) { case 'c': ! docopy = 1; break; case 'f': flags = optarg; if (string_to_flags(&flags, &fset, NULL)) ! errx(1, "%s: invalid flag", flags); iflags |= SETFLAGS; break; case 'g': --- 103,120 ---- char *flags, *to_name, *group = NULL, *owner = NULL; iflags = 0; ! while ((ch = getopt(argc, argv, "Ccdf:g:m:o:pSs")) != EOF) switch((char)ch) { + case 'C': + docompare = 1; + break; case 'c': ! /* For backwards compatibility. */ break; case 'f': flags = optarg; if (string_to_flags(&flags, &fset, NULL)) ! errx(EX_USAGE, "%s: invalid flag", flags); iflags |= SETFLAGS; break; case 'g': *************** *** 113,124 **** break; case 'm': if (!(set = setmode(optarg))) ! errx(1, "%s: invalid file mode", optarg); mode = getmode(set, 0); break; case 'o': owner = optarg; break; case 's': dostrip = 1; break; --- 122,139 ---- break; case 'm': if (!(set = setmode(optarg))) ! errx(EX_USAGE, "%s: invalid file mode", optarg); mode = getmode(set, 0); break; case 'o': owner = optarg; break; + case 'p': + docompare = dopreserve = 1; + break; + case 'S': + safecopy = 1; + break; case 's': dostrip = 1; break; *************** *** 132,157 **** argc -= optind; argv += optind; ! /* copy and strip options make no sense when creating directories */ ! if ((docopy || dostrip) && dodir) usage(); /* must have at least two arguments, except when creating directories */ if (argc < 2 && !dodir) usage(); /* get group and owner id's */ if (group && !(gp = getgrnam(group)) && !isdigit(*group)) ! errx(1, "unknown group %s", group); ! gid = (group) ? ((gp) ? gp->gr_gid : atoi(group)) : -1; if (owner && !(pp = getpwnam(owner)) && !isdigit(*owner)) ! errx(1, "unknown user %s", owner); ! uid = (owner) ? ((pp) ? pp->pw_uid : atoi(owner)) : -1; if (dodir) { for (; *argv != NULL; ++argv) install_dir(*argv); ! exit (0); /* NOTREACHED */ } --- 147,176 ---- argc -= optind; argv += optind; ! /* some options make no sense when creating directories */ ! if ((safecopy || docompare || dostrip) && dodir) usage(); /* must have at least two arguments, except when creating directories */ if (argc < 2 && !dodir) usage(); + /* need to make a temp copy so we can compare stripped version */ + if (docompare && dostrip) + safecopy = 1; + /* get group and owner id's */ if (group && !(gp = getgrnam(group)) && !isdigit(*group)) ! errx(EX_NOUSER, "unknown group %s", group); ! gid = (group) ? ((gp) ? gp->gr_gid : (gid_t)strtoul(group, NULL, 10)) : (gid_t)-1; if (owner && !(pp = getpwnam(owner)) && !isdigit(*owner)) ! errx(EX_NOUSER, "unknown user %s", owner); ! uid = (owner) ? ((pp) ? pp->pw_uid : (uid_t)strtoul(owner, NULL, 10)) : (uid_t)-1; if (dodir) { for (; *argv != NULL; ++argv) install_dir(*argv); ! exit(EX_OK); /* NOTREACHED */ } *************** *** 159,165 **** if (!no_target && S_ISDIR(to_sb.st_mode)) { for (; *argv != to_name; ++argv) install(*argv, to_name, fset, iflags | DIRECTORY); ! exit(0); } /* can't do file1 file2 directory/file */ --- 178,185 ---- if (!no_target && S_ISDIR(to_sb.st_mode)) { for (; *argv != to_name; ++argv) install(*argv, to_name, fset, iflags | DIRECTORY); ! exit(EX_OK); ! /* NOTREACHED */ } /* can't do file1 file2 directory/file */ *************** *** 168,192 **** if (!no_target) { if (stat(*argv, &from_sb)) ! err(1, "%s", *argv); if (!S_ISREG(to_sb.st_mode)) ! errx(1, "%s: %s", to_name, strerror(EFTYPE)); 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. ! */ ! #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); } install(*argv, to_name, fset, iflags); ! exit(0); } /* --- 188,203 ---- if (!no_target) { if (stat(*argv, &from_sb)) ! err(EX_OSERR, "%s", *argv); if (!S_ISREG(to_sb.st_mode)) ! errx(EX_OSERR, "%s: %s", to_name, strerror(EFTYPE)); if (to_sb.st_dev == from_sb.st_dev && to_sb.st_ino == from_sb.st_ino) ! errx(EX_USAGE, "%s and %s are the same file", *argv, to_name); } install(*argv, to_name, fset, iflags); ! exit(EX_OK); ! /* NOTREACHED */ } /* *************** *** 200,214 **** u_int flags; { struct stat from_sb, to_sb; ! int devnull, from_fd, to_fd, serrno; char *p; /* 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)) ! errx(1, "%s: %s", from_name, strerror(EFTYPE)); /* Build the target path. */ if (flags & DIRECTORY) { (void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s", --- 211,229 ---- u_int flags; { struct stat from_sb, to_sb; ! struct utimbuf utb; ! int devnull, from_fd, to_fd, serrno, files_match = 0; char *p; + (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(EX_OSERR, "%s", from_name); if (!S_ISREG(from_sb.st_mode)) ! errx(EX_OSERR, "%s: %s", from_name, strerror(EFTYPE)); /* Build the target path. */ if (flags & DIRECTORY) { (void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s", *************** *** 218,275 **** } devnull = 0; } else { - from_sb.st_flags = 0; /* XXX */ 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(1, "%s", to_name); if (!devnull) { if ((from_fd = open(from_name, O_RDONLY, 0)) < 0) { ! (void)unlink(to_name); ! err(1, "%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); /* * 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); } /* * Set owner, group, mode for target; do the chown first, * chown may lose the setuid bits. */ ! if ((gid != -1 || uid != -1) && fchown(to_fd, uid, gid)) { serrno = errno; (void)unlink(to_name); ! errx(1, "%s: chown/chgrp: %s", to_name, strerror(serrno)); } if (fchmod(to_fd, mode)) { serrno = errno; (void)unlink(to_name); ! errx(1, "%s: chmod: %s", to_name, strerror(serrno)); } /* --- 233,380 ---- } 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; ! warnx("%s: %s", to_name, strerror(EFTYPE)); ! } ! } else if (docompare) { ! /* File does not exist so silently ignore compare flag. */ ! docompare = 0; ! } ! if (safecopy) { ! to_fd = create_tempfile(to_name, tempfile, sizeof(tempfile)); ! if (to_fd < 0) ! err(EX_OSERR, "%s", tempfile); ! } else if (docompare && !dostrip) { ! if ((to_fd = open(to_name, O_RDONLY, 0)) < 0) ! err(EX_OSERR, "%s", to_name); ! } else { ! if ((to_fd = create_newfile(to_name, &to_sb)) < 0) ! err(EX_OSERR, "%s", to_name); ! } ! if (!devnull) { if ((from_fd = open(from_name, O_RDONLY, 0)) < 0) { ! serrno = errno; ! (void)unlink(safecopy ? tempfile : to_name); ! errx(EX_OSERR, "%s: %s", from_name, strerror(serrno)); } ! ! if (docompare && !safecopy) { ! files_match = !(compare(from_fd, from_name, ! (size_t)from_sb.st_size, to_fd, ! to_name, (size_t)to_sb.st_size)); ! ! /* Truncate "to" file for copy unless we match */ ! if (!files_match) { ! (void)close(to_fd); ! if ((to_fd = create_newfile(to_name, &to_sb)) < 0) ! err(EX_OSERR, "%s", to_name); ! } ! } ! if (!files_match) ! copy(from_fd, from_name, to_fd, ! safecopy ? tempfile : to_name, from_sb.st_size); } if (dostrip) { ! strip(safecopy ? tempfile : 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(safecopy ? tempfile : to_name, O_RDONLY, ! 0)) < 0) ! err(EX_OSERR, "stripping %s", to_name); } /* + * Compare the (possibly stripped) temp file to the target. + */ + if (safecopy && 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)) < 0) + err(EX_OSERR, "%s", to_name); + + if (fstat(temp_fd, &temp_sb)) { + serrno = errno; + (void)unlink(tempfile); + errx(EX_OSERR, "%s: %s", tempfile, strerror(serrno)); + } + + if (compare(temp_fd, tempfile, (size_t)temp_sb.st_size, to_fd, + to_name, (size_t)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) { + utb.actime = to_sb.st_atime; + utb.modtime = to_sb.st_mtime; + (void)utime(tempfile, &utb); + } else { + files_match = 1; + (void)unlink(tempfile); + } + (void) close(temp_fd); + } + } + + /* + * Move the new file into place if doing a safe copy + * and the files are different (or just not compared). + */ + if (safecopy && !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 (rename(tempfile, to_name) < 0 ) { + serrno = errno; + unlink(tempfile); + errx(EX_OSERR, "rename: %s to %s: %s", tempfile, + to_name, strerror(serrno)); + } + + /* Re-open to_fd so we aren't hosed by the rename(2). */ + (void) close(to_fd); + if ((to_fd = open(to_name, O_RDONLY, 0)) < 0) + err(EX_OSERR, "%s", to_name); + } + + /* + * Preserve the timestamp of the source file if necesary. + */ + if (dopreserve && !files_match) { + utb.actime = from_sb.st_atime; + utb.modtime = from_sb.st_mtime; + (void)utime(to_name, &utb); + } + + /* * 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; (void)unlink(to_name); ! errx(EX_OSERR, "%s: chown/chgrp: %s", to_name, strerror(serrno)); } if (fchmod(to_fd, mode)) { serrno = errno; (void)unlink(to_name); ! errx(EX_OSERR, "%s: chmod: %s", to_name, strerror(serrno)); } /* *************** *** 280,291 **** flags & SETFLAGS ? fset : from_sb.st_flags & ~UF_NODUMP)) { serrno = errno; (void)unlink(to_name); ! errx(1, "%s: chflags: %s", to_name, strerror(serrno)); } (void)close(to_fd); ! if (!docopy && !devnull && unlink(from_name)) ! err(1, "%s", from_name); } /* --- 385,395 ---- flags & SETFLAGS ? fset : from_sb.st_flags & ~UF_NODUMP)) { serrno = errno; (void)unlink(to_name); ! errx(EX_OSERR, "%s: chflags: %s", to_name, strerror(serrno)); } (void)close(to_fd); ! (void)close(from_fd); } /* *************** *** 311,337 **** if (size <= 8 * 1048576) { if ((p = mmap(NULL, (size_t)size, PROT_READ, 0, from_fd, (off_t)0)) == (char *)-1) ! err(1, "%s", from_name); siz = (size_t)size; if (write(to_fd, p, siz) != siz) ! err(1, "%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); ! errx(1, "%s: %s", to_name, strerror(nw > 0 ? EIO : serrno)); } if (nr != 0) { serrno = errno; (void)unlink(to_name); ! errx(1, "%s: %s", from_name, strerror(serrno)); } } } /* * strip -- * use strip(1) to strip the target file */ --- 415,497 ---- if (size <= 8 * 1048576) { if ((p = mmap(NULL, (size_t)size, PROT_READ, 0, from_fd, (off_t)0)) == (char *)-1) ! err(EX_OSERR, "%s", from_name); siz = (size_t)size; if (write(to_fd, p, siz) != siz) ! err(EX_OSERR, "%s", to_name); ! (void) munmap(p, (size_t)size); } else { while ((nr = read(from_fd, buf, sizeof(buf))) > 0) if ((nw = write(to_fd, buf, nr)) != nr) { serrno = errno; (void)unlink(to_name); ! errx(EX_OSERR, "%s: %s", to_name, strerror(nw > 0 ? EIO : serrno)); } if (nr != 0) { serrno = errno; (void)unlink(to_name); ! errx(EX_OSERR, "%s: %s", from_name, strerror(serrno)); } } } /* + * compare -- + * compare two files; non-zero means files differ + */ + int + compare(from_fd, from_name, from_len, to_fd, to_name, to_len) + int from_fd; + const char *from_name; + size_t from_len; + int to_fd; + const char *to_name; + size_t to_len; + { + caddr_t p1, p2; + register size_t length, remainder; + int dfound; + + if (from_len != to_len) + return(1); + + /* Rewind file descriptors. */ + if (lseek(from_fd, (off_t)0, SEEK_SET) == (off_t)-1) + err(EX_OSERR, "lseek: %s", from_name); + if (lseek(to_fd, (off_t)0, SEEK_SET) == (off_t)-1) + err(EX_OSERR, "lseek: %s", to_name); + + /* + * Compare the two files being careful not to mmap + * more than 8M at a time. + */ + remainder = from_len; + do { + length = MIN(remainder, 8 * 1048576); + remainder -= length; + + if ((p1 = mmap(NULL, length, PROT_READ, 0, from_fd, (off_t)0)) + == (caddr_t)-1) + err(EX_OSERR, "%s", from_name); + if ((p2 = mmap(NULL, length, PROT_READ, 0, to_fd, (off_t)0)) + == (caddr_t)-1) + err(EX_OSERR, "%s", to_name); + + dfound = memcmp(p1, p2, length); + + (void) munmap(p1, length); + (void) munmap(p2, length); + + if (dfound) + break; + + } while (remainder > 0); + + return(dfound); + } + + /* * strip -- * use strip(1) to strip the target file */ *************** *** 345,354 **** case -1: serrno = errno; (void)unlink(to_name); ! errx(1, "forks: %s", strerror(errno)); case 0: execl(_PATH_STRIP, "strip", to_name, NULL); ! err(1, "%s", _PATH_STRIP); default: if (wait(&status) == -1 || status) (void)unlink(to_name); --- 505,514 ---- case -1: serrno = errno; (void)unlink(to_name); ! errx(EX_TEMPFAIL, "forks: %s", strerror(serrno)); case 0: execl(_PATH_STRIP, "strip", to_name, NULL); ! err(EX_OSERR, "%s", _PATH_STRIP); default: if (wait(&status) == -1 || status) (void)unlink(to_name); *************** *** 373,379 **** *p = '\0'; if (stat(path, &sb)) { if (errno != ENOENT || mkdir(path, 0777) < 0) { ! err(1, "%s", path); /* NOTREACHED */ } } --- 533,539 ---- *p = '\0'; if (stat(path, &sb)) { if (errno != ENOENT || mkdir(path, 0777) < 0) { ! err(EX_OSERR, "%s", path); /* NOTREACHED */ } } *************** *** 381,387 **** break; } ! if (((gid != -1 || uid != -1) && chown(path, uid, gid)) || chmod(path, mode)) { warn("%s", path); } --- 541,547 ---- break; } ! if (((gid != (gid_t)-1 || uid != (uid_t)-1) && chown(path, uid, gid)) || chmod(path, mode)) { warn("%s", path); } *************** *** 398,402 **** usage: install [-cs] [-f flags] [-g group] [-m mode] [-o owner] file1 file2\n\ install [-cs] [-f flags] [-g group] [-m mode] [-o owner] file1 ... fileN directory\n\ install -d [-g group] [-m mode] [-o owner] directory ...\n"); ! exit(1); } --- 558,609 ---- usage: install [-cs] [-f flags] [-g group] [-m mode] [-o owner] file1 file2\n\ install [-cs] [-f flags] [-g group] [-m mode] [-o owner] file1 ... fileN directory\n\ install -d [-g group] [-m mode] [-o owner] directory ...\n"); ! exit(EX_USAGE); ! /* NOTREACHED */ ! } ! ! /* ! * create_tempfile -- ! * create a temporary file based on path and open it ! */ ! int ! create_tempfile(path, temp, tsize) ! char *path; ! char *temp; ! size_t tsize; ! { ! char *p; ! ! (void)strncpy(temp, path, tsize); ! temp[tsize - 1] = '\0'; ! if ((p = strrchr(temp, '/'))) ! p++; ! else ! p = temp; ! (void)strncpy(p, "INS@XXXXXX", &temp[tsize - 1] - p); ! temp[tsize - 1] = '\0'; ! ! return(mkstemp(temp)); ! } ! ! /* ! * create_newfile -- ! * create a new file, overwriting an existing one if necesary ! */ ! int ! create_newfile(path, sbp) ! char *path; ! struct stat *sbp; ! { ! /* ! * Unlink now... avoid ETXTBSY errors later. Try and turn ! * off the append/immutable bits -- if we fail, go ahead, ! * it might work. ! */ ! if (sbp->st_flags & (NOCHANGEBITS)) ! (void)chflags(path, sbp->st_flags & ~(NOCHANGEBITS)); ! ! (void)unlink(path); ! ! return(open(path, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR)); }