plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

unzip.c (14165B)


      1 #include <u.h>
      2 #include <libc.h>
      3 #include <bio.h>
      4 #include <flate.h>
      5 #include "zip.h"
      6 
      7 enum
      8 {
      9 	BufSize	= 4096
     10 };
     11 
     12 static	int	cheader(Biobuf *bin, ZipHead *zh);
     13 static	int	copyout(int ofd, Biobuf *bin, long len);
     14 static	int	crcwrite(void *ofd, void *buf, int n);
     15 static	int	findCDir(Biobuf *bin, char *file);
     16 static	int	get1(Biobuf *b);
     17 static	int	get2(Biobuf *b);
     18 static	u32int	get4(Biobuf *b);
     19 static	char	*getname(Biobuf *b, int len);
     20 static	int	header(Biobuf *bin, ZipHead *zh);
     21 static	long	msdos2time(int time, int date);
     22 static	int	sunzip(Biobuf *bin);
     23 static	int	sunztable(Biobuf *bin);
     24 static	void	trailer(Biobuf *bin, ZipHead *zh);
     25 static	int	unzip(Biobuf *bin, char *file);
     26 static	int	unzipEntry(Biobuf *bin, ZipHead *czh);
     27 static	int	unztable(Biobuf *bin, char *file);
     28 static	int	wantFile(char *file);
     29 
     30 static	void	*emalloc(u32int);
     31 static	void	error(char*, ...);
     32 /* #pragma	varargck	argpos	error	1 */
     33 
     34 static	Biobuf	bin;
     35 static	u32int	crc;
     36 static	u32int	*crctab;
     37 static	int	debug;
     38 static	char	*delfile;
     39 static	int	lower;
     40 static	int	nwant;
     41 static	u32int	rlen;
     42 static	int	settimes;
     43 static	int	stdout;
     44 static	int	verbose;
     45 static	char	**want;
     46 static	int	wbad;
     47 static	u32int	wlen;
     48 static	jmp_buf	zjmp;
     49 
     50 static void
     51 usage(void)
     52 {
     53 	fprint(2, "usage: unzip [-tsv] [-f zipfile] [file ...]\n");
     54 	exits("usage");
     55 }
     56 
     57 void
     58 main(int argc, char *argv[])
     59 {
     60 	char *zfile;
     61 	int fd, ok, table, stream;
     62 
     63 	table = 0;
     64 	stream = 0;
     65 	zfile = nil;
     66 	ARGBEGIN{
     67 	case 'D':
     68 		debug++;
     69 		break;
     70 	case 'c':
     71 		stdout++;
     72 		break;
     73 	case 'i':
     74 		lower++;
     75 		break;
     76 	case 'f':
     77 		zfile = ARGF();
     78 		if(zfile == nil)
     79 			usage();
     80 		break;
     81 	case 's':
     82 		stream++;
     83 		break;
     84 	case 't':
     85 		table++;
     86 		break;
     87 	case 'T':
     88 		settimes++;
     89 		break;
     90 	case 'v':
     91 		verbose++;
     92 		break;
     93 	default:
     94 		usage();
     95 		break;
     96 	}ARGEND
     97 
     98 	nwant = argc;
     99 	want = argv;
    100 
    101 	crctab = mkcrctab(ZCrcPoly);
    102 	ok = inflateinit();
    103 	if(ok != FlateOk)
    104 		sysfatal("inflateinit failed: %s\n", flateerr(ok));
    105 
    106 	if(zfile == nil){
    107 		Binit(&bin, 0, OREAD);
    108 		zfile = "<stdin>";
    109 	}else{
    110 		fd = open(zfile, OREAD);
    111 		if(fd < 0)
    112 			sysfatal("can't open %s: %r", zfile);
    113 		Binit(&bin, fd, OREAD);
    114 	}
    115 
    116 	if(table){
    117 		if(stream)
    118 			ok = sunztable(&bin);
    119 		else
    120 			ok = unztable(&bin, zfile);
    121 	}else{
    122 		if(stream)
    123 			ok = sunzip(&bin);
    124 		else
    125 			ok = unzip(&bin, zfile);
    126 	}
    127 
    128 	exits(ok ? nil: "errors");
    129 }
    130 
    131 /*
    132  * print the table of contents from the "central directory structure"
    133  */
    134 static int
    135 unztable(Biobuf *bin, char *file)
    136 {
    137 	ZipHead zh;
    138 	int volatile entries;
    139 
    140 	entries = findCDir(bin, file);
    141 	if(entries < 0)
    142 		return 0;
    143 
    144 	if(verbose > 1)
    145 		print("%d items in the archive\n", entries);
    146 	while(entries-- > 0){
    147 		if(setjmp(zjmp)){
    148 			free(zh.file);
    149 			return 0;
    150 		}
    151 
    152 		memset(&zh, 0, sizeof(zh));
    153 		if(!cheader(bin, &zh))
    154 			return 1;
    155 
    156 		if(wantFile(zh.file)){
    157 			if(verbose)
    158 				print("%-32s %10lud %s", zh.file, zh.uncsize, ctime(msdos2time(zh.modtime, zh.moddate)));
    159 			else
    160 				print("%s\n", zh.file);
    161 
    162 			if(verbose > 1){
    163 				print("\tmade by os %d vers %d.%d\n", zh.madeos, zh.madevers/10, zh.madevers % 10);
    164 				print("\textract by os %d vers %d.%d\n", zh.extos, zh.extvers/10, zh.extvers % 10);
    165 				print("\tflags %x\n", zh.flags);
    166 				print("\tmethod %d\n", zh.meth);
    167 				print("\tmod time %d\n", zh.modtime);
    168 				print("\tmod date %d\n", zh.moddate);
    169 				print("\tcrc %lux\n", zh.crc);
    170 				print("\tcompressed size %lud\n", zh.csize);
    171 				print("\tuncompressed size %lud\n", zh.uncsize);
    172 				print("\tinternal attributes %ux\n", zh.iattr);
    173 				print("\texternal attributes %lux\n", zh.eattr);
    174 				print("\tstarts at %ld\n", zh.off);
    175 			}
    176 		}
    177 
    178 		free(zh.file);
    179 		zh.file = nil;
    180 	}
    181 
    182 	return 1;
    183 }
    184 
    185 /*
    186  * print the "local file header" table of contents
    187  */
    188 static int
    189 sunztable(Biobuf *bin)
    190 {
    191 	ZipHead zh;
    192 	vlong off;
    193 	u32int hcrc, hcsize, huncsize;
    194 	int ok, err;
    195 
    196 	ok = 1;
    197 	for(;;){
    198 		if(setjmp(zjmp)){
    199 			free(zh.file);
    200 			return 0;
    201 		}
    202 
    203 		memset(&zh, 0, sizeof(zh));
    204 		if(!header(bin, &zh))
    205 			return ok;
    206 
    207 		hcrc = zh.crc;
    208 		hcsize = zh.csize;
    209 		huncsize = zh.uncsize;
    210 
    211 		wlen = 0;
    212 		rlen = 0;
    213 		crc = 0;
    214 		wbad = 0;
    215 
    216 		if(zh.meth == 0){
    217 			if(!copyout(-1, bin, zh.csize))
    218 				error("reading data for %s failed: %r", zh.file);
    219 		}else if(zh.meth == 8){
    220 			off = Boffset(bin);
    221 			err = inflate((void*)-1, crcwrite, bin, (int(*)(void*))Bgetc);
    222 			if(err != FlateOk)
    223 				error("inflate %s failed: %s", zh.file, flateerr(err));
    224 			rlen = Boffset(bin) - off;
    225 		}else
    226 			error("can't handle compression method %d for %s", zh.meth, zh.file);
    227 
    228 		trailer(bin, &zh);
    229 
    230 		if(wantFile(zh.file)){
    231 			if(verbose)
    232 				print("%-32s %10lud %s", zh.file, zh.uncsize, ctime(msdos2time(zh.modtime, zh.moddate)));
    233 			else
    234 				print("%s\n", zh.file);
    235 
    236 			if(verbose > 1){
    237 				print("\textract by os %d vers %d.%d\n", zh.extos, zh.extvers / 10, zh.extvers % 10);
    238 				print("\tflags %x\n", zh.flags);
    239 				print("\tmethod %d\n", zh.meth);
    240 				print("\tmod time %d\n", zh.modtime);
    241 				print("\tmod date %d\n", zh.moddate);
    242 				print("\tcrc %lux\n", zh.crc);
    243 				print("\tcompressed size %lud\n", zh.csize);
    244 				print("\tuncompressed size %lud\n", zh.uncsize);
    245 				if((zh.flags & ZTrailInfo) && (hcrc || hcsize || huncsize)){
    246 					print("\theader crc %lux\n", zh.crc);
    247 					print("\theader compressed size %lud\n", zh.csize);
    248 					print("\theader uncompressed size %lud\n", zh.uncsize);
    249 				}
    250 			}
    251 		}
    252 
    253 		if(zh.crc != crc)
    254 			error("crc mismatch for %s", zh.file);
    255 		if(zh.uncsize != wlen)
    256 			error("output size mismatch for %s", zh.file);
    257 		if(zh.csize != rlen)
    258 			error("input size mismatch for %s", zh.file);
    259 
    260 
    261 		free(zh.file);
    262 		zh.file = nil;
    263 	}
    264 }
    265 
    266 /*
    267  * extract files using the info in the central directory structure
    268  */
    269 static int
    270 unzip(Biobuf *bin, char *file)
    271 {
    272 	ZipHead zh;
    273 	vlong off;
    274 	int volatile ok, eok, entries;
    275 
    276 	entries = findCDir(bin, file);
    277 	if(entries < 0)
    278 		return 0;
    279 
    280 	ok = 1;
    281 	while(entries-- > 0){
    282 		if(setjmp(zjmp)){
    283 			free(zh.file);
    284 			return 0;
    285 		}
    286 		memset(&zh, 0, sizeof(zh));
    287 		if(!cheader(bin, &zh))
    288 			return ok;
    289 
    290 
    291 		off = Boffset(bin);
    292 		if(wantFile(zh.file)){
    293 			if(Bseek(bin, zh.off, 0) < 0){
    294 				fprint(2, "unzip: can't seek to start of %s, skipping\n", zh.file);
    295 				ok = 0;
    296 			}else{
    297 				eok = unzipEntry(bin, &zh);
    298 				if(eok <= 0){
    299 					fprint(2, "unzip: skipping %s\n", zh.file);
    300 					ok = 0;
    301 				}
    302 			}
    303 		}
    304 
    305 		free(zh.file);
    306 		zh.file = nil;
    307 
    308 		if(Bseek(bin, off, 0) < 0){
    309 			fprint(2, "unzip: can't seek to start of next entry, terminating extraction\n");
    310 			return 0;
    311 		}
    312 	}
    313 
    314 	return ok;
    315 }
    316 
    317 /*
    318  * extract files using the info the "local file headers"
    319  */
    320 static int
    321 sunzip(Biobuf *bin)
    322 {
    323 	int eok;
    324 
    325 	for(;;){
    326 		eok = unzipEntry(bin, nil);
    327 		if(eok == 0)
    328 			return 1;
    329 		if(eok < 0)
    330 			return 0;
    331 	}
    332 }
    333 
    334 static int
    335 makedir(char *s)
    336 {
    337 	int f;
    338 
    339 	if (access(s, AEXIST) == 0)
    340 		return -1;
    341 	f = create(s, OREAD, DMDIR | 0777);
    342 	if (f >= 0)
    343 		close(f);
    344 	return f;
    345 }
    346 
    347 static void
    348 mkpdirs(char *s)
    349 {
    350 	int done = 0;
    351 	char *p = s;
    352 
    353 	while (!done && (p = strchr(p + 1, '/')) != nil) {
    354 		*p = '\0';
    355 		done = (access(s, AEXIST) < 0 && makedir(s) < 0);
    356 		*p = '/';
    357 	}
    358 }
    359 
    360 /*
    361  * extracts a single entry from a zip file
    362  * czh is the optional corresponding central directory entry
    363  */
    364 static int
    365 unzipEntry(Biobuf *bin, ZipHead *czh)
    366 {
    367 	Dir *d;
    368 	ZipHead zh;
    369 	char *p;
    370 	vlong off;
    371 	int fd, isdir, ok, err;
    372 
    373 	zh.file = nil;
    374 	if(setjmp(zjmp)){
    375 		delfile = nil;
    376 		free(zh.file);
    377 		return -1;
    378 	}
    379 
    380 	memset(&zh, 0, sizeof(zh));
    381 	if(!header(bin, &zh))
    382 		return 0;
    383 
    384 	ok = 1;
    385 	isdir = 0;
    386 
    387 	fd = -1;
    388 	if(wantFile(zh.file)){
    389 		if(verbose)
    390 			fprint(2, "extracting %s\n", zh.file);
    391 
    392 		if(czh != nil && czh->extos == ZDos){
    393 			isdir = czh->eattr & ZDDir;
    394 			if(isdir && zh.uncsize != 0)
    395 				fprint(2, "unzip: ignoring directory data for %s\n", zh.file);
    396 		}
    397 		if(zh.meth == 0 && zh.uncsize == 0){
    398 			p = strchr(zh.file, '\0');
    399 			if(p > zh.file && p[-1] == '/')
    400 				isdir = 1;
    401 		}
    402 
    403 		if(stdout){
    404 			if(ok && !isdir)
    405 				fd = 1;
    406 		}else if(isdir){
    407 			fd = create(zh.file, OREAD, DMDIR | 0775);
    408 			if(fd < 0){
    409 				mkpdirs(zh.file);
    410 				fd = create(zh.file, OREAD, DMDIR | 0775);
    411 			}
    412 			if(fd < 0){
    413 				d = dirstat(zh.file);
    414 				if(d == nil || (d->mode & DMDIR) != DMDIR){
    415 					fprint(2, "unzip: can't create directory %s: %r\n", zh.file);
    416 					ok = 0;
    417 				}
    418 				free(d);
    419 			}
    420 		}else if(ok){
    421 			fd = create(zh.file, OWRITE, 0664);
    422 			if(fd < 0){
    423 				mkpdirs(zh.file);
    424 				fd = create(zh.file, OWRITE, 0664);
    425 			}
    426 			if(fd < 0){
    427 				fprint(2, "unzip: can't create %s: %r\n", zh.file);
    428 				ok = 0;
    429 			}else
    430 				delfile = zh.file;
    431 		}
    432 	}
    433 
    434 	wlen = 0;
    435 	rlen = 0;
    436 	crc = 0;
    437 	wbad = 0;
    438 
    439 	if(zh.meth == 0){
    440 		if(!copyout(fd, bin, zh.csize))
    441 			error("copying data for %s failed: %r", zh.file);
    442 	}else if(zh.meth == 8){
    443 		off = Boffset(bin);
    444 		err = inflate((void*)(uintptr)fd, crcwrite, bin, (int(*)(void*))Bgetc);
    445 		if(err != FlateOk)
    446 			error("inflate failed: %s", flateerr(err));
    447 		rlen = Boffset(bin) - off;
    448 	}else
    449 		error("can't handle compression method %d for %s", zh.meth, zh.file);
    450 
    451 	trailer(bin, &zh);
    452 
    453 	if(zh.crc != crc)
    454 		error("crc mismatch for %s", zh.file);
    455 	if(zh.uncsize != wlen)
    456 		error("output size mismatch for %s", zh.file);
    457 	if(zh.csize != rlen)
    458 		error("input size mismatch for %s", zh.file);
    459 
    460 	delfile = nil;
    461 	free(zh.file);
    462 
    463 	if(fd >= 0 && !stdout){
    464 		if(settimes){
    465 			d = dirfstat(fd);
    466 			if(d != nil){
    467 				d->mtime = msdos2time(zh.modtime, zh.moddate);
    468 				if(d->mtime)
    469 					dirfwstat(fd, d);
    470 			}
    471 		}
    472 		close(fd);
    473 	}
    474 
    475 	return ok;
    476 }
    477 
    478 static int
    479 wantFile(char *file)
    480 {
    481 	int i, n;
    482 
    483 	if(nwant == 0)
    484 		return 1;
    485 	for(i = 0; i < nwant; i++){
    486 		if(strcmp(want[i], file) == 0)
    487 			return 1;
    488 		n = strlen(want[i]);
    489 		if(strncmp(want[i], file, n) == 0 && file[n] == '/')
    490 			return 1;
    491 	}
    492 	return 0;
    493 }
    494 
    495 /*
    496  * find the start of the central directory
    497  * returns the number of entries in the directory,
    498  * or -1 if there was an error
    499  */
    500 static int
    501 findCDir(Biobuf *bin, char *file)
    502 {
    503 	vlong ecoff;
    504 	long off, size;
    505 	int entries, zclen, dn, ds, de;
    506 
    507 	ecoff = Bseek(bin, -ZECHeadSize, 2);
    508 	if(ecoff < 0){
    509 		fprint(2, "unzip: can't seek to contents of %s; try adding -s\n", file);
    510 		return -1;
    511 	}
    512 	if(setjmp(zjmp))
    513 		return -1;
    514 
    515 	if(get4(bin) != ZECHeader){
    516 		fprint(2, "unzip: bad magic number for contents of %s\n", file);
    517 		return -1;
    518 	}
    519 	dn = get2(bin);
    520 	ds = get2(bin);
    521 	de = get2(bin);
    522 	entries = get2(bin);
    523 	size = get4(bin);
    524 	off = get4(bin);
    525 	zclen = get2(bin);
    526 	while(zclen-- > 0)
    527 		get1(bin);
    528 
    529 	if(verbose > 1){
    530 		print("table starts at %ld for %ld bytes\n", off, size);
    531 		if(ecoff - size != off)
    532 			print("\ttable should start at %lld-%ld=%lld\n", ecoff, size, ecoff-size);
    533 		if(dn || ds || de != entries)
    534 			print("\tcurrent disk=%d start disk=%d table entries on this disk=%d\n", dn, ds, de);
    535 	}
    536 
    537 	if(Bseek(bin, off, 0) != off){
    538 		fprint(2, "unzip: can't seek to start of contents of %s\n", file);
    539 		return -1;
    540 	}
    541 
    542 	return entries;
    543 }
    544 
    545 static int
    546 cheader(Biobuf *bin, ZipHead *zh)
    547 {
    548 	u32int v;
    549 	int flen, xlen, fclen;
    550 
    551 	v = get4(bin);
    552 	if(v != ZCHeader){
    553 		if(v == ZECHeader)
    554 			return 0;
    555 		error("bad magic number %lux", v);
    556 	}
    557 	zh->madevers = get1(bin);
    558 	zh->madeos = get1(bin);
    559 	zh->extvers = get1(bin);
    560 	zh->extos = get1(bin);
    561 	zh->flags = get2(bin);
    562 	zh->meth = get2(bin);
    563 	zh->modtime = get2(bin);
    564 	zh->moddate = get2(bin);
    565 	zh->crc = get4(bin);
    566 	zh->csize = get4(bin);
    567 	zh->uncsize = get4(bin);
    568 	flen = get2(bin);
    569 	xlen = get2(bin);
    570 	fclen = get2(bin);
    571 	get2(bin);		/* disk number start */
    572 	zh->iattr = get2(bin);
    573 	zh->eattr = get4(bin);
    574 	zh->off = get4(bin);
    575 
    576 	zh->file = getname(bin, flen);
    577 
    578 	while(xlen-- > 0)
    579 		get1(bin);
    580 
    581 	while(fclen-- > 0)
    582 		get1(bin);
    583 
    584 	return 1;
    585 }
    586 
    587 static int
    588 header(Biobuf *bin, ZipHead *zh)
    589 {
    590 	u32int v;
    591 	int flen, xlen;
    592 
    593 	v = get4(bin);
    594 	if(v != ZHeader){
    595 		if(v == ZCHeader)
    596 			return 0;
    597 		error("bad magic number %lux at %lld", v, Boffset(bin)-4);
    598 	}
    599 	zh->extvers = get1(bin);
    600 	zh->extos = get1(bin);
    601 	zh->flags = get2(bin);
    602 	zh->meth = get2(bin);
    603 	zh->modtime = get2(bin);
    604 	zh->moddate = get2(bin);
    605 	zh->crc = get4(bin);
    606 	zh->csize = get4(bin);
    607 	zh->uncsize = get4(bin);
    608 	flen = get2(bin);
    609 	xlen = get2(bin);
    610 
    611 	zh->file = getname(bin, flen);
    612 
    613 	while(xlen-- > 0)
    614 		get1(bin);
    615 
    616 	return 1;
    617 }
    618 
    619 static void
    620 trailer(Biobuf *bin, ZipHead *zh)
    621 {
    622 	if(zh->flags & ZTrailInfo){
    623 		zh->crc = get4(bin);
    624 		zh->csize = get4(bin);
    625 		zh->uncsize = get4(bin);
    626 	}
    627 }
    628 
    629 static char*
    630 getname(Biobuf *bin, int len)
    631 {
    632 	char *s;
    633 	int i, c;
    634 
    635 	s = emalloc(len + 1);
    636 	for(i = 0; i < len; i++){
    637 		c = get1(bin);
    638 		if(lower)
    639 			c = tolower(c);
    640 		s[i] = c;
    641 	}
    642 	s[i] = '\0';
    643 	return s;
    644 }
    645 
    646 static int
    647 crcwrite(void *out, void *buf, int n)
    648 {
    649 	int fd, nw;
    650 
    651 	wlen += n;
    652 	crc = blockcrc(crctab, crc, buf, n);
    653 	fd = (int)(uintptr)out;
    654 	if(fd < 0)
    655 		return n;
    656 	nw = write(fd, buf, n);
    657 	if(nw != n)
    658 		wbad = 1;
    659 	return nw;
    660 }
    661 
    662 static int
    663 copyout(int ofd, Biobuf *bin, long len)
    664 {
    665 	char buf[BufSize];
    666 	int n;
    667 
    668 	for(; len > 0; len -= n){
    669 		n = len;
    670 		if(n > BufSize)
    671 			n = BufSize;
    672 		n = Bread(bin, buf, n);
    673 		if(n <= 0)
    674 			return 0;
    675 		rlen += n;
    676 		if(crcwrite((void*)(uintptr)ofd, buf, n) != n)
    677 			return 0;
    678 	}
    679 	return 1;
    680 }
    681 
    682 static u32int
    683 get4(Biobuf *b)
    684 {
    685 	u32int v;
    686 	int i, c;
    687 
    688 	v = 0;
    689 	for(i = 0; i < 4; i++){
    690 		c = Bgetc(b);
    691 		if(c < 0)
    692 			error("unexpected eof reading file information");
    693 		v |= c << (i * 8);
    694 	}
    695 	return v;
    696 }
    697 
    698 static int
    699 get2(Biobuf *b)
    700 {
    701 	int i, c, v;
    702 
    703 	v = 0;
    704 	for(i = 0; i < 2; i++){
    705 		c = Bgetc(b);
    706 		if(c < 0)
    707 			error("unexpected eof reading file information");
    708 		v |= c << (i * 8);
    709 	}
    710 	return v;
    711 }
    712 
    713 static int
    714 get1(Biobuf *b)
    715 {
    716 	int c;
    717 
    718 	c = Bgetc(b);
    719 	if(c < 0)
    720 		error("unexpected eof reading file information");
    721 	return c;
    722 }
    723 
    724 static long
    725 msdos2time(int time, int date)
    726 {
    727 	Tm tm;
    728 
    729 	tm.hour = time >> 11;
    730 	tm.min = (time >> 5) & 63;
    731 	tm.sec = (time & 31) << 1;
    732 	tm.year = 80 + (date >> 9);
    733 	tm.mon = ((date >> 5) & 15) - 1;
    734 	tm.mday = date & 31;
    735 	tm.zone[0] = '\0';
    736 	tm.yday = 0;
    737 
    738 	return tm2sec(&tm);
    739 }
    740 
    741 static void*
    742 emalloc(u32int n)
    743 {
    744 	void *p;
    745 
    746 	p = malloc(n);
    747 	if(p == nil)
    748 		sysfatal("out of memory");
    749 	return p;
    750 }
    751 
    752 static void
    753 error(char *fmt, ...)
    754 {
    755 	va_list arg;
    756 
    757 	fprint(2, "unzip: ");
    758 	va_start(arg, fmt);
    759 	vfprint(2, fmt, arg);
    760 	va_end(arg);
    761 	fprint(2, "\n");
    762 
    763 	if(delfile != nil){
    764 		fprint(2, "unzip: removing output file %s\n", delfile);
    765 		remove(delfile);
    766 		delfile = nil;
    767 	}
    768 
    769 	longjmp(zjmp, 1);
    770 }