1164 lines · 23.5 KB · C
RawDownload
/* * 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 <thread.h>#include <fcall.h>		/* for %M */#include <libString.h> /* * modified versions of those in libc.h; scans only the first arg for * keyletters and options. */#define	TARGBEGIN {\	if (!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,	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 */}; #define islink(lf)	(isreallink(lf) || issymlink(lf))#define isreallink(lf)	((lf) == LF_LINK)#define issymlink(lf)	((lf) == LF_SYMLINK1 || (lf) == LF_SYMLINK2) typedef struct Hdr {	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 */		char pad[12];} Hdr; typedef struct Compress {	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 Pushstate {	int	kid;	int	fd;	/* original fd */	int	rfd;	/* replacement fd */	int	input;	int	open;} Pushstate; #define OTHER(rdwr) (rdwr == Rd? Wr: Rd) /* static int debug; */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 Off blkoff;	/* offset of the current archive block (not Tblock) */static Off nexthdr; static int nblock = Dblock;static char *usefile;static char origdir[Maxname*2];static Hdr *tpblk, *endblk;static Hdr *curblk; static voidusage(void){	fprint(2, "usage: %s {crtx}[PRTfgkmpuvz] [archive] file1 file2...\n",		argv0);	threadexitsall("usage");} /* compression */ static Compress *compmethod(char *name){	int i, nmlen = strlen(name), sfxlen;	Compress *cp; 	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 intpush(int fd, char *cmd, int input, Pushstate *ps){	int nfd, pifds[2], fds[3]; 	ps->open = 0;	ps->fd = fd;	ps->input = input;	if (fd < 0 || pipe(pifds) < 0)		return -1;	if (input)		fds[Stdout] = pifds[Wr];	else		fds[Stdin] = pifds[Rd];	nfd = pifds[input? Rd: Wr];	fds[input? Stdin: Stdout] = dup(fd, -1);	fds[Stderr] = dup(Stderr, -1);	ps->kid = threadspawnl(fds, cmd, cmd, nil);	if (ps->kid==-1)		sysfatal("can't spawn %s: %r", cmd);	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 voidinitblks(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; 	if (first)		seekable = seek(ar, 0, 1) >= 0;	blkoff = seek(ar, 0, 1);		/* note position for `tar r' */	/* try to size non-pipe input at first read */	if (first && usefile) {		n = read(ar, bufs, bytes);		if (n <= 0)			sysfatal("error reading archive: %r");		i = n;		if (i % Tblock != 0) {			fprint(2, "%s: archive block size (%d) error\n",				argv0, i);			threadexitsall("blocksize");		}		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 - seek(ar, 0, 1) >= bytes) {		/* optimisation for huge archive members on seekable media */		if (seek(ar, bytes, 1) < 0)			sysfatal("can't seek on archive: %r");		n = bytes;	} else		n = readn(ar, bufs, bytes);	first = 0; 	if (n == 0)		sysfatal("unexpected EOF reading archive");	else if (n < 0)		sysfatal("error reading archive: %r");	else if (n%Tblock != 0)		sysfatal("partial block read from archive");	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, 0, Tblock);	return hp;} /* * how many block buffers are available, starting at the address * just returned by getblk*? */static intgothowmany(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 voidputlastblk(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);	if (write(ar, tpblk, bytes) != bytes)		sysfatal("error writing archive: %r");} static voidputblk(int ar){	if (curblk >= endblk)		putlastblk(ar);} static voidputbackblk(int ar){	curblk--;	USED(ar);} static voidputreadblks(int ar, int blks){	curblk += blks - 1;	USED(ar);} static voidputblkmany(int ar, int blks){	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 longchksum(Hdr *hp){	int n = Tblock;	long i = 0;	uchar *cp = (uchar*)hp;	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 intisustar(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 intsstrnlen(char *s, int n){	return s[n - 1] != '\0'? n: strlen(s);} /* set fullname from header */static char *name(Hdr *hp){	int pfxlen, namlen;	static char fullnamebuf[2 + Maxname + 1];	/* 2 at beginning for ./ on relative names */	char *fullname; 	fullname = fullnamebuf+2;	namlen = sstrnlen(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 = sstrnlen(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 intisdir(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 inteotar(Hdr *hp){	return name(hp)[0] == '\0';} /*static uvlonggetbe(uchar *src, int size){	uvlong vl = 0; 	while (size-- > 0) {		vl <<= 8;		vl |= *src++;	}	return vl;} */ static voidputbe(uchar *dest, uvlong vl, int size){	for (dest += size; size-- > 0; vl >>= 8)		*--dest = vl;} /* * 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 Offhdrsize(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 */

Lines 1–500 of 1164.