ec8246bbc6
It actually makes the binaries smaller, the code easier to read (gems like "val == true", "val == false" are gone) and actually predictable in the sense of that we actually know what we're working with (one bitwise operator was quite adventurous and should now be fixed). This is also more consistent with the other suckless projects around which don't use boolean types.
333 lines
6.8 KiB
C
333 lines
6.8 KiB
C
/* See LICENSE file for copyright and license details. */
|
|
#include <grp.h>
|
|
#include <limits.h>
|
|
#include <pwd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include "util.h"
|
|
|
|
typedef struct Header Header;
|
|
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];
|
|
};
|
|
|
|
enum {
|
|
Blksiz = 512
|
|
};
|
|
|
|
enum Type {
|
|
REG = '0', AREG = '\0', HARDLINK = '1', SYMLINK = '2', CHARDEV = '3',
|
|
BLOCKDEV = '4', DIRECTORY = '5', FIFO = '6'
|
|
};
|
|
|
|
static void putoctal(char *, unsigned, int);
|
|
static int archive(const char *);
|
|
static int unarchive(char *, int, char[Blksiz]);
|
|
static int print(char *, int , char[Blksiz]);
|
|
static void c(const char *);
|
|
static void xt(int (*)(char*, int, char[Blksiz]));
|
|
|
|
static FILE *tarfile;
|
|
static ino_t tarinode;
|
|
static dev_t tardev;
|
|
|
|
static int mflag = 0;
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
eprintf("usage: tar [-f tarfile] [-C dir] [-]x[m]|t\n"
|
|
" tar [-f tarfile] [-C dir] [-]c dir\n"
|
|
" tar [-C dir] cf tarfile dir\n"
|
|
" tar [-C dir] x[m]|tf tarfile\n");
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
struct stat st;
|
|
char *file = NULL, *dir = ".", *ap;
|
|
char 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;
|
|
default:
|
|
usage();
|
|
} ARGEND;
|
|
|
|
if (!mode) {
|
|
if (argc < 1)
|
|
usage();
|
|
|
|
for (ap = argv[0]; *ap; ap++) {
|
|
switch (*ap) {
|
|
case 'x':
|
|
case 'c':
|
|
case 't':
|
|
if (mode)
|
|
usage();
|
|
mode = *ap;
|
|
break;
|
|
case 'f':
|
|
if (argc < 2)
|
|
usage();
|
|
argc--, argv++;
|
|
file = argv[0];
|
|
break;
|
|
case 'C':
|
|
if (argc < 2)
|
|
usage();
|
|
argc--, argv++;
|
|
dir = argv[0];
|
|
break;
|
|
case 'm':
|
|
mflag = 1;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
argc--, argv++;
|
|
}
|
|
|
|
if (!mode || argc != (mode == 'c'))
|
|
usage();
|
|
|
|
if (file) {
|
|
tarfile = fopen(file, (mode == 'c') ? "wb" : "rb");
|
|
if (!tarfile)
|
|
eprintf("tar: open '%s':", file);
|
|
if (lstat(file, &st) < 0)
|
|
eprintf("tar: stat '%s':", file);
|
|
tarinode = st.st_ino;
|
|
tardev = st.st_dev;
|
|
} else {
|
|
tarfile = (mode == 'c') ? stdout : stdin;
|
|
}
|
|
|
|
chdir(dir);
|
|
|
|
if (mode == 'c') {
|
|
c(argv[0]);
|
|
} else {
|
|
xt((mode == 'x') ? unarchive : print);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
putoctal(char *dst, unsigned num, int n)
|
|
{
|
|
snprintf(dst, n, "%.*o", n-1, num);
|
|
}
|
|
|
|
static int
|
|
archive(const char* path)
|
|
{
|
|
unsigned char b[Blksiz];
|
|
unsigned chksum;
|
|
int l, x;
|
|
Header *h = (void*)b;
|
|
FILE *f = NULL;
|
|
struct stat st;
|
|
struct passwd *pw;
|
|
struct group *gr;
|
|
mode_t mode;
|
|
|
|
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);
|
|
|
|
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])
|
|
{
|
|
char lname[101];
|
|
FILE *f = NULL;
|
|
unsigned long mode, major, minor, type, mtime;
|
|
struct timeval times[2];
|
|
Header *h = (void*)b;
|
|
|
|
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)
|
|
{
|
|
archive(path);
|
|
recurse(path, c);
|
|
}
|
|
|
|
static void
|
|
xt(int (*fn)(char*, int, char[Blksiz]))
|
|
{
|
|
char b[Blksiz], fname[257], *s;
|
|
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);
|
|
}
|
|
}
|