Files
sbase/tar.c
T

602 lines
13 KiB
C
Raw Normal View History

2013-07-18 11:15:35 -04:00
/* 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>
2013-07-18 11:15:35 -04:00
#include <string.h>
#include <unistd.h>
2014-11-13 18:29:30 +01:00
2015-03-13 00:25:32 +01:00
#include "fs.h"
2013-07-18 11:15:35 -04:00
#include "util.h"
2015-04-23 17:45:35 +01:00
#define BLKSIZ 512
enum Type {
REG = '0',
AREG = '\0',
HARDLINK = '1',
SYMLINK = '2',
CHARDEV = '3',
BLOCKDEV = '4',
DIRECTORY = '5',
FIFO = '6',
RESERVED = '7'
2015-04-23 17:45:35 +01:00
};
2015-02-16 19:47:36 +01:00
struct header {
2013-07-18 11:15:35 -04:00
char name[100];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char chksum[8];
char type;
2015-04-23 16:14:09 +01:00
char linkname[100];
2015-02-16 20:01:33 +01:00
char magic[6];
char version[2];
2013-07-18 11:15:35 -04:00
char uname[32];
char gname[32];
char major[8];
char minor[8];
2014-11-01 20:36:40 +00:00
char prefix[155];
2013-07-18 11:15:35 -04:00
};
static struct dirtime {
2015-04-23 17:45:35 +01:00
char *name;
time_t mtime;
} *dirtimes;
static size_t dirtimeslen;
2013-07-18 11:15:35 -04:00
static int tarfd;
2013-07-20 18:08:58 +02:00
static ino_t tarinode;
static dev_t tardev;
2013-07-18 11:15:35 -04:00
2015-05-08 12:28:51 +02:00
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",
};
2013-07-18 11:15:35 -04:00
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)
2013-07-18 11:15:35 -04:00
{
int fds[2];
if (pipe(fds) < 0)
eprintf("pipe:");
2015-03-09 15:01:29 +01:00
switch (fork()) {
case -1:
2015-03-10 20:05:18 +01:00
eprintf("fork:");
2015-03-09 15:01:29 +01:00
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);
2013-07-18 11:15:35 -04:00
}
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;
2013-07-18 11:15:35 -04:00
}
2014-06-01 14:59:47 +02:00
static void
putoctal(char *dst, unsigned num, int size)
2013-07-18 11:15:35 -04:00
{
if (snprintf(dst, size, "%.*o", size - 1, num) >= size)
eprintf("snprintf: input number too large\n");
2013-07-18 11:15:35 -04:00
}
2014-06-01 14:59:47 +02:00
static int
archive(const char *path)
2013-07-18 11:15:35 -04:00
{
2015-04-23 15:31:26 +01:00
char b[BLKSIZ];
2015-02-16 19:47:36 +01:00
struct group *gr;
struct header *h;
struct passwd *pw;
struct stat st;
size_t chksum, i;
ssize_t l, r;
int fd = -1;
2013-07-18 11:15:35 -04:00
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);
2013-07-18 11:15:35 -04:00
h = (struct header *)b;
2015-02-16 19:47:36 +01:00
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)) {
2013-07-18 11:15:35 -04:00
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)) {
2013-07-18 11:15:35 -04:00
h->type = DIRECTORY;
} else if (S_ISLNK(st.st_mode)) {
2013-07-18 11:15:35 -04:00
h->type = SYMLINK;
2015-04-23 16:14:09 +01:00
if ((r = readlink(path, h->linkname, sizeof(h->linkname) - 1)) < 0)
eprintf("readlink %s:", path);
2015-04-23 16:14:09 +01:00
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)) {
2013-07-18 11:15:35 -04:00
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);
2013-07-18 11:15:35 -04:00
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);
2013-07-18 11:15:35 -04:00
}
2013-07-19 17:05:28 +01:00
return 0;
2013-07-18 11:15:35 -04:00
}
2014-06-01 14:59:47 +02:00
static int
unarchive(char *fname, ssize_t l, char b[BLKSIZ])
2013-07-18 11:15:35 -04:00
{
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];
2015-04-20 16:33:24 +01:00
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);
2013-07-18 11:15:35 -04:00
2015-04-20 18:32:15 +02:00
tmp = estrdup(fname);
2016-12-14 19:40:06 -08:00
mkdirp(dirname(tmp), 0777, 0777);
free(tmp);
2014-11-13 18:29:30 +01:00
switch (h->type) {
2013-07-18 11:15:35 -04:00
case REG:
2014-11-01 20:36:38 +00:00
case AREG:
case RESERVED:
2015-04-20 16:33:24 +01:00
if ((mode = strtol(h->mode, &p, 8)) < 0 || *p != '\0')
eprintf("strtol %s: invalid number\n", h->mode);
2015-11-18 20:49:07 -06:00
fd = open(fname, O_WRONLY | O_TRUNC | O_CREAT, 0600);
if (fd < 0)
eprintf("open %s:", fname);
2013-07-18 11:15:35 -04:00
break;
case HARDLINK:
case SYMLINK:
2015-04-23 16:14:09 +01:00
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);
2013-07-18 11:15:35 -04:00
break;
case DIRECTORY:
2015-04-20 16:33:24 +01:00
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);
2013-07-18 11:15:35 -04:00
break;
case CHARDEV:
case BLOCKDEV:
2015-04-20 16:33:24 +01:00
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);
2013-07-18 11:15:35 -04:00
type = (h->type == CHARDEV) ? S_IFCHR : S_IFBLK;
if (mknod(fname, type | mode, makedev(major, minor)) < 0)
eprintf("mknod %s:", fname);
2013-07-18 11:15:35 -04:00
break;
case FIFO:
2015-04-20 16:33:24 +01:00
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);
2013-07-18 11:15:35 -04:00
break;
default:
eprintf("unsupported tar-filetype %c\n", h->type);
2013-07-18 11:15:35 -04:00
}
2015-04-20 16:33:24 +01:00
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);
2013-07-18 11:15:35 -04:00
if (fd != -1) {
for (; l > 0; l -= BLKSIZ)
if (eread(tarfd, b, BLKSIZ) > 0)
ewrite(fd, b, MIN(l, BLKSIZ));
close(fd);
2013-07-18 11:15:35 -04:00
}
2014-11-01 20:36:39 +00:00
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);
}
2013-07-18 11:15:35 -04:00
return 0;
}
2015-04-23 12:06:44 +01:00
static void
skipblk(ssize_t l)
2013-07-18 11:15:35 -04:00
{
2015-04-23 12:06:44 +01:00
char b[BLKSIZ];
2015-02-16 19:47:36 +01:00
for (; l > 0; l -= BLKSIZ)
if (!eread(tarfd, b, BLKSIZ))
break;
2015-04-23 12:06:44 +01:00
}
2015-04-23 12:06:44 +01:00
static int
print(char *fname, ssize_t l, char b[BLKSIZ])
{
puts(fname);
skipblk(l);
2013-07-18 11:15:35 -04:00
return 0;
}
2014-06-01 14:59:47 +02:00
static void
2015-03-19 00:53:42 +01:00
c(const char *path, struct stat *st, void *data, struct recursor *r)
2013-07-18 11:15:35 -04:00
{
2014-01-30 14:12:12 +00:00
archive(path);
2015-05-08 12:28:51 +02:00
if (vflag)
puts(path);
2015-03-19 00:53:42 +01:00
if (S_ISDIR(st->st_mode))
2015-03-19 00:53:42 +01:00
recurse(path, NULL, r);
2013-07-18 11:15:35 -04:00
}
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)
{
2017-02-03 14:56:49 -08:00
char tmp[8], *err, *p = (char *)h;
const char *reason;
long s1, s2, i;
2017-02-03 14:56:49 -08:00
if (h->prefix[0] == '\0' && h->name[0] == '\0') {
reason = "empty filename";
goto bad;
2017-02-03 14:56:49 -08:00
}
if (h->magic[0] && strncmp("ustar", h->magic, 5)) {
reason = "not ustar format";
goto bad;
2017-02-03 14:56:49 -08:00
}
memcpy(tmp, h->chksum, sizeof(tmp));
for (i = 0; i < sizeof(tmp); i++)
if (tmp[i] == ' ')
tmp[i] = '\0';
s1 = strtol(tmp, &err, 8);
2017-02-03 14:56:49 -08:00
if (s1 < 0 || *err != '\0') {
reason = "invalid checksum";
goto bad;
2017-02-03 14:56:49 -08:00
}
memset(h->chksum, ' ', sizeof(h->chksum));
for (i = 0, s2 = 0; i < sizeof(*h); i++)
s2 += (unsigned char)p[i];
2017-02-03 14:56:49 -08:00
if (s1 != s2) {
reason = "incorrect checksum";
goto bad;
2017-02-03 14:56:49 -08:00
}
memcpy(h->chksum, tmp, sizeof(h->chksum));
return;
bad:
2017-02-03 14:56:49 -08:00
eprintf("malformed tar archive: %s\n", reason);
}
2014-06-01 14:59:47 +02:00
static void
xt(int argc, char *argv[], int mode)
2013-07-18 11:15:35 -04:00
{
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) {
2015-04-23 12:06:44 +01:00
skipblk(size);
continue;
}
}
2015-04-23 00:04:55 +01:00
/* ignore global pax header craziness */
2015-05-08 22:28:24 +02:00
if (h->type == 'g' || h->type == 'x') {
2015-04-23 12:06:44 +01:00
skipblk(size);
2015-04-23 00:04:55 +01:00
continue;
}
fn(fname, size, b);
2015-05-08 12:28:51 +02:00
if (vflag && mode != 't')
puts(fname);
2013-07-18 11:15:35 -04:00
}
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;
}
2013-07-18 11:15:35 -04:00
}
static void
usage(void)
{
2015-12-21 18:36:28 +01:00
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[])
{
2015-04-18 22:04:49 +02:00
struct recursor r = { .fn = c, .hist = NULL, .depth = 0, .maxdepth = 0,
.follow = 'P', .flags = DIRFIRST };
2015-02-16 19:47:36 +01:00
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':
2015-03-13 00:25:32 +01:00
r.follow = 'L';
break;
2015-05-08 12:28:51 +02:00
case 'v':
vflag = 1;
break;
default:
usage();
2015-11-01 10:16:49 +00:00
} ARGEND
if (!mode)
usage();
if (mode == 'c')
if (!argc)
usage();
switch (mode) {
case 'c':
tarfd = 1;
2016-02-15 09:48:32 +00:00
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;
2016-02-15 09:48:32 +00:00
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;
}
2015-03-13 00:25:32 +01:00
return recurse_status;
}