]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/gzip/unzip.c
webfs(4): document -d and -D flags
[plan9front.git] / sys / src / cmd / gzip / unzip.c
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 [-acistTvD] [-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;
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         off = 0;
544         while(get4(bin) != ZECHeader){
545                 if(ecoff <= 0 || off >= 1024){
546                         fprint(2, "unzip: cannot find end of table of contents in %s\n", file);
547                         longjmp(seekjmp, 1);
548                         return -1;
549                 }
550                 off++;
551                 ecoff--;
552                 Bseek(bin, ecoff, 0);
553         }
554         dn = get2(bin);
555         ds = get2(bin);
556         de = get2(bin);
557         entries = get2(bin);
558         size = get4(bin);
559         off = get4(bin);
560         zclen = get2(bin);
561         while(zclen-- > 0)
562                 get1(bin);
563
564         if(verbose > 1){
565                 print("table starts at %ld for %ld bytes\n", off, size);
566                 if(ecoff - size != off)
567                         print("\ttable should start at %lld-%ld=%lld\n", ecoff, size, ecoff-size);
568                 if(dn || ds || de != entries)
569                         print("\tcurrent disk=%d start disk=%d table entries on this disk=%d\n", dn, ds, de);
570         }
571
572         if(Bseek(bin, off, 0) != off){
573                 fprint(2, "unzip: can't seek to start of contents of %s\n", file);
574                 longjmp(seekjmp, 1);
575                 return -1;
576         }
577
578         return entries;
579 }
580
581 static int
582 cheader(Biobuf *bin, ZipHead *zh)
583 {
584         ulong v;
585         int flen, xlen, fclen;
586
587         v = get4(bin);
588         if(v != ZCHeader){
589                 if(v == ZECHeader)
590                         return 0;
591                 error("bad magic number %lux", v);
592         }
593         zh->madevers = get1(bin);
594         zh->madeos = get1(bin);
595         zh->extvers = get1(bin);
596         zh->extos = get1(bin);
597         zh->flags = get2(bin);
598         zh->meth = get2(bin);
599         zh->modtime = get2(bin);
600         zh->moddate = get2(bin);
601         zh->crc = get4(bin);
602         zh->csize = get4(bin);
603         zh->uncsize = get4(bin);
604         flen = get2(bin);
605         xlen = get2(bin);
606         fclen = get2(bin);
607         get2(bin);              /* disk number start */
608         zh->iattr = get2(bin);
609         zh->eattr = get4(bin);
610         zh->off = get4(bin);
611
612         zh->file = getname(bin, flen);
613
614         while(xlen-- > 0)
615                 get1(bin);
616
617         while(fclen-- > 0)
618                 get1(bin);
619
620         return 1;
621 }
622
623 static int
624 header(Biobuf *bin, ZipHead *zh)
625 {
626         ulong v;
627         int flen, xlen;
628
629         v = get4(bin);
630         if(v != ZHeader){
631                 if(v == ZCHeader)
632                         return 0;
633                 error("bad magic number %lux at %lld", v, Boffset(bin)-4);
634         }
635         zh->extvers = get1(bin);
636         zh->extos = get1(bin);
637         zh->flags = get2(bin);
638         zh->meth = get2(bin);
639         zh->modtime = get2(bin);
640         zh->moddate = get2(bin);
641         zh->crc = get4(bin);
642         zh->csize = get4(bin);
643         zh->uncsize = get4(bin);
644         flen = get2(bin);
645         xlen = get2(bin);
646
647         zh->file = getname(bin, flen);
648
649         while(xlen-- > 0)
650                 get1(bin);
651
652         return 1;
653 }
654
655 static void
656 trailer(Biobuf *bin, ZipHead *zh)
657 {
658         if(zh->flags & ZTrailInfo){
659                 zh->crc = get4(bin);
660                 if(zh->crc == 0x08074b50)       /* thanks apple */
661                         zh->crc = get4(bin);
662                 zh->csize = get4(bin);
663                 zh->uncsize = get4(bin);
664         }
665 }
666
667 static char*
668 getname(Biobuf *bin, int len)
669 {
670         char *s;
671         int i, c;
672
673         s = emalloc(len + 1);
674         for(i = 0; i < len; i++){
675                 c = get1(bin);
676                 if(lower)
677                         c = tolower(c);
678                 s[i] = c;
679         }
680         s[i] = '\0';
681         return s;
682 }
683
684 static int
685 crcwrite(void *out, void *buf, int n)
686 {
687         int fd, nw;
688
689         wlen += n;
690         crc = blockcrc(crctab, crc, buf, n);
691         fd = (int)(uintptr)out;
692         if(fd < 0)
693                 return n;
694         nw = write(fd, buf, n);
695         if(nw != n)
696                 wbad = 1;
697         return nw;
698 }
699
700 static int
701 copyout(int ofd, Biobuf *bin, long len)
702 {
703         char buf[BufSize];
704         int n;
705
706         for(; len > 0; len -= n){
707                 n = len;
708                 if(n > BufSize)
709                         n = BufSize;
710                 n = Bread(bin, buf, n);
711                 if(n <= 0)
712                         return 0;
713                 rlen += n;
714                 if(crcwrite((void*)ofd, buf, n) != n)
715                         return 0;
716         }
717         return 1;
718 }
719
720 static ulong
721 get4(Biobuf *b)
722 {
723         ulong v;
724         int i, c;
725
726         v = 0;
727         for(i = 0; i < 4; i++){
728                 c = Bgetc(b);
729                 if(c < 0)
730                         error("unexpected eof reading file information");
731                 v |= c << (i * 8);
732         }
733         return v;
734 }
735
736 static int
737 get2(Biobuf *b)
738 {
739         int i, c, v;
740
741         v = 0;
742         for(i = 0; i < 2; i++){
743                 c = Bgetc(b);
744                 if(c < 0)
745                         error("unexpected eof reading file information");
746                 v |= c << (i * 8);
747         }
748         return v;
749 }
750
751 static int
752 get1(Biobuf *b)
753 {
754         int c;
755
756         c = Bgetc(b);
757         if(c < 0)
758                 error("unexpected eof reading file information");
759         return c;
760 }
761
762 static long
763 msdos2time(int time, int date)
764 {
765         Tm tm;
766
767         tm.hour = time >> 11;
768         tm.min = (time >> 5) & 63;
769         tm.sec = (time & 31) << 1;
770         tm.year = 80 + (date >> 9);
771         tm.mon = ((date >> 5) & 15) - 1;
772         tm.mday = date & 31;
773         tm.zone[0] = '\0';
774         tm.yday = 0;
775
776         return tm2sec(&tm);
777 }
778
779 static void*
780 emalloc(ulong n)
781 {
782         void *p;
783
784         p = malloc(n);
785         if(p == nil)
786                 sysfatal("out of memory");
787         return p;
788 }
789
790 static void
791 error(char *fmt, ...)
792 {
793         va_list arg;
794
795         fprint(2, "unzip: ");
796         va_start(arg, fmt);
797         vfprint(2, fmt, arg);
798         va_end(arg);
799         fprint(2, "\n");
800
801         if(delfile != nil){
802                 fprint(2, "unzip: removing output file %s\n", delfile);
803                 remove(delfile);
804                 delfile = nil;
805         }
806
807         longjmp(zjmp, 1);
808 }