diff --git a/Makefile b/Makefile index f825ec4..38f4efa 100644 --- a/Makefile +++ b/Makefile @@ -68,6 +68,7 @@ SRC = \ sponge.c \ sync.c \ tail.c \ + tar.c \ tee.c \ test.c \ touch.c \ diff --git a/tar.1 b/tar.1 new file mode 100644 index 0000000..414ca4b --- /dev/null +++ b/tar.1 @@ -0,0 +1,27 @@ +.TH TAR 1 sbase\-VERSION +.SH NAME +tar \- create, list or extract a tape archive +.SH SYNOPSIS +.B tar +.BR c | t | x +.RI [ path ] +.SH DESCRIPTION +.B tar +is the standard file archiver. Generally the archices +created with it are further compressed. +.SH OPTIONS +.TP +.B x +extract tarball from stdin +.TP +.B t +list all files in tarball from stdin +.TP +.BI c\ path +creates tarball from +.I path +and prints it to stdout +.SH SEE ALSO +.IR ar (1) +.IR gzip (1) +.IR bzip2 (1) diff --git a/tar.c b/tar.c new file mode 100644 index 0000000..28caca8 --- /dev/null +++ b/tar.c @@ -0,0 +1,301 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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]; +}; + +enum { + Blksiz = 512 +}; + +enum Type { + REG = '0', HARDLINK = '1', SYMLINK = '2', CHARDEV = '3', + BLOCKDEV = '4', DIRECTORY = '5', FIFO = '6' +}; + +static void putoctal(char *, unsigned, int); +static int strlcpy(char *, const char *, int n); +static int archive(const char *, const struct stat *, int); +static int unarchive(char *, int, char[Blksiz]); +static int print(char *, int , char[Blksiz]); +static void c(char *); +static void xt(int (*)(char*, int, char[Blksiz])); + +static FILE *tarfile; + +static void +usage(void) +{ + eprintf("usage: tar [-f tarfile] [-C dir] [-]x|t\n" + " tar [-f tarfile] [-C dir] [-]c dir\n" + " tar [-C dir] cf tarfile dir\n" + " tar [-C dir] x|tf tarfile\n"); +} + +int +main(int argc, char *argv[]) +{ + char *file, *dir, *ap; + int flg['y'] = {0}; + + ARGBEGIN { + case 'x': + case 'c': + case 't': + flg[(unsigned)ARGC()] = 1; + break; + case 'C': + dir = EARGF(usage()); + break; + case 'f': + file = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + + if(flg['x'] + flg['c'] + flg['t'] > 1) + usage(); + if(flg['x'] + flg['c'] + flg['t']) + goto Action; + + if(argc < 1) + usage(); + + for(ap = argv[0]; *ap; ap++) { + switch(*ap){ + case 'x': + case 'c': + case 't': + flg[(unsigned)*ap] = 1; + 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; + default: + usage(); + } + } + argc--, argv++; + if(flg['x']+ flg['c']+ flg['t'] != 1) + usage(); + +Action: + if(file) { + tarfile = fopen(file, flg['c'] ? "wb" : "rb"); + if(!tarfile) + eprintf("tar: open '%s':", file); + } else { + tarfile = flg['c'] ? stdout : stdin; + } + + if(dir) + chdir(dir); + + if(flg['c']){ + if(argc != 1) + usage(); + c(argv[0]); + } else if (flg['x'] || flg['t']){ + if(argc != 0) + usage(); + xt(flg['x'] ? unarchive : print); + } + + return 0; +} + +void +putoctal(char *dst, unsigned num, int n) +{ + snprintf(dst, n, "%.*o", n-1, num); +} + +int +strlcpy(char *dst, const char *src, int n) +{ + return snprintf(dst, n, "%s", src); +} + +int +archive(const char* path, const struct stat* sta, int type) +{ + 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); + pw = getpwuid(st.st_uid); + gr = getgrgid(st.st_gid); + + memset(b, 0, sizeof b); + strlcpy (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); + strlcpy(h->uname, pw->pw_name, sizeof h->uname); + strlcpy(h->gname, gr->gr_name, sizeof h->gname); + + 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; + 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(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; +} + +int +unarchive(char *fname, int l, char b[Blksiz]) +{ + char lname[101]; + FILE *f = NULL; + unsigned long mode, major, minor, type; + Header *h = (void*)b; + + unlink(fname); + switch(h->type) { + case REG: + mode = strtoul(h->mode, 0, 8); + if(!(f = fopen(fname, "w")) || chmod(fname, mode)) + perror(fname); + break; + case HARDLINK: + case SYMLINK: + strlcpy(lname, h->link, sizeof lname); + 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: + 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); + 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); + return 0; +} + +int +print(char * fname, int l, char b[Blksiz]) +{ + puts(fname); + for(; l > 0; l -= Blksiz) + fread(b, Blksiz, 1, tarfile); + return 0; +} + +void +c(char * dir) +{ + ftw(dir, archive, FOPEN_MAX); +} + +void +xt(int (*fn)(char*, int, char[Blksiz])) +{ + char b[Blksiz], fname[101]; + Header *h = (void*)b; + + while(fread(b, Blksiz, 1, tarfile) && h->name[0] != '\0') { + strlcpy(fname, h->name, sizeof fname); + fn(fname, strtol(h->size, 0, 8), b); + } +}