mirror of
git://git.suckless.org/sbase
synced 2026-01-26 13:43:17 +00:00
In the description of 3111908b034c73673a2f079b2b13a88c18379baa, it says that the functions must be able to handle st being NULL, but recurse always passes a valid pointer. The only function that was ever passed NULL was rm(), but this was changed to go through recurse in 2f4ab527391135e651b256f8654b050ea4a48f3d, so now the checks are pointless.
594 lines
13 KiB
C
594 lines
13 KiB
C
/* See LICENSE file for copyright and license details. */
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <grp.h>
|
|
#include <libgen.h>
|
|
#include <pwd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "fs.h"
|
|
#include "util.h"
|
|
|
|
#define BLKSIZ 512
|
|
|
|
enum Type {
|
|
REG = '0',
|
|
AREG = '\0',
|
|
HARDLINK = '1',
|
|
SYMLINK = '2',
|
|
CHARDEV = '3',
|
|
BLOCKDEV = '4',
|
|
DIRECTORY = '5',
|
|
FIFO = '6',
|
|
RESERVED = '7'
|
|
};
|
|
|
|
struct header {
|
|
char name[100];
|
|
char mode[8];
|
|
char uid[8];
|
|
char gid[8];
|
|
char size[12];
|
|
char mtime[12];
|
|
char chksum[8];
|
|
char type;
|
|
char linkname[100];
|
|
char magic[6];
|
|
char version[2];
|
|
char uname[32];
|
|
char gname[32];
|
|
char major[8];
|
|
char minor[8];
|
|
char prefix[155];
|
|
};
|
|
|
|
static struct dirtime {
|
|
char *name;
|
|
time_t mtime;
|
|
} *dirtimes;
|
|
|
|
static size_t dirtimeslen;
|
|
|
|
static int tarfd;
|
|
static ino_t tarinode;
|
|
static dev_t tardev;
|
|
|
|
static int mflag, vflag;
|
|
static int filtermode;
|
|
static const char *filtertool;
|
|
|
|
static const char *filtertools[] = {
|
|
['J'] = "xz",
|
|
['Z'] = "compress",
|
|
['a'] = "lzma",
|
|
['j'] = "bzip2",
|
|
['z'] = "gzip",
|
|
};
|
|
|
|
static void
|
|
pushdirtime(char *name, time_t mtime)
|
|
{
|
|
dirtimes = reallocarray(dirtimes, dirtimeslen + 1, sizeof(*dirtimes));
|
|
dirtimes[dirtimeslen].name = strdup(name);
|
|
dirtimes[dirtimeslen].mtime = mtime;
|
|
dirtimeslen++;
|
|
}
|
|
|
|
static struct dirtime *
|
|
popdirtime(void)
|
|
{
|
|
if (dirtimeslen) {
|
|
dirtimeslen--;
|
|
return &dirtimes[dirtimeslen];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
comp(int fd, const char *tool, const char *flags)
|
|
{
|
|
int fds[2];
|
|
|
|
if (pipe(fds) < 0)
|
|
eprintf("pipe:");
|
|
|
|
switch (fork()) {
|
|
case -1:
|
|
eprintf("fork:");
|
|
case 0:
|
|
dup2(fd, 1);
|
|
dup2(fds[0], 0);
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
|
|
execlp(tool, tool, flags, NULL);
|
|
weprintf("execlp %s:", tool);
|
|
_exit(1);
|
|
}
|
|
close(fds[0]);
|
|
return fds[1];
|
|
}
|
|
|
|
static int
|
|
decomp(int fd, const char *tool, const char *flags)
|
|
{
|
|
int fds[2];
|
|
|
|
if (pipe(fds) < 0)
|
|
eprintf("pipe:");
|
|
|
|
switch (fork()) {
|
|
case -1:
|
|
eprintf("fork:");
|
|
case 0:
|
|
dup2(fd, 0);
|
|
dup2(fds[1], 1);
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
|
|
execlp(tool, tool, flags, NULL);
|
|
weprintf("execlp %s:", tool);
|
|
_exit(1);
|
|
}
|
|
close(fds[1]);
|
|
return fds[0];
|
|
}
|
|
|
|
static ssize_t
|
|
eread(int fd, void *buf, size_t n)
|
|
{
|
|
ssize_t r;
|
|
|
|
again:
|
|
r = read(fd, buf, n);
|
|
if (r < 0) {
|
|
if (errno == EINTR)
|
|
goto again;
|
|
eprintf("read:");
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static ssize_t
|
|
ewrite(int fd, const void *buf, size_t n)
|
|
{
|
|
ssize_t r;
|
|
|
|
if ((r = write(fd, buf, n)) != n)
|
|
eprintf("write:");
|
|
return r;
|
|
}
|
|
|
|
static void
|
|
putoctal(char *dst, unsigned num, int size)
|
|
{
|
|
if (snprintf(dst, size, "%.*o", size - 1, num) >= size)
|
|
eprintf("snprintf: input number too large\n");
|
|
}
|
|
|
|
static int
|
|
archive(const char *path)
|
|
{
|
|
char b[BLKSIZ];
|
|
struct group *gr;
|
|
struct header *h;
|
|
struct passwd *pw;
|
|
struct stat st;
|
|
size_t chksum, i;
|
|
ssize_t l, r;
|
|
int fd = -1;
|
|
|
|
if (lstat(path, &st) < 0) {
|
|
weprintf("lstat %s:", path);
|
|
return 0;
|
|
} else if (st.st_ino == tarinode && st.st_dev == tardev) {
|
|
weprintf("ignoring %s\n", path);
|
|
return 0;
|
|
}
|
|
|
|
pw = getpwuid(st.st_uid);
|
|
gr = getgrgid(st.st_gid);
|
|
|
|
h = (struct header *)b;
|
|
memset(b, 0, sizeof(b));
|
|
estrlcpy(h->name, path, sizeof(h->name));
|
|
putoctal(h->mode, (unsigned)st.st_mode & 0777, sizeof(h->mode));
|
|
putoctal(h->uid, (unsigned)st.st_uid, sizeof(h->uid));
|
|
putoctal(h->gid, (unsigned)st.st_gid, sizeof(h->gid));
|
|
putoctal(h->size, 0, sizeof(h->size));
|
|
putoctal(h->mtime, (unsigned)st.st_mtime, sizeof(h->mtime));
|
|
memcpy( h->magic, "ustar", sizeof(h->magic));
|
|
memcpy( h->version, "00", sizeof(h->version));
|
|
estrlcpy(h->uname, pw ? pw->pw_name : "", sizeof(h->uname));
|
|
estrlcpy(h->gname, gr ? gr->gr_name : "", sizeof(h->gname));
|
|
|
|
if (S_ISREG(st.st_mode)) {
|
|
h->type = REG;
|
|
putoctal(h->size, (unsigned)st.st_size, sizeof(h->size));
|
|
fd = open(path, O_RDONLY);
|
|
if (fd < 0)
|
|
eprintf("open %s:", path);
|
|
} else if (S_ISDIR(st.st_mode)) {
|
|
h->type = DIRECTORY;
|
|
} else if (S_ISLNK(st.st_mode)) {
|
|
h->type = SYMLINK;
|
|
if ((r = readlink(path, h->linkname, sizeof(h->linkname) - 1)) < 0)
|
|
eprintf("readlink %s:", path);
|
|
h->linkname[r] = '\0';
|
|
} else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
|
|
h->type = S_ISCHR(st.st_mode) ? CHARDEV : BLOCKDEV;
|
|
putoctal(h->major, (unsigned)major(st.st_dev), sizeof(h->major));
|
|
putoctal(h->minor, (unsigned)minor(st.st_dev), sizeof(h->minor));
|
|
} else if (S_ISFIFO(st.st_mode)) {
|
|
h->type = FIFO;
|
|
}
|
|
|
|
memset(h->chksum, ' ', sizeof(h->chksum));
|
|
for (i = 0, chksum = 0; i < sizeof(*h); i++)
|
|
chksum += (unsigned char)b[i];
|
|
putoctal(h->chksum, chksum, sizeof(h->chksum));
|
|
ewrite(tarfd, b, BLKSIZ);
|
|
|
|
if (fd != -1) {
|
|
while ((l = eread(fd, b, BLKSIZ)) > 0) {
|
|
if (l < BLKSIZ)
|
|
memset(b + l, 0, BLKSIZ - l);
|
|
ewrite(tarfd, b, BLKSIZ);
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
unarchive(char *fname, ssize_t l, char b[BLKSIZ])
|
|
{
|
|
char lname[101], *tmp, *p;
|
|
long mode, major, minor, type, mtime, uid, gid;
|
|
struct header *h = (struct header *)b;
|
|
int fd = -1;
|
|
struct timespec times[2];
|
|
|
|
if (!mflag && ((mtime = strtol(h->mtime, &p, 8)) < 0 || *p != '\0'))
|
|
eprintf("strtol %s: invalid number\n", h->mtime);
|
|
if (remove(fname) < 0 && errno != ENOENT)
|
|
weprintf("remove %s:", fname);
|
|
|
|
tmp = estrdup(fname);
|
|
mkdirp(dirname(tmp));
|
|
free(tmp);
|
|
|
|
switch (h->type) {
|
|
case REG:
|
|
case AREG:
|
|
case RESERVED:
|
|
if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0')
|
|
eprintf("strtol %s: invalid number\n", h->mode);
|
|
fd = open(fname, O_WRONLY | O_TRUNC | O_CREAT, 0600);
|
|
if (fd < 0)
|
|
eprintf("open %s:", fname);
|
|
break;
|
|
case HARDLINK:
|
|
case SYMLINK:
|
|
snprintf(lname, sizeof(lname), "%.*s", (int)sizeof(h->linkname),
|
|
h->linkname);
|
|
if (((h->type == HARDLINK) ? link : symlink)(lname, fname) < 0)
|
|
eprintf("%s %s -> %s:",
|
|
(h->type == HARDLINK) ? "link" : "symlink",
|
|
fname, lname);
|
|
break;
|
|
case DIRECTORY:
|
|
if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0')
|
|
eprintf("strtol %s: invalid number\n", h->mode);
|
|
if (mkdir(fname, (mode_t)mode) < 0 && errno != EEXIST)
|
|
eprintf("mkdir %s:", fname);
|
|
pushdirtime(fname, mtime);
|
|
break;
|
|
case CHARDEV:
|
|
case BLOCKDEV:
|
|
if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0')
|
|
eprintf("strtol %s: invalid number\n", h->mode);
|
|
if ((major = strtol(h->major, &p, 8)) < 0 || *p != '\0')
|
|
eprintf("strtol %s: invalid number\n", h->major);
|
|
if ((minor = strtol(h->minor, &p, 8)) < 0 || *p != '\0')
|
|
eprintf("strtol %s: invalid number\n", h->minor);
|
|
type = (h->type == CHARDEV) ? S_IFCHR : S_IFBLK;
|
|
if (mknod(fname, type | mode, makedev(major, minor)) < 0)
|
|
eprintf("mknod %s:", fname);
|
|
break;
|
|
case FIFO:
|
|
if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0')
|
|
eprintf("strtol %s: invalid number\n", h->mode);
|
|
if (mknod(fname, S_IFIFO | mode, 0) < 0)
|
|
eprintf("mknod %s:", fname);
|
|
break;
|
|
default:
|
|
eprintf("unsupported tar-filetype %c\n", h->type);
|
|
}
|
|
|
|
if ((uid = strtol(h->uid, &p, 8)) < 0 || *p != '\0')
|
|
eprintf("strtol %s: invalid number\n", h->uid);
|
|
if ((gid = strtol(h->gid, &p, 8)) < 0 || *p != '\0')
|
|
eprintf("strtol %s: invalid number\n", h->gid);
|
|
|
|
if (fd != -1) {
|
|
for (; l > 0; l -= BLKSIZ)
|
|
if (eread(tarfd, b, BLKSIZ) > 0)
|
|
ewrite(fd, b, MIN(l, BLKSIZ));
|
|
close(fd);
|
|
}
|
|
|
|
if (h->type == HARDLINK)
|
|
return 0;
|
|
|
|
times[0].tv_sec = times[1].tv_sec = mtime;
|
|
times[0].tv_nsec = times[1].tv_nsec = 0;
|
|
if (!mflag && utimensat(AT_FDCWD, fname, times, AT_SYMLINK_NOFOLLOW) < 0)
|
|
weprintf("utimensat %s:\n", fname);
|
|
if (h->type == SYMLINK) {
|
|
if (!getuid() && lchown(fname, uid, gid))
|
|
weprintf("lchown %s:\n", fname);
|
|
} else {
|
|
if (!getuid() && chown(fname, uid, gid))
|
|
weprintf("chown %s:\n", fname);
|
|
if (chmod(fname, mode) < 0)
|
|
eprintf("fchmod %s:\n", fname);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
skipblk(ssize_t l)
|
|
{
|
|
char b[BLKSIZ];
|
|
|
|
for (; l > 0; l -= BLKSIZ)
|
|
if (!eread(tarfd, b, BLKSIZ))
|
|
break;
|
|
}
|
|
|
|
static int
|
|
print(char *fname, ssize_t l, char b[BLKSIZ])
|
|
{
|
|
puts(fname);
|
|
skipblk(l);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
c(const char *path, struct stat *st, void *data, struct recursor *r)
|
|
{
|
|
archive(path);
|
|
if (vflag)
|
|
puts(path);
|
|
|
|
if (S_ISDIR(st->st_mode))
|
|
recurse(path, NULL, r);
|
|
}
|
|
|
|
static void
|
|
sanitize(struct header *h)
|
|
{
|
|
size_t i, j;
|
|
struct {
|
|
char *f;
|
|
size_t l;
|
|
} fields[] = {
|
|
{ h->mode, sizeof(h->mode) },
|
|
{ h->uid, sizeof(h->uid) },
|
|
{ h->gid, sizeof(h->gid) },
|
|
{ h->size, sizeof(h->size) },
|
|
{ h->mtime, sizeof(h->mtime) },
|
|
{ h->chksum, sizeof(h->chksum) },
|
|
{ h->major, sizeof(h->major) },
|
|
{ h->minor, sizeof(h->minor) }
|
|
};
|
|
|
|
/* Numeric fields can be terminated with spaces instead of
|
|
* NULs as per the ustar specification. Patch all of them to
|
|
* use NULs so we can perform string operations on them. */
|
|
for (i = 0; i < LEN(fields); i++)
|
|
for (j = 0; j < fields[i].l; j++)
|
|
if (fields[i].f[j] == ' ')
|
|
fields[i].f[j] = '\0';
|
|
}
|
|
|
|
static void
|
|
chktar(struct header *h)
|
|
{
|
|
char tmp[8], *err;
|
|
char *p = (char *)h;
|
|
long s1, s2, i;
|
|
|
|
if (h->prefix[0] == '\0' && h->name[0] == '\0')
|
|
goto bad;
|
|
if (h->magic[0] && strncmp("ustar", h->magic, 5))
|
|
goto bad;
|
|
memcpy(tmp, h->chksum, sizeof(tmp));
|
|
for (i = 0; i < sizeof(tmp); i++)
|
|
if (tmp[i] == ' ')
|
|
tmp[i] = '\0';
|
|
s1 = strtol(tmp, &err, 8);
|
|
if (s1 < 0 || *err != '\0')
|
|
goto bad;
|
|
memset(h->chksum, ' ', sizeof(h->chksum));
|
|
for (i = 0, s2 = 0; i < sizeof(*h); i++)
|
|
s2 += (unsigned char)p[i];
|
|
if (s1 != s2)
|
|
goto bad;
|
|
memcpy(h->chksum, tmp, sizeof(h->chksum));
|
|
return;
|
|
bad:
|
|
eprintf("malformed tar archive\n");
|
|
}
|
|
|
|
static void
|
|
xt(int argc, char *argv[], int mode)
|
|
{
|
|
char b[BLKSIZ], fname[256 + 1], *p;
|
|
struct timespec times[2];
|
|
struct header *h = (struct header *)b;
|
|
struct dirtime *dirtime;
|
|
long size;
|
|
int i, n;
|
|
int (*fn)(char *, ssize_t, char[BLKSIZ]) = (mode == 'x') ? unarchive : print;
|
|
|
|
while (eread(tarfd, b, BLKSIZ) > 0 && h->name[0]) {
|
|
chktar(h);
|
|
sanitize(h), n = 0;
|
|
|
|
/* small dance around non-null terminated fields */
|
|
if (h->prefix[0])
|
|
n = snprintf(fname, sizeof(fname), "%.*s/",
|
|
(int)sizeof(h->prefix), h->prefix);
|
|
snprintf(fname + n, sizeof(fname) - n, "%.*s",
|
|
(int)sizeof(h->name), h->name);
|
|
|
|
if ((size = strtol(h->size, &p, 8)) < 0 || *p != '\0')
|
|
eprintf("strtol %s: invalid number\n", h->size);
|
|
|
|
if (argc) {
|
|
/* only extract the given files */
|
|
for (i = 0; i < argc; i++)
|
|
if (!strcmp(argv[i], fname))
|
|
break;
|
|
if (i == argc) {
|
|
skipblk(size);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* ignore global pax header craziness */
|
|
if (h->type == 'g' || h->type == 'x') {
|
|
skipblk(size);
|
|
continue;
|
|
}
|
|
|
|
fn(fname, size, b);
|
|
if (vflag && mode != 't')
|
|
puts(fname);
|
|
}
|
|
|
|
if (mode == 'x' && !mflag) {
|
|
while ((dirtime = popdirtime())) {
|
|
times[0].tv_sec = times[1].tv_sec = dirtime->mtime;
|
|
times[0].tv_nsec = times[1].tv_nsec = 0;
|
|
if (utimensat(AT_FDCWD, dirtime->name, times, 0) < 0)
|
|
eprintf("utimensat %s:", fname);
|
|
free(dirtime->name);
|
|
}
|
|
free(dirtimes);
|
|
dirtimes = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
eprintf("usage: %s [-C dir] [-J | -Z | -a | -j | -z] -x [-m | -t] "
|
|
"[-f file] [file ...]\n"
|
|
" %s [-C dir] [-J | -Z | -a | -j | -z] [-h] -c path ... "
|
|
"[-f file]\n", argv0, argv0);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
struct recursor r = { .fn = c, .hist = NULL, .depth = 0, .maxdepth = 0,
|
|
.follow = 'P', .flags = DIRFIRST };
|
|
struct stat st;
|
|
char *file = NULL, *dir = ".", mode = '\0';
|
|
int fd;
|
|
|
|
ARGBEGIN {
|
|
case 'x':
|
|
case 'c':
|
|
case 't':
|
|
mode = ARGC();
|
|
break;
|
|
case 'C':
|
|
dir = EARGF(usage());
|
|
break;
|
|
case 'f':
|
|
file = EARGF(usage());
|
|
break;
|
|
case 'm':
|
|
mflag = 1;
|
|
break;
|
|
case 'J':
|
|
case 'Z':
|
|
case 'a':
|
|
case 'j':
|
|
case 'z':
|
|
filtermode = ARGC();
|
|
filtertool = filtertools[filtermode];
|
|
break;
|
|
case 'h':
|
|
r.follow = 'L';
|
|
break;
|
|
case 'v':
|
|
vflag = 1;
|
|
break;
|
|
default:
|
|
usage();
|
|
} ARGEND
|
|
|
|
if (!mode)
|
|
usage();
|
|
if (mode == 'c')
|
|
if (!argc)
|
|
usage();
|
|
|
|
switch (mode) {
|
|
case 'c':
|
|
tarfd = 1;
|
|
if (file && *file != '-') {
|
|
tarfd = open(file, O_WRONLY | O_TRUNC | O_CREAT, 0644);
|
|
if (tarfd < 0)
|
|
eprintf("open %s:", file);
|
|
if (lstat(file, &st) < 0)
|
|
eprintf("lstat %s:", file);
|
|
tarinode = st.st_ino;
|
|
tardev = st.st_dev;
|
|
}
|
|
|
|
if (filtertool)
|
|
tarfd = comp(tarfd, filtertool, "-cf");
|
|
|
|
if (chdir(dir) < 0)
|
|
eprintf("chdir %s:", dir);
|
|
for (; *argv; argc--, argv++)
|
|
recurse(*argv, NULL, &r);
|
|
break;
|
|
case 't':
|
|
case 'x':
|
|
tarfd = 0;
|
|
if (file && *file != '-') {
|
|
tarfd = open(file, O_RDONLY);
|
|
if (tarfd < 0)
|
|
eprintf("open %s:", file);
|
|
}
|
|
|
|
if (filtertool) {
|
|
fd = tarfd;
|
|
tarfd = decomp(tarfd, filtertool, "-cd");
|
|
close(fd);
|
|
}
|
|
|
|
if (chdir(dir) < 0)
|
|
eprintf("chdir %s:", dir);
|
|
xt(argc, argv, mode);
|
|
break;
|
|
}
|
|
|
|
return recurse_status;
|
|
}
|