shithub: riscv

Download patch

ref: a68bee44d3a833442671329357169dc755060156
parent: 4dab28b14f62cc7f4b474d7e45941d6fbf26f5a7
author: Ori Bernstein <[email protected]>
date: Thu Nov 14 08:52:41 EST 2019

Add pax extended header support to tar.

Support for 'path=', 'uname=', 'gname=', 'size=', and 'atime=' pax
headers is useful.  Others are ignored, possibly with a warning.

We were running into missing support with the 'go' extraction.

At the same time, this cleans up the way that we handle paths,
getting rid of static buffers with hidden space at the front.

--- a/sys/src/cmd/tar.c
+++ b/sys/src/cmd/tar.c
@@ -78,6 +78,8 @@
 
 	LF_LONGNAME =	'L',		/* GNU extenstion */
 	LF_LONGLINK = 	'K',
+	LF_PAXHDR =	'x',		/* PAX header */
+	LF_PAXGLOBL =	'g',
 };
 
 #define islink(lf)	(isreallink(lf) || issymlink(lf))
@@ -106,6 +108,16 @@
 		char	devminor[8];
 		char	prefix[Maxpfx]; /* if non-null, path= prefix "/" name */
 	};
+} Blk;
+
+typedef struct {
+	char	*name;
+	char	*uid;
+	char	*gid;
+	ulong	mode;
+	vlong	size;
+	vlong	mtime;
+	vlong	atime;
 } Hdr;
 
 typedef struct {
@@ -150,8 +162,9 @@
 static int resync;
 static char *usefile, *arname = "archive";
 static char origdir[Maxlongname+1];
-static Hdr *tpblk, *endblk;
-static Hdr *curblk;
+static Blk *tpblk, *endblk;
+static Blk *curblk;
+static Hdr globlhdr;
 
 static void
 usage(void)
@@ -337,7 +350,7 @@
 		if (i != nblock) {
 			nblock = i;
 			fprint(2, "%s: blocking = %d\n", argv0, nblock);
-			endblk = (Hdr *)bufs + nblock;
+			endblk = (Blk *)bufs + nblock;
 			bytes = n;
 		}
 	} else if (justhdr && seekable && nexthdr - blkoff >= bytes) {
@@ -360,7 +373,7 @@
 	return bufs;
 }
 
-static Hdr *
+static Blk *
 getblk(int ar, Refill rfp, int justhdr)
 {
 	if (curblk == nil || curblk >= endblk) {  /* input block exhausted? */
@@ -371,26 +384,26 @@
 	return curblk++;
 }
 
-static Hdr *
+static Blk *
 getblkrd(int ar, int justhdr)
 {
 	return getblk(ar, refill, justhdr);
 }
 
-static Hdr *
+static Blk *
 getblke(int ar)
 {
 	return getblk(ar, nil, Alldata);
 }
 
-static Hdr *
+static Blk *
 getblkz(int ar)
 {
-	Hdr *hp = getblke(ar);
+	Blk *bp = getblke(ar);
 
-	if (hp != nil)
-		memset(hp->data, 0, Tblock);
-	return hp;
+	if (bp != nil)
+		memset(bp->data, 0, Tblock);
+	return bp;
 }
 
 /*
@@ -458,25 +471,25 @@
  * old archive when updating with `tar rf archive'
  */
 static long
-chksum(Hdr *hp)
+chksum(Blk *bp)
 {
 	int n = Tblock;
 	long i = 0;
-	uchar *cp = hp->data;
-	char oldsum[sizeof hp->chksum];
+	uchar *cp = bp->data;
+	char oldsum[sizeof bp->chksum];
 
-	memmove(oldsum, hp->chksum, sizeof oldsum);
-	memset(hp->chksum, ' ', sizeof hp->chksum);
+	memmove(oldsum, bp->chksum, sizeof oldsum);
+	memset(bp->chksum, ' ', sizeof bp->chksum);
 	while (n-- > 0)
 		i += *cp++;
-	memmove(hp->chksum, oldsum, sizeof oldsum);
+	memmove(bp->chksum, oldsum, sizeof oldsum);
 	return i;
 }
 
 static int
-isustar(Hdr *hp)
+isustar(Blk *bp)
 {
-	return strcmp(hp->magic, "ustar") == 0;
+	return strcmp(bp->magic, "ustar") == 0;
 }
 
 /*
@@ -492,42 +505,42 @@
 
 /* set fullname from header */
 static char *
-name(Hdr *hp)
+parsename(Blk *bp, char *buf, int nbuf)
 {
 	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;
+	namlen = strnlen(bp->name, sizeof bp->name);
+	if (bp->prefix[0] == '\0' || !isustar(bp)) {	/* old-style name? */
+		assert(nbuf > namlen);
+		memmove(buf, bp->name, namlen);
+		buf[namlen] = '\0';
+		return buf;
 	}
 
 	/* 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;
+	pfxlen = strnlen(bp->prefix, sizeof bp->prefix);
+	assert(nbuf > pfxlen + 1 + namlen);
+	memmove(buf, bp->prefix, pfxlen);
+	buf[pfxlen] = '/';
+	memmove(buf + pfxlen + 1, bp->name, namlen);
+	buf[pfxlen + 1 + namlen] = '\0';
+	return buf;
 }
 
 static int
-isdir(Hdr *hp, char *name)
+isdir(Blk *bp, char *name)
 {
 	/* the mode test is ugly but sometimes necessary */
-	return hp->linkflag == LF_DIR ||
+	return bp->linkflag == LF_DIR ||
 		strrchr(name, '\0')[-1] == '/' ||
-		(strtoul(hp->mode, nil, 8)&0170000) == 040000;
+		(strtoul(bp->mode, nil, 8)&0170000) == 040000;
 }
 
 static int
-eotar(Hdr *hp)
+eotar(Blk *bp)
 {
-	return name(hp)[0] == '\0';
+	char buf[Maxname + 1];
+	return parsename(bp, buf, sizeof(buf))[0] == '\0';
 }
 
 /*
@@ -583,22 +596,23 @@
  * 2^64-1 (actually 2^80-1 but our file sizes are vlongs) rather than 2^33-1.
  */
 static Off
-hdrsize(Hdr *hp)
+hdrsize(Blk *bp)
 {
 	uchar *p;
+	char buf[Maxname + 1];
 
-	if((uchar)hp->size[0] == Binnegsz) {
+	if((uchar)bp->size[0] == Binnegsz) {
 		fprint(2, "%s: %s: negative length, which is insane\n",
-			argv0, name(hp));
+			argv0, parsename(bp, buf, sizeof(buf)));
 		return 0;
-	} else if((uchar)hp->size[0] == Binsize) {
-		p = (uchar *)hp->size + sizeof hp->size - 1 -
+	} else if((uchar)bp->size[0] == Binsize) {
+		p = (uchar *)bp->size + sizeof bp->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 hdrotoull(bp->size, bp->size + sizeof bp->size, 0,
+		parsename(bp, buf, sizeof(buf)), "size");
 }
 
 /*
@@ -605,72 +619,272 @@
  * return the number of bytes recorded in the archive.
  */
 static Off
-arsize(Hdr *hp, char *fname)
+arsize(Blk *bp, char *fname)
 {
-	if(isdir(hp, fname) || islink(hp->linkflag))
+	if(isdir(bp, fname) || islink(bp->linkflag))
 		return 0;
-	return hdrsize(hp);
+	return hdrsize(bp);
 }
 
 static long
 parsecksum(char *cksum, char *name)
 {
-	Hdr *hp;
+	Blk *bp;
 
-	return hdrotoull(cksum, cksum + sizeof hp->chksum, (uvlong)-1LL,
+	return hdrotoull(cksum, cksum + sizeof bp->chksum, (uvlong)-1LL,
 		name, "checksum");
 }
 
-static Hdr *
-readhdr(int ar)
+static Blk *
+readhdrblk(int ar)
 {
+	char buf[Maxname + 1];
 	long hdrcksum;
-	Hdr *hp;
+	Blk *bp;
 
-	hp = getblkrd(ar, Alldata);
-	if (hp == nil)
+	bp = getblkrd(ar, Alldata);
+	if (bp == nil)
 		sysfatal("unexpected EOF instead of archive header in %s",
 			arname);
-	if (eotar(hp))			/* end-of-archive block? */
+	if (eotar(bp))			/* end-of-archive block? */
 		return nil;
 
-	hdrcksum = parsecksum(hp->chksum, name(hp));
-	if (hdrcksum == -1 || chksum(hp) != hdrcksum) {
+	hdrcksum = parsecksum(bp->chksum, parsename(bp, buf, sizeof(buf)));
+	if (hdrcksum == -1 || chksum(bp) != hdrcksum) {
 		if (!resync)
 			sysfatal("bad archive header checksum in %s: "
 				"name %.100s...; expected %#luo got %#luo",
-				arname, hp->name, hdrcksum, chksum(hp));
+				arname, bp->name, hdrcksum, chksum(bp));
 		fprint(2, "%s: skipping past archive header with bad checksum in %s...",
 			argv0, arname);
 		do {
-			hp = getblkrd(ar, Alldata);
-			if (hp == nil)
+			bp = getblkrd(ar, Alldata);
+			if (bp == 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));
+			hdrcksum = parsecksum(bp->chksum, parsename(bp, buf, sizeof(buf)));
+		} while (hdrcksum == -1 || chksum(bp) != hdrcksum);
+		fprint(2, "found %s\n", parsename(bp, buf, sizeof(buf)));
 	}
-	nexthdr += Tblock*(1 + BYTES2TBLKS(hdrsize(hp)));
-	return hp;
+	nexthdr += Tblock*(1 + BYTES2TBLKS(hdrsize(bp)));
+
+	return bp;
 }
 
-/*
- * tar r[c]
- */
+static int
+getname(int ar, Blk *bp, Hdr *hdr)
+{
+	char buf[Maxlongname+1];
+	ulong blksleft, blksread;
+	char *p;
+	int n;
 
+	if (bp->linkflag != LF_LONGNAME){
+		/* PAX attributes take precedence */
+		if(hdr->name == nil)
+			hdr->name = strdup(parsename(bp, buf, sizeof(buf)));
+		return 0;
+	}
+
+	p = buf;
+	for (blksleft = BYTES2TBLKS(hdrsize(bp)); blksleft > 0; blksleft -= blksread) {
+		bp = getblkrd(ar, Alldata);
+		if (bp == nil)
+			sysfatal("unexpected EOF on archive reading from %s", arname);
+		blksread = gothowmany(blksleft);
+		n = &buf[Maxlongname] - p;
+		if(Tblock*blksread < n)
+			n = Tblock*blksread;
+		memmove(p, bp->data, n);
+		p += n;
+	}
+	*p = '\0';
+	hdr->name = strdup(buf);
+	return 1;
+}
+
+static char *
+matchattr(char *kvp, char *k)
+{
+	if (strncmp(kvp, k, strlen(k)) == 0)
+		return kvp + strlen(k);
+	return nil;
+}
+
+static int
+parsepax(int ar, Blk *bp, Hdr *hdr, int paxtype)
+{
+	char *p, *lp, *le, *e, *kvp, *val, lenbuf[16];
+	ulong blksleft;
+	int n, off, len;
+	Blk *b;
+
+	if (!isustar(bp) || bp->linkflag != paxtype)
+		return 0;
+	off = 0;
+	len = -1;
+	kvp = nil;
+	lp = lenbuf;
+	le = lenbuf + sizeof(lenbuf);
+	for (blksleft = BYTES2TBLKS(hdrsize(bp)); blksleft > 0; ) {
+		b = getblkrd(ar, Alldata);
+		if (b == nil)
+			sysfatal("unexpected EOF on archive reading attr for %s from %s",  bp->name, arname);
+		p = (char *)b->data;
+		e = (char *)b->data + sizeof(b->data);
+		while(p != e) {
+			if(*p == '\0')
+				break;
+			/*
+			 * Copy out the length prefix. This may span a block boundary, so be
+			 * careful about when we get the next block. The length prefix includes
+			 * both its own length and the trailing newline. We want to trim the
+			 * length prefix, since we already consumed it.
+			 */
+			if(len == -1){
+				while(p != e && *p >= '0' && *p <= '9'){
+					if(lp + 1 == le)
+						sysfatal("oversize length prefix in pax header");
+					*lp++ = *p++;
+				}
+				if (p == e && blksleft > 0)
+					goto nextblk;
+				if (*p++ != ' ')
+					sysfatal("invalid delimiter in pax header: %c (%s)\n", *p, p);
+				*lp++ = '\0';
+				len = atoi(lenbuf) - strlen(lenbuf) - 1;
+			}
+	
+			if (kvp == nil && (kvp = malloc(len)) == nil)
+				sysfatal("out of memory: %r");
+			n = (len - off > e - p) ? e - p : len - off;
+			memcpy(kvp + off, p, n);
+			kvp[len - 1] = '\0';
+			off += n;
+			p += n;
+			assert(e >= p);
+			if (len == off) {
+				if ((val = matchattr(kvp, "path=")) != nil) {
+					free(hdr->name);
+					hdr->name = strdup(val);
+				} else if ((val = matchattr(kvp, "linkpath=")) != nil) {
+					/* Mostly for better error messages. */
+					free(hdr->name);
+					hdr->name = strdup(val);
+				} else if ((val = matchattr(kvp, "uname=")) != nil) {
+					free(hdr->uid);
+					hdr->uid = strdup(val);
+				} else if ((val = matchattr(kvp, "gname=")) != nil) {
+					free(hdr->gid);
+					hdr->gid = strdup(val);
+				} else if ((val = matchattr(kvp, "atime=")) != nil)
+					hdr->atime = strtoll(val, nil, 0);
+				else if ((val = matchattr(kvp, "mtime=")) != nil)
+					hdr->mtime = strtoll(val, nil, 0);
+				else if ((val = matchattr(kvp, "size=")) != nil)
+					hdr->size = strtoll(val, nil, 0);
+				else
+					if (matchattr(kvp, "comment=") == nil && matchattr(kvp, "ctime=") == nil)
+						fprint(2, "warning: pax attribute not supported: %s\n", kvp);
+				free(kvp);
+				kvp = nil;
+				lp = lenbuf;
+				off = 0;
+				len = -1;
+			}
+		}
+nextblk:
+		blksleft--;
+	}
+	return 1;
+}
+
+static int
+parsehdr(Hdr *hdr, Blk *bp)
+{
+	int ustar;
+
+	ustar = isustar(bp);
+	if(hdr->mode == -1)
+		hdr->mode = (strtoul(bp->mode, nil, 8) & 0777);
+	if(hdr->mode == -1)
+		hdr->mode = globlhdr.mode;
+	if (isdir(bp, hdr->name))
+		hdr->mode |= DMDIR;
+	if (hdr->atime == -1)
+		hdr->atime = -1;
+	if (hdr->atime == -1)
+		hdr->atime = globlhdr.atime;
+	if (hdr->mtime == -1)
+		hdr->mtime = strtol(bp->mtime, nil, 8);
+	if (hdr->mtime == -1)
+		hdr->mtime = globlhdr.mtime;
+	if (hdr->size == -1)
+		hdr->size = arsize(bp, hdr->name);
+	if (ustar && hdr->uid == nil)
+		hdr->uid = strdup(bp->uname);
+	if (hdr->uid == nil)
+		hdr->uid = (globlhdr.uid != nil) ? strdup(globlhdr.uid) : nil;
+	if (ustar && hdr->gid == nil)
+		hdr->gid = strdup(bp->uname);
+	if (hdr->gid == nil)
+		hdr->gid = (globlhdr.gid != nil) ? strdup(globlhdr.gid) : nil;
+	return 0;
+}
+
+static void
+nullhdr(Hdr *hdr)
+{
+	hdr->name = nil;
+	hdr->uid = nil;
+	hdr->gid = nil;
+	hdr->mode = -1;
+	hdr->mtime = -1;
+	hdr->atime = -1;
+	hdr->size = -1;
+}
+
+static Blk *
+readhdr(int ar, Hdr *hdr)
+{
+	Blk *bp;
+
+	nullhdr(hdr);
+again:
+	if ((bp = readhdrblk(ar)) == nil)
+		return nil;
+	if (parsepax(ar, bp, hdr, LF_PAXHDR))
+		goto again;
+	if (getname(ar, bp, hdr))
+		goto again;
+	if (parsehdr(hdr, bp) == -1)
+		sysfatal("could not parse header: %r");
+	if (parsepax(ar, bp, &globlhdr, LF_PAXGLOBL))
+		goto again;
+
+	return bp;
+}
+
+void
+freehdr(Hdr *hdr)
+{
+	free(hdr->name);
+	free(hdr->uid);
+	free(hdr->gid);
+}
+
 /*
  * if name is longer than Namsiz bytes, try to split it at a slash and fit the
- * pieces into hp->prefix and hp->name.
+ * pieces into bp->prefix and bp->name.
  */
 static int
-putfullname(Hdr *hp, char *name)
+putfullname(Blk *bp, char *name)
 {
 	int namlen, pfxlen;
 	char *sl, *osl;
 	String *slname = nil;
 
-	if (isdir(hp, name)) {
+	if (isdir(bp, name)) {
 		slname = s_new();
 		s_append(slname, name);
 		s_append(slname, "/");		/* posix requires this */
@@ -679,8 +893,8 @@
 
 	namlen = strlen(name);
 	if (namlen <= Namsiz) {
-		strncpy(hp->name, name, Namsiz);
-		hp->prefix[0] = '\0';		/* ustar paranoia */
+		strncpy(bp->name, name, Namsiz);
+		bp->prefix[0] = '\0';		/* ustar paranoia */
 		return 0;
 	}
 
@@ -693,12 +907,12 @@
 	 * 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.
+	 * bp->prefix, which is larger than bp->name.
 	 */
 	sl = strrchr(name, '/');
 	while (sl != nil) {
 		pfxlen = sl - name;
-		if (pfxlen <= sizeof hp->prefix && namlen-1 - pfxlen <= Namsiz)
+		if (pfxlen <= sizeof bp->prefix && namlen-1 - pfxlen <= Namsiz)
 			break;
 		osl = sl;
 		*osl = '\0';
@@ -711,9 +925,9 @@
 		return -1;
 	}
 	*sl = '\0';
-	strncpy(hp->prefix, name, sizeof hp->prefix);
+	strncpy(bp->prefix, name, sizeof bp->prefix);
 	*sl++ = '/';
-	strncpy(hp->name, sl, sizeof hp->name);
+	strncpy(bp->name, sl, sizeof bp->name);
 	if (slname)
 		s_free(slname);
 	return 0;
@@ -720,7 +934,7 @@
 }
 
 static int
-mkhdr(Hdr *hp, Dir *dir, char *file)
+mkhdr(Blk *bp, Dir *dir, char *file)
 {
 	int r;
 
@@ -728,9 +942,9 @@
 	 * 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);
+	sprint(bp->mode, "%6lo ", dir->mode & 0777);
+	sprint(bp->uid, "%6o ", aruid);
+	sprint(bp->gid, "%6o ", argid);
 	if (dir->length >= (Off)1<<32) {
 		static int printed;
 
@@ -738,22 +952,22 @@
 			printed = 1;
 			fprint(2, "%s: storing large sizes in \"base 256\"\n", argv0);
 		}
-		hp->size[0] = Binsize;
+		bp->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] = ' ';
+		putbe((uchar *)bp->size+1, dir->length, sizeof bp->size - 2);
+		bp->size[sizeof bp->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);
+		sprint(bp->size, "%11lluo ", dir->length);
+	sprint(bp->mtime, "%11luo ", dir->mtime);
+	bp->linkflag = (dir->mode&DMDIR? LF_DIR: LF_PLAIN1);
+	r = putfullname(bp, 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);
+		strncpy(bp->magic, "ustar", sizeof bp->magic);
+		strncpy(bp->version, "00", sizeof bp->version);
+		strncpy(bp->uname, dir->uid, sizeof bp->uname);
+		strncpy(bp->gname, dir->gid, sizeof bp->gname);
 	}
-	sprint(hp->chksum, "%6luo", chksum(hp));
+	sprint(bp->chksum, "%6luo", chksum(bp));
 	return r;
 }
 
@@ -806,7 +1020,7 @@
 	int n, fd, isdir;
 	long bytes, blksread;
 	ulong blksleft;
-	Hdr *hbp;
+	Blk *hbp;
 	Dir *dir;
 	String *name = nil;
 
@@ -877,7 +1091,7 @@
 	int i, ar;
 	ulong blksleft, blksread;
 	Off bytes;
-	Hdr *hp;
+	Blk *bp;
 	Compress *comp = nil;
 	Pushstate ps;
 
@@ -897,8 +1111,8 @@
 
 	if (usefile && !docreate) {
 		/* skip quickly to the end */
-		while ((hp = readhdr(ar)) != nil) {
-			bytes = hdrsize(hp);
+		while ((bp = readhdrblk(ar)) != nil) {
+			bytes = hdrsize(bp);
 			for (blksleft = BYTES2TBLKS(bytes);
 			     blksleft > 0 && getblkrd(ar, Justnxthdr) != nil;
 			     blksleft -= blksread) {
@@ -1044,13 +1258,14 @@
 }
 
 static int
-openfname(Hdr *hp, char *fname, int dir, int mode)
+openfname(Blk *bp, char *fname, int mode)
 {
-	int fd;
+	int fd, dir;
 
 	fd = -1;
+	dir = mode & DMDIR;
 	cleanname(fname);
-	switch (hp->linkflag) {
+	switch (bp->linkflag) {
 	case LF_LINK:
 	case LF_SYMLINK1:
 	case LF_SYMLINK2:
@@ -1063,7 +1278,7 @@
 		break;
 	default:
 		if (!keepexisting || access(fname, AEXIST) < 0) {
-			int rw = (dir? OREAD: OWRITE);
+			int rw = (dir ? OREAD: OWRITE);
 
 			fd = create(fname, rw, mode);
 			if (fd < 0) {
@@ -1086,7 +1301,7 @@
 {
 	int wrbytes;
 	ulong blksread;
-	Hdr *hbp;
+	Blk *hbp;
 
 	if (blksleft == 0 || bytes < 0)
 		bytes = 0;
@@ -1118,20 +1333,22 @@
 }
 
 static void
-wrmeta(int fd, Hdr *hp, long mtime, int mode)		/* update metadata */
+wrmeta(int fd, Hdr *hdr)		/* update metadata */
 {
 	Dir nd;
 
 	nulldir(&nd);
-	nd.mtime = mtime;
-	nd.mode = mode;
+	nd.mtime = hdr->mtime;
+	nd.mode = hdr->mode;
 	dirfwstat(fd, &nd);
-	if (isustar(hp)) {
+	if (hdr->gid) {
 		nulldir(&nd);
-		nd.gid = hp->gname;
+		nd.gid = hdr->gid;
 		dirfwstat(fd, &nd);
+	}
+	if (hdr->uid){
 		nulldir(&nd);
-		nd.uid = hp->uname;
+		nd.uid = hdr->uid;
 		dirfwstat(fd, &nd);
 	}
 }
@@ -1141,20 +1358,15 @@
  * fname is result of getname(), so has two extra bytes at beginning.
  */
 static void
-extract1(int ar, Hdr *hp, char *fname)
+extract1(int ar, Blk *bp, Hdr *hdr)
 {
-	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, fname));
+	int fd = -1;
+	Off bytes = hdr->size;		/* for printing */
+	uvlong blksleft = BYTES2TBLKS(bytes);
+	char *path;
 
 	/* fiddle name, figure out mode and blocks */
-	if (isdir(hp, fname)) {
-		mode |= DMDIR|0700;
-		dir = 1;
-	}
-	switch (hp->linkflag) {
+	switch (bp->linkflag) {
 	case LF_LINK:
 	case LF_SYMLINK1:
 	case LF_SYMLINK2:
@@ -1162,25 +1374,27 @@
 		blksleft = 0;
 		break;
 	}
-	if (relative)
-		if(fname[0] == '/')
-			*--fname = '.';
-		else if(fname[0] == '#'){
-			*--fname = '/';
-			*--fname = '.';
-		}
+	if((path = malloc(strlen(hdr->name) + 3)) == nil)
+		sysfatal("malloc: %r");
+	if (relative && hdr->name[0] == '/')
+		strcpy(path, ".");
+	else if(relative && hdr->name[0] == '#')
+		strcpy(path, "./");
+	else
+		path[0] = '\0';
+	strcat(path, hdr->name);
 
 	if (verb == Xtract)
-		fd = openfname(hp, fname, dir, mode);
+		fd = openfname(bp, path, hdr->mode);
 	else if (verbose) {
-		char *cp = ctime(mtime);
+		char *cp = ctime(hdr->mtime);
 
-		print("%M %8lld %-12.12s %-4.4s %s\n",
-			mode, bytes, cp+4, cp+24, fname);
+		print("%M %8lld  %-12.12s %-4.4s %s\n",
+			hdr->mode, bytes, cp+4, cp+24, path);
 	} else
-		print("%s\n", fname);
+		print("%s\n", path);
 
-	copyfromar(ar, fd, fname, blksleft, bytes);
+	copyfromar(ar, fd, path, blksleft, hdr->size);
 
 	/* touch up meta data and close */
 	if (fd >= 0) {
@@ -1189,70 +1403,35 @@
 		 * creating files in them, but we don't do that.
 		 */
 		if (settime)
-			wrmeta(fd, hp, mtime, mode);
+			wrmeta(fd, hdr);
 		close(fd);
 	}
+	free(path);
 }
 
 static void
-skip(int ar, Hdr *hp, char *fname)
+skip(int ar, Blk *bp, Hdr *hdr)
 {
 	ulong blksleft, blksread;
-	Hdr *hbp;
+	Blk *hbp;
 
-	for (blksleft = BYTES2TBLKS(arsize(hp, fname)); blksleft > 0;
+	for (blksleft = BYTES2TBLKS(arsize(bp, hdr->name)); blksleft > 0;
 	     blksleft -= blksread) {
 		hbp = getblkrd(ar, Justnxthdr);
 		if (hbp == nil)
 			sysfatal("unexpected EOF on archive extracting %s from %s",
-				fname, arname);
+				hdr->name, 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(hdrsize(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;
+	Blk *bp;
+	Hdr hdr;
 	Compress *comp;
 	Pushstate ps;
 
@@ -1266,14 +1445,12 @@
 	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);
+	while ((bp = readhdr(ar, &hdr)) != nil) {
+		if (match(hdr.name, argv))
+			extract1(ar, bp, &hdr);
 		else
-			skip(ar, hp, longname);
+			skip(ar, bp, &hdr);
+		freehdr(&hdr);
 	}
 
 	if (comp)
@@ -1356,6 +1533,7 @@
 		usage();
 
 	initblks();
+	nullhdr(&globlhdr);
 	switch (verb) {
 	case Toc:
 	case Xtract: