tunzip.c - plan9port - [fork] Plan 9 from user space
git clone git://src.adamsgaard.dk/plan9port
Log
Files
Refs
README
LICENSE
---
tunzip.c (14165B)
---
     1 #include 
     2 #include 
     3 #include 
     4 #include 
     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 = "";
   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 }