#include <sys/types.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>
#include <paths.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <libgen.h>
#include "pathnames.h"
#define _MAXBSIZE (64 * 1024)
#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
#define DIRECTORY 0x01
#define SETFLAGS 0x02
#define USEFSYNC 0x04
#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;
case 'b':
dobackup = 1;
break;
case 'c':
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':
break;
case 's':
dostrip = 1;
break;
case 'D':
dodest = 1;
break;
case 'd':
dodir = 1;
break;
default:
usage();
}
argc -= optind;
argv += optind;
if ((docompare || dostrip) && dodir)
usage();
if (argc == 0 || (argc == 1 && !dodir))
usage();
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);
}
if (dodest) {
char *dest = dirname(argv[argc - 1]);
if (dest == NULL)
errx(1, "cannot determine dirname");
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);
}
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);
}
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 (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);
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) {
if (docompare && !S_ISREG(to_sb.st_mode)) {
docompare = 0;
warnc(EFTYPE, "%s", to_name);
}
} else if (docompare) {
docompare = 0;
}
if (!devnull) {
if ((from_fd = open(from_name, O_RDONLY)) == -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);
close(to_fd);
if ((to_fd = open(tempfile, O_RDONLY)) == -1)
err(1, "stripping %s", to_name);
}
if (docompare) {
int temp_fd = to_fd;
struct stat temp_sb;
if ((to_fd = open(to_name, O_RDONLY)) == -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 (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;
}
}
if (dopreserve && !files_match) {
ts[0] = from_sb.st_atim;
ts[1] = from_sb.st_mtim;
futimens(to_fd, ts);
}
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 (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);
if (!files_match) {
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);
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));
}
}
}
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;
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);
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;
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);
}
}
}
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);
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);
}
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);
}
}
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)) {
errc(1, mkdir_errno, "%s",
path);
}
if (!S_ISDIR(sb.st_mode)) {
errc(1, ENOTDIR, "%s", path);
}
}
if (!(*p = ch))
break;
}
if (((gid != (gid_t)-1 || uid != (uid_t)-1) && chown(path, uid, gid)) ||
chmod(path, mode)) {
warn("%s", path);
}
}
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);
}
int
create_tempfile(char *path, char *temp, size_t tsize)
{
char *p;
if (strlcpy(temp, path, tsize) >= tsize) {
errno = ENAMETOOLONG;
return(-1);
}
if ((p = strrchr(temp, '/')) != NULL)
p++;
else
p = temp;
*p = '\0';
if (strlcat(temp, "INS@XXXXXXXXXX", tsize) >= tsize) {
errno = ENAMETOOLONG;
return(-1);
}
return(mkstemp(temp));
}
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 (cnt) {
if (!*rem) {
*isempt = 1;
*rem = sz;
}
wcnt = MINIMUM(cnt, *rem);
cnt -= wcnt;
*rem -= wcnt;
if (*isempt) {
pt = st;
end = st + wcnt;
while ((pt < end) && (*pt == '\0'))
++pt;
if (pt == end) {
if (lseek(fd, (off_t)wcnt, SEEK_CUR) == -1) {
warn("lseek");
return(-1);
}
st = pt;
continue;
}
*isempt = 0;
}
if (write(fd, st, wcnt) != wcnt) {
warn("write");
return(-1);
}
st += wcnt;
}
return(st - str);
}
void
file_flush(int fd, int isempt)
{
static char blnk[] = "\0";
if (!isempt)
return;
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;
}