sbase/tar.c
FRIGN 01de5df8e6 Audit du(1) and refactor recurse()
While auditing du(1) I realized that there's no way the over 100 lines
of procedures in du() would pass the audit.
Instead, I decided to rewrite this section using recurse() from libutil.
However, the issue was that you'd need some kind of payload to count
the number of bytes in the subdirectories and use them in the higher
hierarchies.
The solution is to add a "void *data" data pointer to each recurse-
function-prototype, which we might also be able to use in other
recurse-applications.
recurse() itself had to be augmented with a recurse_samedev-flag, which
basically prevents recurse from leaving the current device.

Now, let's take a closer look at the audit:
1) Removing the now unnecessary util-functions push, pop, xrealpath,
   rename print() to printpath(), localize some global variables.
2) Only pass the block count to nblks instead of the entire stat-
   pointer.
3) Fix estrtonum to use the minimum of LLONG_MAX and SIZE_MAX.
4) Use idiomatic argv+argc-loop
5) Report proper exit-status.
2015-03-11 23:21:52 +01:00

347 lines
6.8 KiB
C

/* See LICENSE file for copyright and license details. */
#include <sys/stat.h>
#include <sys/time.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "util.h"
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 link[100];
char magic[6];
char version[2];
char uname[32];
char gname[32];
char major[8];
char minor[8];
char prefix[155];
};
#define BLKSIZ 512
enum Type {
REG = '0', AREG = '\0', HARDLINK = '1', SYMLINK = '2', CHARDEV = '3',
BLOCKDEV = '4', DIRECTORY = '5', FIFO = '6'
};
static FILE *tarfile;
static ino_t tarinode;
static dev_t tardev;
static int mflag;
static char filtermode;
static FILE *
decomp(FILE *fp)
{
int fds[2];
if (pipe(fds) < 0)
eprintf("pipe:");
switch (fork()) {
case -1:
eprintf("fork:");
case 0:
dup2(fileno(fp), 0);
dup2(fds[1], 1);
close(fds[0]);
close(fds[1]);
switch (filtermode) {
case 'j':
execlp("bzip2", "bzip2", "-cd", NULL);
weprintf("execlp bzip2:");
_exit(1);
case 'z':
execlp("gzip", "gzip", "-cd", NULL);
weprintf("execlp gzip:");
_exit(1);
}
}
close(fds[1]);
return fdopen(fds[0], "r");
}
static void
putoctal(char *dst, unsigned num, int n)
{
snprintf(dst, n, "%.*o", n - 1, num);
}
static int
archive(const char* path)
{
FILE *f = NULL;
mode_t mode;
struct group *gr;
struct header *h;
struct passwd *pw;
struct stat st;
size_t chksum, x;
ssize_t l;
unsigned char b[BLKSIZ];
lstat(path, &st);
if (st.st_ino == tarinode && st.st_dev == tardev) {
fprintf(stderr, "ignoring '%s'\n", path);
return 0;
}
pw = getpwuid(st.st_uid);
gr = getgrgid(st.st_gid);
h = (void*)b;
memset(b, 0, sizeof(b));
snprintf(h->name, sizeof(h->name), "%s", path);
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));
snprintf(h->uname, sizeof h->uname, "%s", pw ? pw->pw_name : "");
snprintf(h->gname, sizeof h->gname, "%s", gr ? gr->gr_name : "");
mode = st.st_mode;
if (S_ISREG(mode)) {
h->type = REG;
putoctal(h->size, (unsigned)st.st_size, sizeof h->size);
f = fopen(path, "r");
} else if (S_ISDIR(mode)) {
h->type = DIRECTORY;
} else if (S_ISLNK(mode)) {
h->type = SYMLINK;
readlink(path, h->link, (sizeof h->link)-1);
} else if (S_ISCHR(mode) || S_ISBLK(mode)) {
h->type = S_ISCHR(mode) ? CHARDEV : BLOCKDEV;
#if defined(major) && defined(minor)
putoctal(h->major, (unsigned)major(st.st_dev), sizeof h->major);
putoctal(h->minor, (unsigned)minor(st.st_dev), sizeof h->minor);
#else
return 0;
#endif
} else if (S_ISFIFO(mode)) {
h->type = FIFO;
}
memset(h->chksum, ' ', sizeof h->chksum);
for (x = 0, chksum = 0; x < sizeof *h; x++)
chksum += b[x];
putoctal(h->chksum, chksum, sizeof h->chksum);
fwrite(b, BLKSIZ, 1, tarfile);
if (!f)
return 0;
while ((l = fread(b, 1, BLKSIZ, f)) > 0) {
if (l < BLKSIZ)
memset(b+l, 0, BLKSIZ-l);
fwrite(b, BLKSIZ, 1, tarfile);
}
fclose(f);
return 0;
}
static int
unarchive(char *fname, int l, char b[BLKSIZ])
{
FILE *f = NULL;
struct timeval times[2];
struct header *h = (void*)b;
unsigned long mode, major, minor, type, mtime;
char lname[101];
if (!mflag)
mtime = strtoul(h->mtime, 0, 8);
unlink(fname);
switch (h->type) {
case REG:
case AREG:
mode = strtoul(h->mode, 0, 8);
if (!(f = fopen(fname, "w")) || chmod(fname, mode))
perror(fname);
break;
case HARDLINK:
case SYMLINK:
snprintf(lname, sizeof lname, "%s", h->link);
if (!((h->type == HARDLINK) ? link : symlink)(lname, fname))
perror(fname);
break;
case DIRECTORY:
mode = strtoul(h->mode, 0, 8);
if (mkdir(fname, (mode_t)mode))
perror(fname);
break;
case CHARDEV:
case BLOCKDEV:
#ifdef makedev
mode = strtoul(h->mode, 0, 8);
major = strtoul(h->major, 0, 8);
minor = strtoul(h->mode, 0, 8);
type = (h->type == CHARDEV) ? S_IFCHR : S_IFBLK;
if (mknod(fname, type | mode, makedev(major, minor)))
perror(fname);
#endif
break;
case FIFO:
mode = strtoul(h->mode, 0, 8);
if (mknod(fname, S_IFIFO | mode, 0))
perror(fname);
break;
default:
fprintf(stderr, "usupported tarfiletype %c\n", h->type);
}
if (getuid() == 0 && chown(fname, strtoul(h->uid, 0, 8), strtoul(h->gid, 0, 8)))
perror(fname);
for (; l > 0; l -= BLKSIZ) {
fread(b, BLKSIZ, 1, tarfile);
if (f)
fwrite(b, MIN(l, 512), 1, f);
}
if (f)
fclose(f);
if (!mflag) {
times[0].tv_sec = times[1].tv_sec = mtime;
times[0].tv_usec = times[1].tv_usec = 0;
if (utimes(fname, times))
perror(fname);
}
return 0;
}
static int
print(char * fname, int l, char b[BLKSIZ])
{
puts(fname);
for (; l > 0; l -= BLKSIZ)
fread(b, BLKSIZ, 1, tarfile);
return 0;
}
static void
c(const char *path, int depth, void *data)
{
archive(path);
recurse(path, c, depth, NULL);
}
static void
xt(int (*fn)(char *, int, char[BLKSIZ]))
{
char b[BLKSIZ], fname[257], *s;
struct header *h = (void*)b;
while (fread(b, BLKSIZ, 1, tarfile) && h->name[0] != '\0') {
s = fname;
if (h->prefix[0] != '\0')
s += sprintf(s, "%.*s/", (int)sizeof h->prefix, h->prefix);
sprintf(s, "%.*s", (int)sizeof h->name, h->name);
fn(fname, strtol(h->size, 0, 8), b);
}
}
static void
usage(void)
{
eprintf("usage: tar [-f tarfile] [-C dir] -j|z -x[m]|t\n"
" tar [-f tarfile] [-C dir] -c dir\n");
}
int
main(int argc, char *argv[])
{
FILE *fp;
struct stat st;
char *file = NULL, *dir = ".", mode = '\0';
ARGBEGIN {
case 'x':
case 'c':
case 't':
if (mode)
usage();
mode = ARGC();
break;
case 'C':
dir = EARGF(usage());
break;
case 'f':
file = EARGF(usage());
break;
case 'm':
mflag = 1;
break;
case 'j':
case 'z':
if (filtermode)
usage();
filtermode = ARGC();
break;
case 'h':
recurse_follow = 'L';
break;
default:
usage();
} ARGEND;
if (!mode || argc != (mode == 'c'))
usage();
switch (mode) {
case 'c':
if (file) {
if (!(fp = fopen(file, "w")))
eprintf("fopen %s:", file);
if (lstat(file, &st) < 0)
eprintf("tar: stat '%s':", file);
tarinode = st.st_ino;
tardev = st.st_dev;
tarfile = fp;
} else {
tarfile = stdout;
}
chdir(dir);
c(argv[0], 0, NULL);
break;
case 't':
case 'x':
if (file) {
if (!(fp = fopen(file, "r")))
eprintf("fopen %s:", file);
} else {
fp = stdin;
}
switch (filtermode) {
case 'j':
case 'z':
tarfile = decomp(fp);
break;
default:
tarfile = fp;
break;
}
chdir(dir);
xt(mode == 'x' ? unarchive : print);
break;
}
return 0;
}