mirror of git://git.suckless.org/sbase
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);
|
|
}
|
|
}
|