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 }