diff options
Diffstat (limited to 'src/cmd/gzip/unzip.c')
-rw-r--r-- | src/cmd/gzip/unzip.c | 740 |
1 files changed, 740 insertions, 0 deletions
diff --git a/src/cmd/gzip/unzip.c b/src/cmd/gzip/unzip.c new file mode 100644 index 00000000..2f7888de --- /dev/null +++ b/src/cmd/gzip/unzip.c @@ -0,0 +1,740 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <flate.h> +#include "zip.h" + +enum +{ + BufSize = 4096 +}; + +static int cheader(Biobuf *bin, ZipHead *zh); +static int copyout(int ofd, Biobuf *bin, long len); +static int crcwrite(void *ofd, void *buf, int n); +static int findCDir(Biobuf *bin, char *file); +static int get1(Biobuf *b); +static int get2(Biobuf *b); +static ulong get4(Biobuf *b); +static char *getname(Biobuf *b, int len); +static int header(Biobuf *bin, ZipHead *zh); +static long msdos2time(int time, int date); +static int sunzip(Biobuf *bin); +static int sunztable(Biobuf *bin); +static void trailer(Biobuf *bin, ZipHead *zh); +static int unzip(Biobuf *bin, char *file); +static int unzipEntry(Biobuf *bin, ZipHead *czh); +static int unztable(Biobuf *bin, char *file); +static int wantFile(char *file); + +static void *emalloc(ulong); +static void error(char*, ...); +/* #pragma varargck argpos error 1 */ + +static Biobuf bin; +static ulong crc; +static ulong *crctab; +static int debug; +static char *delfile; +static int lower; +static int nwant; +static ulong rlen; +static int settimes; +static int stdout; +static int verbose; +static char **want; +static int wbad; +static ulong wlen; +static jmp_buf zjmp; + +static void +usage(void) +{ + fprint(2, "usage: unzip [-tsv] [-f zipfile] [file ...]\n"); + exits("usage"); +} + +void +main(int argc, char *argv[]) +{ + char *zfile; + int fd, ok, table, stream; + + table = 0; + stream = 0; + zfile = nil; + ARGBEGIN{ + case 'D': + debug++; + break; + case 'c': + stdout++; + break; + case 'i': + lower++; + break; + case 'f': + zfile = ARGF(); + if(zfile == nil) + usage(); + break; + case 's': + stream++; + break; + case 't': + table++; + break; + case 'T': + settimes++; + break; + case 'v': + verbose++; + break; + default: + usage(); + break; + }ARGEND + + nwant = argc; + want = argv; + + crctab = mkcrctab(ZCrcPoly); + ok = inflateinit(); + if(ok != FlateOk) + sysfatal("inflateinit failed: %s\n", flateerr(ok)); + + if(zfile == nil){ + Binit(&bin, 0, OREAD); + zfile = "<stdin>"; + }else{ + fd = open(zfile, OREAD); + if(fd < 0) + sysfatal("can't open %s: %r", zfile); + Binit(&bin, fd, OREAD); + } + + if(table){ + if(stream) + ok = sunztable(&bin); + else + ok = unztable(&bin, zfile); + }else{ + if(stream) + ok = sunzip(&bin); + else + ok = unzip(&bin, zfile); + } + + exits(ok ? nil: "errors"); +} + +/* + * print the table of contents from the "central directory structure" + */ +static int +unztable(Biobuf *bin, char *file) +{ + ZipHead zh; + int entries; + + entries = findCDir(bin, file); + if(entries < 0) + return 0; + + if(verbose > 1) + print("%d items in the archive\n", entries); + while(entries-- > 0){ + if(setjmp(zjmp)){ + free(zh.file); + return 0; + } + + memset(&zh, 0, sizeof(zh)); + if(!cheader(bin, &zh)) + return 1; + + if(wantFile(zh.file)){ + if(verbose) + print("%-32s %10lud %s", zh.file, zh.uncsize, ctime(msdos2time(zh.modtime, zh.moddate))); + else + print("%s\n", zh.file); + + if(verbose > 1){ + print("\tmade by os %d vers %d.%d\n", zh.madeos, zh.madevers/10, zh.madevers % 10); + print("\textract by os %d vers %d.%d\n", zh.extos, zh.extvers/10, zh.extvers % 10); + print("\tflags %x\n", zh.flags); + print("\tmethod %d\n", zh.meth); + print("\tmod time %d\n", zh.modtime); + print("\tmod date %d\n", zh.moddate); + print("\tcrc %lux\n", zh.crc); + print("\tcompressed size %lud\n", zh.csize); + print("\tuncompressed size %lud\n", zh.uncsize); + print("\tinternal attributes %ux\n", zh.iattr); + print("\texternal attributes %lux\n", zh.eattr); + print("\tstarts at %ld\n", zh.off); + } + } + + free(zh.file); + zh.file = nil; + } + + return 1; +} + +/* + * print the "local file header" table of contents + */ +static int +sunztable(Biobuf *bin) +{ + ZipHead zh; + vlong off; + ulong hcrc, hcsize, huncsize; + int ok, err; + + ok = 1; + for(;;){ + if(setjmp(zjmp)){ + free(zh.file); + return 0; + } + + memset(&zh, 0, sizeof(zh)); + if(!header(bin, &zh)) + return ok; + + hcrc = zh.crc; + hcsize = zh.csize; + huncsize = zh.uncsize; + + wlen = 0; + rlen = 0; + crc = 0; + wbad = 0; + + if(zh.meth == 0){ + if(!copyout(-1, bin, zh.csize)) + error("reading data for %s failed: %r", zh.file); + }else if(zh.meth == 8){ + off = Boffset(bin); + err = inflate((void*)-1, crcwrite, bin, (int(*)(void*))Bgetc); + if(err != FlateOk) + error("inflate %s failed: %s", zh.file, flateerr(err)); + rlen = Boffset(bin) - off; + }else + error("can't handle compression method %d for %s", zh.meth, zh.file); + + trailer(bin, &zh); + + if(wantFile(zh.file)){ + if(verbose) + print("%-32s %10lud %s", zh.file, zh.uncsize, ctime(msdos2time(zh.modtime, zh.moddate))); + else + print("%s\n", zh.file); + + if(verbose > 1){ + print("\textract by os %d vers %d.%d\n", zh.extos, zh.extvers / 10, zh.extvers % 10); + print("\tflags %x\n", zh.flags); + print("\tmethod %d\n", zh.meth); + print("\tmod time %d\n", zh.modtime); + print("\tmod date %d\n", zh.moddate); + print("\tcrc %lux\n", zh.crc); + print("\tcompressed size %lud\n", zh.csize); + print("\tuncompressed size %lud\n", zh.uncsize); + if((zh.flags & ZTrailInfo) && (hcrc || hcsize || huncsize)){ + print("\theader crc %lux\n", zh.crc); + print("\theader compressed size %lud\n", zh.csize); + print("\theader uncompressed size %lud\n", zh.uncsize); + } + } + } + + if(zh.crc != crc) + error("crc mismatch for %s", zh.file); + if(zh.uncsize != wlen) + error("output size mismatch for %s", zh.file); + if(zh.csize != rlen) + error("input size mismatch for %s", zh.file); + + + free(zh.file); + zh.file = nil; + } + + return ok; +} + +/* + * extract files using the info in the central directory structure + */ +static int +unzip(Biobuf *bin, char *file) +{ + ZipHead zh; + vlong off; + int ok, eok, entries; + + entries = findCDir(bin, file); + if(entries < 0) + return 0; + + ok = 1; + while(entries-- > 0){ + if(setjmp(zjmp)){ + free(zh.file); + return 0; + } + memset(&zh, 0, sizeof(zh)); + if(!cheader(bin, &zh)) + return ok; + + + off = Boffset(bin); + if(wantFile(zh.file)){ + if(Bseek(bin, zh.off, 0) < 0){ + fprint(2, "unzip: can't seek to start of %s, skipping\n", zh.file); + ok = 0; + }else{ + eok = unzipEntry(bin, &zh); + if(eok <= 0){ + fprint(2, "unzip: skipping %s\n", zh.file); + ok = 0; + } + } + } + + free(zh.file); + zh.file = nil; + + if(Bseek(bin, off, 0) < 0){ + fprint(2, "unzip: can't seek to start of next entry, terminating extraction\n"); + return 0; + } + } + + return ok; +} + +/* + * extract files using the info the "local file headers" + */ +static int +sunzip(Biobuf *bin) +{ + int eok; + + for(;;){ + eok = unzipEntry(bin, nil); + if(eok == 0) + return 1; + if(eok < 0) + return 0; + } + + return 1; +} + +/* + * extracts a single entry from a zip file + * czh is the optional corresponding central directory entry + */ +static int +unzipEntry(Biobuf *bin, ZipHead *czh) +{ + Dir *d; + ZipHead zh; + char *p; + vlong off; + int fd, isdir, ok, err; + + zh.file = nil; + if(setjmp(zjmp)){ + delfile = nil; + free(zh.file); + return -1; + } + + memset(&zh, 0, sizeof(zh)); + if(!header(bin, &zh)) + return 0; + + ok = 1; + isdir = 0; + + fd = -1; + if(wantFile(zh.file)){ + if(verbose) + fprint(2, "extracting %s\n", zh.file); + + if(czh != nil && czh->extos == ZDos){ + isdir = czh->eattr & ZDDir; + if(isdir && zh.uncsize != 0) + fprint(2, "unzip: ignoring directory data for %s\n", zh.file); + } + if(zh.meth == 0 && zh.uncsize == 0){ + p = strchr(zh.file, '\0'); + if(p > zh.file && p[-1] == '/') + isdir = 1; + } + + if(stdout){ + if(ok && !isdir) + fd = 1; + }else if(isdir){ + fd = create(zh.file, OREAD, DMDIR | 0775); + if(fd < 0){ + d = dirstat(zh.file); + if(d == nil || (d->mode & DMDIR) != DMDIR){ + fprint(2, "unzip: can't create directory %s: %r\n", zh.file); + ok = 0; + } + free(d); + } + }else if(ok){ + fd = create(zh.file, OWRITE, 0664); + if(fd < 0){ + fprint(2, "unzip: can't create %s: %r\n", zh.file); + ok = 0; + }else + delfile = zh.file; + } + } + + wlen = 0; + rlen = 0; + crc = 0; + wbad = 0; + + if(zh.meth == 0){ + if(!copyout(fd, bin, zh.csize)) + error("copying data for %s failed: %r", zh.file); + }else if(zh.meth == 8){ + off = Boffset(bin); + err = inflate((void*)fd, crcwrite, bin, (int(*)(void*))Bgetc); + if(err != FlateOk) + error("inflate failed: %s", flateerr(err)); + rlen = Boffset(bin) - off; + }else + error("can't handle compression method %d for %s", zh.meth, zh.file); + + trailer(bin, &zh); + + if(zh.crc != crc) + error("crc mismatch for %s", zh.file); + if(zh.uncsize != wlen) + error("output size mismatch for %s", zh.file); + if(zh.csize != rlen) + error("input size mismatch for %s", zh.file); + + delfile = nil; + free(zh.file); + + if(fd >= 0 && !stdout){ + if(settimes){ + d = dirfstat(fd); + if(d != nil){ + d->mtime = msdos2time(zh.modtime, zh.moddate); + if(d->mtime) + dirfwstat(fd, d); + } + } + close(fd); + } + + return ok; +} + +static int +wantFile(char *file) +{ + int i, n; + + if(nwant == 0) + return 1; + for(i = 0; i < nwant; i++){ + if(strcmp(want[i], file) == 0) + return 1; + n = strlen(want[i]); + if(strncmp(want[i], file, n) == 0 && file[n] == '/') + return 1; + } + return 0; +} + +/* + * find the start of the central directory + * returns the number of entries in the directory, + * or -1 if there was an error + */ +static int +findCDir(Biobuf *bin, char *file) +{ + vlong ecoff; + long off, size; + int entries, zclen, dn, ds, de; + + ecoff = Bseek(bin, -ZECHeadSize, 2); + if(ecoff < 0){ + fprint(2, "unzip: can't seek to contents of %s; try adding -s\n", file); + return -1; + } + if(setjmp(zjmp)) + return -1; + + if(get4(bin) != ZECHeader){ + fprint(2, "unzip: bad magic number for contents of %s\n", file); + return -1; + } + dn = get2(bin); + ds = get2(bin); + de = get2(bin); + entries = get2(bin); + size = get4(bin); + off = get4(bin); + zclen = get2(bin); + while(zclen-- > 0) + get1(bin); + + if(verbose > 1){ + print("table starts at %ld for %ld bytes\n", off, size); + if(ecoff - size != off) + print("\ttable should start at %lld-%ld=%lld\n", ecoff, size, ecoff-size); + if(dn || ds || de != entries) + print("\tcurrent disk=%d start disk=%d table entries on this disk=%d\n", dn, ds, de); + } + + if(Bseek(bin, off, 0) != off){ + fprint(2, "unzip: can't seek to start of contents of %s\n", file); + return -1; + } + + return entries; +} + +static int +cheader(Biobuf *bin, ZipHead *zh) +{ + ulong v; + int flen, xlen, fclen; + + v = get4(bin); + if(v != ZCHeader){ + if(v == ZECHeader) + return 0; + error("bad magic number %lux", v); + } + zh->madevers = get1(bin); + zh->madeos = get1(bin); + zh->extvers = get1(bin); + zh->extos = get1(bin); + zh->flags = get2(bin); + zh->meth = get2(bin); + zh->modtime = get2(bin); + zh->moddate = get2(bin); + zh->crc = get4(bin); + zh->csize = get4(bin); + zh->uncsize = get4(bin); + flen = get2(bin); + xlen = get2(bin); + fclen = get2(bin); + get2(bin); /* disk number start */ + zh->iattr = get2(bin); + zh->eattr = get4(bin); + zh->off = get4(bin); + + zh->file = getname(bin, flen); + + while(xlen-- > 0) + get1(bin); + + while(fclen-- > 0) + get1(bin); + + return 1; +} + +static int +header(Biobuf *bin, ZipHead *zh) +{ + ulong v; + int flen, xlen; + + v = get4(bin); + if(v != ZHeader){ + if(v == ZCHeader) + return 0; + error("bad magic number %lux at %lld", v, Boffset(bin)-4); + } + zh->extvers = get1(bin); + zh->extos = get1(bin); + zh->flags = get2(bin); + zh->meth = get2(bin); + zh->modtime = get2(bin); + zh->moddate = get2(bin); + zh->crc = get4(bin); + zh->csize = get4(bin); + zh->uncsize = get4(bin); + flen = get2(bin); + xlen = get2(bin); + + zh->file = getname(bin, flen); + + while(xlen-- > 0) + get1(bin); + + return 1; +} + +static void +trailer(Biobuf *bin, ZipHead *zh) +{ + if(zh->flags & ZTrailInfo){ + zh->crc = get4(bin); + zh->csize = get4(bin); + zh->uncsize = get4(bin); + } +} + +static char* +getname(Biobuf *bin, int len) +{ + char *s; + int i, c; + + s = emalloc(len + 1); + for(i = 0; i < len; i++){ + c = get1(bin); + if(lower) + c = tolower(c); + s[i] = c; + } + s[i] = '\0'; + return s; +} + +static int +crcwrite(void *out, void *buf, int n) +{ + int fd, nw; + + wlen += n; + crc = blockcrc(crctab, crc, buf, n); + fd = (int)out; + if(fd < 0) + return n; + nw = write(fd, buf, n); + if(nw != n) + wbad = 1; + return nw; +} + +static int +copyout(int ofd, Biobuf *bin, long len) +{ + char buf[BufSize]; + int n; + + for(; len > 0; len -= n){ + n = len; + if(n > BufSize) + n = BufSize; + n = Bread(bin, buf, n); + if(n <= 0) + return 0; + rlen += n; + if(crcwrite((void*)ofd, buf, n) != n) + return 0; + } + return 1; +} + +static ulong +get4(Biobuf *b) +{ + ulong v; + int i, c; + + v = 0; + for(i = 0; i < 4; i++){ + c = Bgetc(b); + if(c < 0) + error("unexpected eof reading file information"); + v |= c << (i * 8); + } + return v; +} + +static int +get2(Biobuf *b) +{ + int i, c, v; + + v = 0; + for(i = 0; i < 2; i++){ + c = Bgetc(b); + if(c < 0) + error("unexpected eof reading file information"); + v |= c << (i * 8); + } + return v; +} + +static int +get1(Biobuf *b) +{ + int c; + + c = Bgetc(b); + if(c < 0) + error("unexpected eof reading file information"); + return c; +} + +static long +msdos2time(int time, int date) +{ + Tm tm; + + tm.hour = time >> 11; + tm.min = (time >> 5) & 63; + tm.sec = (time & 31) << 1; + tm.year = 80 + (date >> 9); + tm.mon = ((date >> 5) & 15) - 1; + tm.mday = date & 31; + tm.zone[0] = '\0'; + tm.yday = 0; + + return tm2sec(&tm); +} + +static void* +emalloc(ulong n) +{ + void *p; + + p = malloc(n); + if(p == nil) + sysfatal("out of memory"); + return p; +} + +static void +error(char *fmt, ...) +{ + va_list arg; + + fprint(2, "unzip: "); + va_start(arg, fmt); + vfprint(2, fmt, arg); + va_end(arg); + fprint(2, "\n"); + + if(delfile != nil){ + fprint(2, "unzip: removing output file %s\n", delfile); + remove(delfile); + delfile = nil; + } + + longjmp(zjmp, 1); +} |