plan9fox/sys/src/cmd/tar.c
cinap_lenrek 37827f533b tar: fix memory corruption in extract1 (thanks petter)
extract1() expects two extra bytes to be avilabe before
fname buffer so it can prepend ./ before the name. this
used to be the case with name(), but was violated when
long name support was added and getname() was used in
place of name() which did not reserve the 2 extra bytes.

this change reserves two extra bytes in the getname()'s
static buffer and also removes the extra copy as name()
already makes a copy.
2019-11-02 14:17:34 +01:00

1375 lines
28 KiB
C

/*
* tar - `tape archiver', actually usable on any medium.
* POSIX "ustar" compliant when extracting, and by default when creating.
* this tar attempts to read and write multiple Tblock-byte blocks
* at once to and from the filesystem, and does not copy blocks
* around internally.
*/
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <fcall.h> /* for %M */
#include <String.h>
/*
* modified versions of those in libc.h; scans only the first arg for
* keyletters and options.
*/
#define TARGBEGIN {\
(argv0 || (argv0 = *argv)), argv++, argc--;\
if (argv[0]) {\
char *_args, *_argt;\
Rune _argc;\
_args = &argv[0][0];\
_argc = 0;\
while(*_args && (_args += chartorune(&_argc, _args)))\
switch(_argc)
#define TARGEND SET(_argt); USED(_argt);USED(_argc);USED(_args); \
argc--, argv++; } \
USED(argv); USED(argc); }
#define TARGC() (_argc)
#define ROUNDUP(a, b) (((a) + (b) - 1)/(b))
#define BYTES2TBLKS(bytes) ROUNDUP(bytes, Tblock)
/* read big-endian binary integers; args must be (uchar *) */
#define G2BEBYTE(x) (((x)[0]<<8) | (x)[1])
#define G3BEBYTE(x) (((x)[0]<<16) | ((x)[1]<<8) | (x)[2])
#define G4BEBYTE(x) (((x)[0]<<24) | ((x)[1]<<16) | ((x)[2]<<8) | (x)[3])
#define G8BEBYTE(x) (((vlong)G4BEBYTE(x)<<32) | (u32int)G4BEBYTE((x)+4))
typedef vlong Off;
typedef char *(*Refill)(int ar, char *bufs, int justhdr);
enum { Stdin, Stdout, Stderr };
enum { Rd, Wr }; /* pipe fd-array indices */
enum { Output, Input };
enum { None, Toc, Xtract, Replace };
enum { Alldata, Justnxthdr };
enum {
Tblock = 512,
Namsiz = 100,
Maxpfx = 155, /* from POSIX */
Maxname = Namsiz + 1 + Maxpfx,
Maxlongname = 65535,
Binsize = 0x80, /* flag in size[0], from gnu: positive binary size */
Binnegsz = 0xff, /* flag in size[0]: negative binary size */
Nblock = 40, /* maximum blocksize */
Dblock = 20, /* default blocksize */
Debug = 0,
};
/* POSIX link flags */
enum {
LF_PLAIN1 = '\0',
LF_PLAIN2 = '0',
LF_LINK = '1',
LF_SYMLINK1 = '2',
LF_SYMLINK2 = 's', /* 4BSD used this */
LF_CHR = '3',
LF_BLK = '4',
LF_DIR = '5',
LF_FIFO = '6',
LF_CONTIG = '7',
/* 'A' - 'Z' are reserved for custom implementations */
LF_LONGNAME = 'L', /* GNU extenstion */
LF_LONGLINK = 'K',
};
#define islink(lf) (isreallink(lf) || issymlink(lf))
#define isreallink(lf) ((lf) == LF_LINK)
#define issymlink(lf) ((lf) == LF_SYMLINK1 || (lf) == LF_SYMLINK2)
typedef union {
uchar data[Tblock];
struct {
char name[Namsiz];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char chksum[8];
char linkflag;
char linkname[Namsiz];
/* rest are defined by POSIX's ustar format; see p1003.2b */
char magic[6]; /* "ustar" */
char version[2];
char uname[32];
char gname[32];
char devmajor[8];
char devminor[8];
char prefix[Maxpfx]; /* if non-null, path= prefix "/" name */
};
} Hdr;
typedef struct {
char *comp;
char *decomp;
char *sfx[4];
} Compress;
static Compress comps[] = {
"gzip", "gunzip", { ".tar.gz", ".tgz" }, /* default */
"compress", "uncompress", { ".tar.Z", ".tz" },
"bzip2", "bunzip2", { ".tar.bz", ".tbz",
".tar.bz2",".tbz2" },
};
typedef struct {
int kid;
int fd; /* original fd */
int rfd; /* replacement fd */
int input;
int open;
} Pushstate;
#define OTHER(rdwr) ((rdwr) == Rd? Wr: Rd)
static int fixednblock;
static int verb;
static int posix = 1;
static int docreate;
static int aruid;
static int argid;
static int relative = 1;
static int settime;
static int verbose;
static int docompress;
static int keepexisting;
static int ignerrs; /* flag: ignore i/o errors if possible */
static Off blkoff; /* offset of the current archive block (not Tblock) */
static Off nexthdr;
static int nblock = Dblock;
static int resync;
static char *usefile, *arname = "archive";
static char origdir[Maxlongname+1];
static Hdr *tpblk, *endblk;
static Hdr *curblk;
static void
usage(void)
{
fprint(2, "usage: %s {crtx}[PRTfgikmpsuvz] [archive] [file1 file2...]\n",
argv0);
exits("usage");
}
/* I/O, with error retry or exit */
static int
cope(char *name, int fd, void *buf, long len, Off off)
{
fprint(2, "%s: %serror reading %s: %r\n", argv0,
(ignerrs? "ignoring ": ""), name);
if (!ignerrs)
exits("read error");
/* pretend we read len bytes of zeroes */
memset(buf, 0, len);
if (off >= 0) /* seekable? */
seek(fd, off + len, 0);
return len;
}
static int
eread(char *name, int fd, void *buf, long len)
{
int rd;
Off off;
off = seek(fd, 0, 1); /* for coping with errors */
rd = read(fd, buf, len);
if (rd < 0)
rd = cope(name, fd, buf, len, off);
return rd;
}
static int
ereadn(char *name, int fd, void *buf, long len)
{
int rd;
Off off;
off = seek(fd, 0, 1);
rd = readn(fd, buf, len);
if (rd < 0)
rd = cope(name, fd, buf, len, off);
return rd;
}
static int
ewrite(char *name, int fd, void *buf, long len)
{
int rd;
werrstr("");
rd = write(fd, buf, len);
if (rd != len)
sysfatal("error writing %s: %r", name);
return rd;
}
/* compression */
static Compress *
compmethod(char *name)
{
if (name) {
int i, nmlen, sfxlen;
Compress *cp;
nmlen = strlen(name);
for (cp = comps; cp < comps + nelem(comps); cp++) {
for (i = 0; i < nelem(cp->sfx) && cp->sfx[i]; i++) {
sfxlen = strlen(cp->sfx[i]);
if (nmlen > sfxlen &&
strcmp(cp->sfx[i], name + nmlen - sfxlen) == 0)
return cp;
}
}
}
return docompress? comps: nil;
}
/*
* push a filter, cmd, onto fd. if input, it's an input descriptor.
* returns a descriptor to replace fd, or -1 on error.
*/
static int
push(int fd, char *cmd, int input, Pushstate *ps)
{
int nfd, pifds[2];
String *s;
ps->open = 0;
ps->fd = fd;
ps->input = input;
if (fd < 0 || pipe(pifds) < 0)
return -1;
ps->kid = fork();
switch (ps->kid) {
case -1:
return -1;
case 0:
if (input)
dup(pifds[Wr], Stdout);
else
dup(pifds[Rd], Stdin);
close(pifds[input? Rd: Wr]);
dup(fd, (input? Stdin: Stdout));
s = s_new();
if (cmd[0] != '/')
s_append(s, "/bin/");
s_append(s, cmd);
execl(s_to_c(s), cmd, nil);
sysfatal("can't exec %s: %r", cmd);
default:
nfd = pifds[input? Rd: Wr];
close(pifds[input? Wr: Rd]);
break;
}
ps->rfd = nfd;
ps->open = 1;
return nfd;
}
static char *
pushclose(Pushstate *ps)
{
Waitmsg *wm;
if (ps->fd < 0 || ps->rfd < 0 || !ps->open)
return "not open";
close(ps->rfd);
ps->rfd = -1;
ps->open = 0;
while ((wm = wait()) != nil && wm->pid != ps->kid)
continue;
return wm? wm->msg: nil;
}
/*
* block-buffer management
*/
static void
initblks(void)
{
free(tpblk);
tpblk = malloc(Tblock * nblock);
assert(tpblk != nil);
endblk = tpblk + nblock;
}
/*
* (re)fill block buffers from archive. `justhdr' means we don't care
* about the data before the next header block.
*/
static char *
refill(int ar, char *bufs, int justhdr)
{
int i, n;
unsigned bytes = Tblock * nblock;
static int done, first = 1, seekable;
if (done)
return nil;
blkoff = seek(ar, 0, 1); /* note position for `tar r' */
if (first)
seekable = blkoff >= 0;
/* try to size non-pipe input at first read */
if (first && usefile && !fixednblock) {
n = eread(arname, ar, bufs, bytes);
if (n == 0)
sysfatal("EOF reading archive %s: %r", arname);
i = n;
if (i % Tblock != 0)
sysfatal("%s: archive block size (%d) error", arname, i);
i /= Tblock;
if (i != nblock) {
nblock = i;
fprint(2, "%s: blocking = %d\n", argv0, nblock);
endblk = (Hdr *)bufs + nblock;
bytes = n;
}
} else if (justhdr && seekable && nexthdr - blkoff >= bytes) {
/* optimisation for huge archive members on seekable media */
if (seek(ar, bytes, 1) < 0)
sysfatal("can't seek on archive %s: %r", arname);
n = bytes;
} else
n = ereadn(arname, ar, bufs, bytes);
first = 0;
if (n == 0)
sysfatal("unexpected EOF reading archive %s", arname);
if (n % Tblock != 0)
sysfatal("partial block read from archive %s", arname);
if (n != bytes) {
done = 1;
memset(bufs + n, 0, bytes - n);
}
return bufs;
}
static Hdr *
getblk(int ar, Refill rfp, int justhdr)
{
if (curblk == nil || curblk >= endblk) { /* input block exhausted? */
if (rfp != nil && (*rfp)(ar, (char *)tpblk, justhdr) == nil)
return nil;
curblk = tpblk;
}
return curblk++;
}
static Hdr *
getblkrd(int ar, int justhdr)
{
return getblk(ar, refill, justhdr);
}
static Hdr *
getblke(int ar)
{
return getblk(ar, nil, Alldata);
}
static Hdr *
getblkz(int ar)
{
Hdr *hp = getblke(ar);
if (hp != nil)
memset(hp->data, 0, Tblock);
return hp;
}
/*
* how many block buffers are available, starting at the address
* just returned by getblk*?
*/
static int
gothowmany(int max)
{
int n = endblk - (curblk - 1);
return n > max? max: n;
}
/*
* indicate that one is done with the last block obtained from getblke
* and it is now available to be written into the archive.
*/
static void
putlastblk(int ar)
{
unsigned bytes = Tblock * nblock;
/* if writing end-of-archive, aid compression (good hygiene too) */
if (curblk < endblk)
memset(curblk, 0, (char *)endblk - (char *)curblk);
ewrite(arname, ar, tpblk, bytes);
}
static void
putblk(int ar)
{
if (curblk >= endblk)
putlastblk(ar);
}
static void
putbackblk(int ar)
{
curblk--;
USED(ar);
}
static void
putreadblks(int ar, int blks)
{
curblk += blks - 1;
USED(ar);
}
static void
putblkmany(int ar, int blks)
{
assert(blks > 0);
curblk += blks - 1;
putblk(ar);
}
/*
* common routines
*/
/*
* modifies hp->chksum but restores it; important for the last block of the
* old archive when updating with `tar rf archive'
*/
static long
chksum(Hdr *hp)
{
int n = Tblock;
long i = 0;
uchar *cp = hp->data;
char oldsum[sizeof hp->chksum];
memmove(oldsum, hp->chksum, sizeof oldsum);
memset(hp->chksum, ' ', sizeof hp->chksum);
while (n-- > 0)
i += *cp++;
memmove(hp->chksum, oldsum, sizeof oldsum);
return i;
}
static int
isustar(Hdr *hp)
{
return strcmp(hp->magic, "ustar") == 0;
}
/*
* s is at most n bytes long, but need not be NUL-terminated.
* if shorter than n bytes, all bytes after the first NUL must also
* be NUL.
*/
static int
strnlen(char *s, int n)
{
return s[n - 1] != '\0'? n: strlen(s);
}
/* set fullname from header */
static char *
name(Hdr *hp)
{
int pfxlen, namlen;
char *fullname;
static char fullnamebuf[2+Maxname+1]; /* 2+ for ./ on relative names */
fullname = fullnamebuf+2;
namlen = strnlen(hp->name, sizeof hp->name);
if (hp->prefix[0] == '\0' || !isustar(hp)) { /* old-style name? */
memmove(fullname, hp->name, namlen);
fullname[namlen] = '\0';
return fullname;
}
/* name is in two pieces */
pfxlen = strnlen(hp->prefix, sizeof hp->prefix);
memmove(fullname, hp->prefix, pfxlen);
fullname[pfxlen] = '/';
memmove(fullname + pfxlen + 1, hp->name, namlen);
fullname[pfxlen + 1 + namlen] = '\0';
return fullname;
}
static int
isdir(Hdr *hp)
{
/* the mode test is ugly but sometimes necessary */
return hp->linkflag == LF_DIR ||
strrchr(name(hp), '\0')[-1] == '/' ||
(strtoul(hp->mode, nil, 8)&0170000) == 040000;
}
static int
eotar(Hdr *hp)
{
return name(hp)[0] == '\0';
}
/*
static uvlong
getbe(uchar *src, int size)
{
uvlong vl = 0;
while (size-- > 0) {
vl <<= 8;
vl |= *src++;
}
return vl;
}
*/
static void
putbe(uchar *dest, uvlong vl, int size)
{
for (dest += size; size-- > 0; vl >>= 8)
*--dest = vl;
}
/*
* cautious parsing of octal numbers as ascii strings in
* a tar header block. this is particularly important for
* trusting the checksum when trying to resync.
*/
static uvlong
hdrotoull(char *st, char *end, uvlong errval, char *name, char *field)
{
char *numb;
for (numb = st; (*numb == ' ' || *numb == '\0') && numb < end; numb++)
;
if (numb < end && isascii(*numb) && isdigit(*numb))
return strtoull(numb, nil, 8);
else if (numb >= end)
fprint(2, "%s: %s: empty %s in header\n", argv0, name, field);
else
fprint(2, "%s: %s: %s: non-numeric %s in header\n",
argv0, name, numb, field);
return errval;
}
/*
* return the nominal size from the header block, which is not always the
* size in the archive (the archive size may be zero for some file types
* regardless of the nominal size).
*
* gnu and freebsd tars are now recording vlongs as big-endian binary
* with a flag in byte 0 to indicate this, which permits file sizes up to
* 2^64-1 (actually 2^80-1 but our file sizes are vlongs) rather than 2^33-1.
*/
static Off
hdrsize(Hdr *hp)
{
uchar *p;
if((uchar)hp->size[0] == Binnegsz) {
fprint(2, "%s: %s: negative length, which is insane\n",
argv0, name(hp));
return 0;
} else if((uchar)hp->size[0] == Binsize) {
p = (uchar *)hp->size + sizeof hp->size - 1 -
sizeof(vlong); /* -1 for terminating space */
return G8BEBYTE(p);
}
return hdrotoull(hp->size, hp->size + sizeof hp->size, 0,
name(hp), "size");
}
/*
* return the number of bytes recorded in the archive.
*/
static Off
arsize(Hdr *hp)
{
if(isdir(hp) || islink(hp->linkflag))
return 0;
return hdrsize(hp);
}
static long
parsecksum(char *cksum, char *name)
{
Hdr *hp;
return hdrotoull(cksum, cksum + sizeof hp->chksum, (uvlong)-1LL,
name, "checksum");
}
static Hdr *
readhdr(int ar)
{
long hdrcksum;
Hdr *hp;
hp = getblkrd(ar, Alldata);
if (hp == nil)
sysfatal("unexpected EOF instead of archive header in %s",
arname);
if (eotar(hp)) /* end-of-archive block? */
return nil;
hdrcksum = parsecksum(hp->chksum, name(hp));
if (hdrcksum == -1 || chksum(hp) != hdrcksum) {
if (!resync)
sysfatal("bad archive header checksum in %s: "
"name %.100s...; expected %#luo got %#luo",
arname, hp->name, hdrcksum, chksum(hp));
fprint(2, "%s: skipping past archive header with bad checksum in %s...",
argv0, arname);
do {
hp = getblkrd(ar, Alldata);
if (hp == nil)
sysfatal("unexpected EOF looking for archive header in %s",
arname);
hdrcksum = parsecksum(hp->chksum, name(hp));
} while (hdrcksum == -1 || chksum(hp) != hdrcksum);
fprint(2, "found %s\n", name(hp));
}
nexthdr += Tblock*(1 + BYTES2TBLKS(arsize(hp)));
return hp;
}
/*
* tar r[c]
*/
/*
* if name is longer than Namsiz bytes, try to split it at a slash and fit the
* pieces into hp->prefix and hp->name.
*/
static int
putfullname(Hdr *hp, char *name)
{
int namlen, pfxlen;
char *sl, *osl;
String *slname = nil;
if (isdir(hp)) {
slname = s_new();
s_append(slname, name);
s_append(slname, "/"); /* posix requires this */
name = s_to_c(slname);
}
namlen = strlen(name);
if (namlen <= Namsiz) {
strncpy(hp->name, name, Namsiz);
hp->prefix[0] = '\0'; /* ustar paranoia */
return 0;
}
if (!posix || namlen > Maxname) {
fprint(2, "%s: name too long for tar header: %s\n",
argv0, name);
return -1;
}
/*
* try various splits until one results in pieces that fit into the
* appropriate fields of the header. look for slashes from right
* to left, in the hopes of putting the largest part of the name into
* hp->prefix, which is larger than hp->name.
*/
sl = strrchr(name, '/');
while (sl != nil) {
pfxlen = sl - name;
if (pfxlen <= sizeof hp->prefix && namlen-1 - pfxlen <= Namsiz)
break;
osl = sl;
*osl = '\0';
sl = strrchr(name, '/');
*osl = '/';
}
if (sl == nil) {
fprint(2, "%s: name can't be split to fit tar header: %s\n",
argv0, name);
return -1;
}
*sl = '\0';
strncpy(hp->prefix, name, sizeof hp->prefix);
*sl++ = '/';
strncpy(hp->name, sl, sizeof hp->name);
if (slname)
s_free(slname);
return 0;
}
static int
mkhdr(Hdr *hp, Dir *dir, char *file)
{
int r;
/*
* some of these fields run together, so we format them left-to-right
* and don't use snprint.
*/
sprint(hp->mode, "%6lo ", dir->mode & 0777);
sprint(hp->uid, "%6o ", aruid);
sprint(hp->gid, "%6o ", argid);
if (dir->length >= (Off)1<<32) {
static int printed;
if (!printed) {
printed = 1;
fprint(2, "%s: storing large sizes in \"base 256\"\n", argv0);
}
hp->size[0] = Binsize;
/* emit so-called `base 256' representation of size */
putbe((uchar *)hp->size+1, dir->length, sizeof hp->size - 2);
hp->size[sizeof hp->size - 1] = ' ';
} else
sprint(hp->size, "%11lluo ", dir->length);
sprint(hp->mtime, "%11luo ", dir->mtime);
hp->linkflag = (dir->mode&DMDIR? LF_DIR: LF_PLAIN1);
r = putfullname(hp, file);
if (posix) {
strncpy(hp->magic, "ustar", sizeof hp->magic);
strncpy(hp->version, "00", sizeof hp->version);
strncpy(hp->uname, dir->uid, sizeof hp->uname);
strncpy(hp->gname, dir->gid, sizeof hp->gname);
}
sprint(hp->chksum, "%6luo", chksum(hp));
return r;
}
static void addtoar(int ar, char *file, char *shortf);
static void
addtreetoar(int ar, char *file, char *shortf, int fd)
{
int n;
Dir *dent, *dirents;
String *name = s_new();
n = dirreadall(fd, &dirents);
if (n < 0)
fprint(2, "%s: dirreadall %s: %r\n", argv0, file);
close(fd);
if (n <= 0)
return;
if (chdir(shortf) < 0)
sysfatal("chdir %s: %r", file);
if (Debug)
fprint(2, "chdir %s\t# %s\n", shortf, file);
for (dent = dirents; dent < dirents + n; dent++) {
s_reset(name);
s_append(name, file);
s_append(name, "/");
s_append(name, dent->name);
addtoar(ar, s_to_c(name), dent->name);
}
s_free(name);
free(dirents);
/*
* this assumes that shortf is just one component, which is true
* during directory descent, but not necessarily true of command-line
* arguments. Our caller (or addtoar's) must reset the working
* directory if necessary.
*/
if (chdir("..") < 0)
sysfatal("chdir %s/..: %r", file);
if (Debug)
fprint(2, "chdir ..\n");
}
static void
addtoar(int ar, char *file, char *shortf)
{
int n, fd, isdir;
long bytes, blksread;
ulong blksleft;
Hdr *hbp;
Dir *dir;
String *name = nil;
if (shortf[0] == '#') {
name = s_new();
s_append(name, "./");
s_append(name, shortf);
shortf = s_to_c(name);
}
if (Debug)
fprint(2, "opening %s # %s\n", shortf, file);
fd = open(shortf, OREAD);
if (fd < 0) {
fprint(2, "%s: can't open %s: %r\n", argv0, file);
if (name)
s_free(name);
return;
}
dir = dirfstat(fd);
if (dir == nil)
sysfatal("can't fstat %s: %r", file);
hbp = getblkz(ar);
isdir = (dir->qid.type & QTDIR) != 0;
if (mkhdr(hbp, dir, file) < 0) {
putbackblk(ar);
free(dir);
close(fd);
if (name)
s_free(name);
return;
}
putblk(ar);
blksleft = BYTES2TBLKS(dir->length);
free(dir);
if (isdir)
addtreetoar(ar, file, shortf, fd);
else {
for (; blksleft > 0; blksleft -= blksread) {
hbp = getblke(ar);
blksread = gothowmany(blksleft);
assert(blksread >= 0);
bytes = blksread * Tblock;
n = ereadn(file, fd, hbp->data, bytes);
assert(n >= 0);
/*
* ignore EOF. zero any partial block to aid
* compression and emergency recovery of data.
*/
if (n < Tblock)
memset(hbp->data + n, 0, bytes - n);
putblkmany(ar, blksread);
}
close(fd);
if (verbose)
fprint(2, "%s\n", file);
}
if (name)
s_free(name);
}
static char *
replace(char **argv)
{
int i, ar;
ulong blksleft, blksread;
Off bytes;
Hdr *hp;
Compress *comp = nil;
Pushstate ps;
if (usefile && docreate)
ar = create(usefile, OWRITE, 0666);
else if (usefile)
ar = open(usefile, ORDWR);
else
ar = Stdout;
if (docreate && docompress) {
comp = compmethod(usefile);
if (comp)
ar = push(ar, comp->comp, Output, &ps);
}
if (ar < 0)
sysfatal("can't open archive %s: %r", usefile);
if (usefile && !docreate) {
/* skip quickly to the end */
while ((hp = readhdr(ar)) != nil) {
bytes = arsize(hp);
for (blksleft = BYTES2TBLKS(bytes);
blksleft > 0 && getblkrd(ar, Justnxthdr) != nil;
blksleft -= blksread) {
blksread = gothowmany(blksleft);
putreadblks(ar, blksread);
}
}
/*
* we have just read the end-of-archive Tblock.
* now seek back over the (big) archive block containing it,
* and back up curblk ptr over end-of-archive Tblock in memory.
*/
if (seek(ar, blkoff, 0) < 0)
sysfatal("can't seek back over end-of-archive in %s: %r",
arname);
curblk--;
}
for (i = 0; argv[i] != nil; i++) {
addtoar(ar, argv[i], argv[i]);
chdir(origdir); /* for correctness & profiling */
}
/* write end-of-archive marker */
getblkz(ar);
putblk(ar);
getblkz(ar);
putlastblk(ar);
if (comp)
return pushclose(&ps);
if (ar > Stderr)
close(ar);
return nil;
}
/*
* tar [xt]
*/
/* is pfx a file-name prefix of name? */
static int
prefix(char *name, char *pfx)
{
char clpfx[Maxlongname+1];
int pfxlen = strlen(pfx);
clpfx[Maxlongname] = '\0';
strncpy(clpfx, pfx, Maxlongname);
cleanname(clpfx);
return strncmp(clpfx, name, pfxlen) == 0 &&
(name[pfxlen] == '\0' || name[pfxlen] == '/');
}
static int
match(char *name, char **argv)
{
char clname[Maxlongname+1];
int i;
if (argv[0] == nil)
return 1;
clname[Maxlongname] = '\0';
strncpy(clname, name, Maxlongname);
cleanname(clname);
for (i = 0; argv[i] != nil; i++)
if (prefix(clname, argv[i]))
return 1;
return 0;
}
static void
cantcreate(char *s, int mode)
{
int len;
static char *last;
/*
* Always print about files. Only print about directories
* we haven't printed about. (Assumes archive is ordered
* nicely.)
*/
if(mode&DMDIR){
if(last){
/* already printed this directory */
if(strcmp(s, last) == 0)
return;
/* printed a higher directory, so printed this one */
len = strlen(s);
if(memcmp(s, last, len) == 0 && last[len] == '/')
return;
}
/* save */
free(last);
last = strdup(s);
}
fprint(2, "%s: can't create %s: %r\n", argv0, s);
}
static int
makedir(char *s)
{
int f;
if (access(s, AEXIST) == 0)
return -1;
f = create(s, OREAD, DMDIR | 0777);
if (f >= 0)
close(f);
else
cantcreate(s, DMDIR);
return f;
}
static int
mkpdirs(char *s)
{
int err;
char *p;
p = s;
err = 0;
while (!err && (p = strchr(p+1, '/')) != nil) {
*p = '\0';
err = (access(s, AEXIST) < 0 && makedir(s) < 0);
*p = '/';
}
return -err;
}
/* Call access but preserve the error string. */
static int
xaccess(char *name, int mode)
{
char err[ERRMAX];
int rv;
err[0] = 0;
errstr(err, sizeof err);
rv = access(name, mode);
errstr(err, sizeof err);
return rv;
}
static int
openfname(Hdr *hp, char *fname, int dir, int mode)
{
int fd;
fd = -1;
cleanname(fname);
switch (hp->linkflag) {
case LF_LINK:
case LF_SYMLINK1:
case LF_SYMLINK2:
case LF_LONGLINK:
fprint(2, "%s: can't make (sym)link %s\n",
argv0, fname);
break;
case LF_FIFO:
fprint(2, "%s: can't make fifo %s\n", argv0, fname);
break;
default:
if (!keepexisting || access(fname, AEXIST) < 0) {
int rw = (dir? OREAD: OWRITE);
fd = create(fname, rw, mode);
if (fd < 0) {
mkpdirs(fname);
fd = create(fname, rw, mode);
}
if (fd < 0 && (!dir || xaccess(fname, AEXIST) < 0))
cantcreate(fname, mode);
}
if (fd >= 0 && verbose)
fprint(2, "%s\n", fname);
break;
}
return fd;
}
/* copy from archive to file system (or nowhere for table-of-contents) */
static void
copyfromar(int ar, int fd, char *fname, ulong blksleft, Off bytes)
{
int wrbytes;
ulong blksread;
Hdr *hbp;
if (blksleft == 0 || bytes < 0)
bytes = 0;
for (; blksleft > 0; blksleft -= blksread) {
hbp = getblkrd(ar, (fd >= 0? Alldata: Justnxthdr));
if (hbp == nil)
sysfatal("unexpected EOF on archive extracting %s from %s",
fname, arname);
blksread = gothowmany(blksleft);
if (blksread <= 0) {
fprint(2, "%s: got %ld blocks reading %s!\n",
argv0, blksread, fname);
blksread = 0;
}
wrbytes = Tblock*blksread;
assert(bytes >= 0);
if(wrbytes > bytes)
wrbytes = bytes;
assert(wrbytes >= 0);
if (fd >= 0)
ewrite(fname, fd, hbp->data, wrbytes);
putreadblks(ar, blksread);
bytes -= wrbytes;
assert(bytes >= 0);
}
if (bytes > 0)
fprint(2, "%s: %lld bytes uncopied at EOF on archive %s; "
"%s not fully extracted\n", argv0, bytes, arname, fname);
}
static void
wrmeta(int fd, Hdr *hp, long mtime, int mode) /* update metadata */
{
Dir nd;
nulldir(&nd);
nd.mtime = mtime;
nd.mode = mode;
dirfwstat(fd, &nd);
if (isustar(hp)) {
nulldir(&nd);
nd.gid = hp->gname;
dirfwstat(fd, &nd);
nulldir(&nd);
nd.uid = hp->uname;
dirfwstat(fd, &nd);
}
}
/*
* copy a file from the archive into the filesystem.
* fname is result of getname(), so has two extra bytes at beginning.
*/
static void
extract1(int ar, Hdr *hp, char *fname)
{
int fd = -1, dir = 0;
long mtime = strtol(hp->mtime, nil, 8);
ulong mode = strtoul(hp->mode, nil, 8) & 0777;
Off bytes = hdrsize(hp); /* for printing */
ulong blksleft = BYTES2TBLKS(arsize(hp));
/* fiddle name, figure out mode and blocks */
if (isdir(hp)) {
mode |= DMDIR|0700;
dir = 1;
}
switch (hp->linkflag) {
case LF_LINK:
case LF_SYMLINK1:
case LF_SYMLINK2:
case LF_FIFO:
blksleft = 0;
break;
}
if (relative)
if(fname[0] == '/')
*--fname = '.';
else if(fname[0] == '#'){
*--fname = '/';
*--fname = '.';
}
if (verb == Xtract)
fd = openfname(hp, fname, dir, mode);
else if (verbose) {
char *cp = ctime(mtime);
print("%M %8lld %-12.12s %-4.4s %s\n",
mode, bytes, cp+4, cp+24, fname);
} else
print("%s\n", fname);
copyfromar(ar, fd, fname, blksleft, bytes);
/* touch up meta data and close */
if (fd >= 0) {
/*
* directories should be wstated *after* we're done
* creating files in them, but we don't do that.
*/
if (settime)
wrmeta(fd, hp, mtime, mode);
close(fd);
}
}
static void
skip(int ar, Hdr *hp, char *fname)
{
ulong blksleft, blksread;
Hdr *hbp;
for (blksleft = BYTES2TBLKS(arsize(hp)); blksleft > 0;
blksleft -= blksread) {
hbp = getblkrd(ar, Justnxthdr);
if (hbp == nil)
sysfatal("unexpected EOF on archive extracting %s from %s",
fname, arname);
blksread = gothowmany(blksleft);
putreadblks(ar, blksread);
}
}
static char*
getname(int ar, Hdr *hp)
{
static char buf[2+Maxlongname+1], *namebuf = buf+2, *nextname = nil;
ulong blksleft, blksread;
char *fname, *p;
int n;
if(nextname != nil && nextname[0] != '\0'){
fname = nextname, nextname = nil;
return fname;
}
fname = name(hp);
if(hp->linkflag == LF_LONGNAME){
p = namebuf;
for (blksleft = BYTES2TBLKS(arsize(hp)); blksleft > 0;
blksleft -= blksread) {
hp = getblkrd(ar, Alldata);
if (hp == nil)
sysfatal("unexpected EOF on archive reading %s from %s",
fname, arname);
blksread = gothowmany(blksleft);
n = &namebuf[Maxlongname] - p;
if(Tblock*blksread < n)
n = Tblock*blksread;
memmove(p, hp->data, n);
p += n;
putreadblks(ar, blksread);
}
*p = '\0';
fname = nil;
nextname = namebuf;
}
return fname;
}
static char *
extract(char **argv)
{
int ar;
char *longname;
Hdr *hp;
Compress *comp;
Pushstate ps;
if (usefile)
ar = open(usefile, OREAD);
else
ar = Stdin;
comp = compmethod(usefile);
if (comp)
ar = push(ar, comp->decomp, Input, &ps);
if (ar < 0)
sysfatal("can't open archive %s: %r", usefile);
while ((hp = readhdr(ar)) != nil) {
longname = getname(ar, hp);
if(longname == nil)
continue;
if (match(longname, argv))
extract1(ar, hp, longname);
else
skip(ar, hp, longname);
}
if (comp)
return pushclose(&ps);
if (ar > Stderr)
close(ar);
return nil;
}
void
main(int argc, char *argv[])
{
int errflg = 0;
char *ret = nil;
fmtinstall('M', dirmodefmt);
TARGBEGIN {
case 'c':
docreate++;
verb = Replace;
break;
case 'f':
usefile = arname = EARGF(usage());
break;
case 'g':
argid = strtoul(EARGF(usage()), 0, 0);
break;
case 'i':
ignerrs = 1;
break;
case 'k':
keepexisting++;
break;
case 'm': /* compatibility */
settime = 0;
break;
case 'p':
posix++;
break;
case 'P':
posix = 0;
break;
case 'r':
verb = Replace;
break;
case 'R':
relative = 0;
break;
case 's':
resync++;
break;
case 't':
verb = Toc;
break;
case 'T':
settime++;
break;
case 'u':
aruid = strtoul(EARGF(usage()), 0, 0);
break;
case 'v':
verbose++;
break;
case 'x':
verb = Xtract;
break;
case 'z':
docompress++;
break;
case '-':
break;
default:
fprint(2, "tar: unknown letter %C\n", TARGC());
errflg++;
break;
} TARGEND
if (argc < 0 || errflg)
usage();
initblks();
switch (verb) {
case Toc:
case Xtract:
ret = extract(argv);
break;
case Replace:
if (getwd(origdir, sizeof origdir) == nil)
strcpy(origdir, "/tmp");
ret = replace(argv);
break;
default:
usage();
break;
}
exits(ret);
}