Subversion Repositories planix.SVN

Rev

Details | Last modification | View Log | RSS feed

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