From ff3adf608207084d1454acddfdb8904bb139c5e4 Mon Sep 17 00:00:00 2001 From: rsc Date: Wed, 14 Apr 2004 20:09:21 +0000 Subject: add gzip, bzip2 ' --- src/cmd/gzip/gunzip.c | 360 ++++++++++++++++++++++++ src/cmd/gzip/gzip.c | 212 +++++++++++++++ src/cmd/gzip/gzip.h | 40 +++ src/cmd/gzip/mkfile | 16 ++ src/cmd/gzip/unzip.c | 740 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/cmd/gzip/zip.c | 398 +++++++++++++++++++++++++++ src/cmd/gzip/zip.h | 83 ++++++ 7 files changed, 1849 insertions(+) create mode 100644 src/cmd/gzip/gunzip.c create mode 100644 src/cmd/gzip/gzip.c create mode 100644 src/cmd/gzip/gzip.h create mode 100644 src/cmd/gzip/mkfile create mode 100644 src/cmd/gzip/unzip.c create mode 100644 src/cmd/gzip/zip.c create mode 100644 src/cmd/gzip/zip.h (limited to 'src/cmd/gzip') diff --git a/src/cmd/gzip/gunzip.c b/src/cmd/gzip/gunzip.c new file mode 100644 index 00000000..691827d3 --- /dev/null +++ b/src/cmd/gzip/gunzip.c @@ -0,0 +1,360 @@ +#include +#include +#include +#include +#include "gzip.h" + +typedef struct GZHead GZHead; + +struct GZHead +{ + ulong mtime; + char *file; +}; + +static int crcwrite(void *bout, void *buf, int n); +static int get1(Biobuf *b); +static ulong get4(Biobuf *b); +static int gunzipf(char *file, int stdout); +static int gunzip(int ofd, char *ofile, Biobuf *bin); +static void header(Biobuf *bin, GZHead *h); +static void trailer(Biobuf *bin, long wlen); +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 vlong gzok; +static char *infile; +static int settimes; +static int table; +static int verbose; +static int wbad; +static ulong wlen; +static jmp_buf zjmp; + +void +usage(void) +{ + fprint(2, "usage: gunzip [-ctvTD] [file ....]\n"); + exits("usage"); +} + +void +main(int argc, char *argv[]) +{ + int i, ok, stdout; + + stdout = 0; + ARGBEGIN{ + case 'D': + debug++; + break; + case 'c': + stdout++; + break; + case 't': + table++; + break; + case 'T': + settimes++; + break; + case 'v': + verbose++; + break; + default: + usage(); + break; + }ARGEND + + crctab = mkcrctab(GZCRCPOLY); + ok = inflateinit(); + if(ok != FlateOk) + sysfatal("inflateinit failed: %s\n", flateerr(ok)); + + if(argc == 0){ + Binit(&bin, 0, OREAD); + settimes = 0; + infile = ""; + ok = gunzip(1, "", &bin); + }else{ + ok = 1; + if(stdout) + settimes = 0; + for(i = 0; i < argc; i++) + ok &= gunzipf(argv[i], stdout); + } + + exits(ok ? nil: "errors"); +} + +static int +gunzipf(char *file, int stdout) +{ + char ofile[256], *s; + int ofd, ifd, ok; + + infile = file; + ifd = open(file, OREAD); + if(ifd < 0){ + fprint(2, "gunzip: can't open %s: %r\n", file); + return 0; + } + + Binit(&bin, ifd, OREAD); + if(Bgetc(&bin) != GZMAGIC1 || Bgetc(&bin) != GZMAGIC2 || Bgetc(&bin) != GZDEFLATE){ + fprint(2, "gunzip: %s is not a gzip deflate file\n", file); + Bterm(&bin); + close(ifd); + return 0; + } + Bungetc(&bin); + Bungetc(&bin); + Bungetc(&bin); + + if(table) + ofd = -1; + else if(stdout){ + ofd = 1; + strcpy(ofile, ""); + }else{ + s = strrchr(file, '/'); + if(s != nil) + s++; + else + s = file; + strecpy(ofile, ofile+sizeof ofile, s); + s = strrchr(ofile, '.'); + if(s != nil && s != ofile && strcmp(s, ".gz") == 0) + *s = '\0'; + else if(s != nil && strcmp(s, ".tgz") == 0) + strcpy(s, ".tar"); + else if(strcmp(file, ofile) == 0){ + fprint(2, "gunzip: can't overwrite %s\n", file); + Bterm(&bin); + close(ifd); + return 0; + } + + ofd = create(ofile, OWRITE, 0666); + if(ofd < 0){ + fprint(2, "gunzip: can't create %s: %r\n", ofile); + Bterm(&bin); + close(ifd); + return 0; + } + delfile = ofile; + } + + wbad = 0; + ok = gunzip(ofd, ofile, &bin); + Bterm(&bin); + close(ifd); + if(wbad){ + fprint(2, "gunzip: can't write %s: %r\n", ofile); + if(delfile) + remove(delfile); + } + delfile = nil; + if(!stdout && ofd >= 0) + close(ofd); + return ok; +} + +static int +gunzip(int ofd, char *ofile, Biobuf *bin) +{ + Dir *d; + GZHead h; + int err; + + h.file = nil; + gzok = 0; + for(;;){ + if(Bgetc(bin) < 0) + return 1; + Bungetc(bin); + + if(setjmp(zjmp)) + return 0; + header(bin, &h); + gzok = 0; + + wlen = 0; + crc = 0; + + if(!table && verbose) + fprint(2, "extracting %s to %s\n", h.file, ofile); + + err = inflate((void*)ofd, crcwrite, bin, (int(*)(void*))Bgetc); + if(err != FlateOk) + error("inflate failed: %s", flateerr(err)); + + trailer(bin, wlen); + + if(table){ + if(verbose) + print("%-32s %10ld %s", h.file, wlen, ctime(h.mtime)); + else + print("%s\n", h.file); + }else if(settimes && h.mtime && (d=dirfstat(ofd)) != nil){ + d->mtime = h.mtime; + dirfwstat(ofd, d); + free(d); + } + + free(h.file); + h.file = nil; + gzok = Boffset(bin); + } + return 0; +} + +static void +header(Biobuf *bin, GZHead *h) +{ + char *s; + int i, c, flag, ns, nsa; + + if(get1(bin) != GZMAGIC1 || get1(bin) != GZMAGIC2) + error("bad gzip file magic"); + if(get1(bin) != GZDEFLATE) + error("unknown compression type"); + + flag = get1(bin); + if(flag & ~(GZFTEXT|GZFEXTRA|GZFNAME|GZFCOMMENT|GZFHCRC)) + fprint(2, "gunzip: reserved flags set, data may not be decompressed correctly\n"); + + /* mod time */ + h->mtime = get4(bin); + + /* extra flags */ + get1(bin); + + /* OS type */ + get1(bin); + + if(flag & GZFEXTRA) + for(i=get1(bin); i>0; i--) + get1(bin); + + /* name */ + if(flag & GZFNAME){ + nsa = 32; + ns = 0; + s = malloc(nsa); + if(s == nil) + error("out of memory"); + while((c = get1(bin)) != 0){ + s[ns++] = c; + if(ns >= nsa){ + nsa += 32; + s = realloc(s, nsa); + if(s == nil) + error("out of memory"); + } + } + s[ns] = '\0'; + h->file = s; + }else + h->file = strdup(""); + + /* comment */ + if(flag & GZFCOMMENT) + while(get1(bin) != 0) + ; + + /* crc16 */ + if(flag & GZFHCRC){ + get1(bin); + get1(bin); + } +} + +static void +trailer(Biobuf *bin, long wlen) +{ + ulong tcrc; + long len; + + tcrc = get4(bin); + if(tcrc != crc) + error("crc mismatch"); + + len = get4(bin); + + if(len != wlen) + error("bad output length: expected %lud got %lud", wlen, len); +} + +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 +get1(Biobuf *b) +{ + int c; + + c = Bgetc(b); + if(c < 0) + error("unexpected eof reading file information"); + return c; +} + +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 void +error(char *fmt, ...) +{ + va_list arg; + + if(gzok) + fprint(2, "gunzip: %s: corrupted data after byte %lld ignored\n", infile, gzok); + else{ + fprint(2, "gunzip: "); + if(infile) + fprint(2, "%s: ", infile); + va_start(arg, fmt); + vfprint(2, fmt, arg); + va_end(arg); + fprint(2, "\n"); + + if(delfile != nil){ + fprint(2, "gunzip: removing output file %s\n", delfile); + remove(delfile); + delfile = nil; + } + } + + longjmp(zjmp, 1); +} diff --git a/src/cmd/gzip/gzip.c b/src/cmd/gzip/gzip.c new file mode 100644 index 00000000..10954d3a --- /dev/null +++ b/src/cmd/gzip/gzip.c @@ -0,0 +1,212 @@ +#include +#include +#include +#include +#include "gzip.h" + +static int gzipf(char*, int); +static int gzip(char*, long, int, Biobuf*); +static int crcread(void *fd, void *buf, int n); +static int gzwrite(void *bout, void *buf, int n); + +static Biobuf bout; +static ulong crc; +static ulong *crctab; +static int debug; +static int eof; +static int level; +static ulong totr; +static int verbose; + +void +usage(void) +{ + fprint(2, "usage: gzip [-vcD] [-1-9] [file ...]\n"); + exits("usage"); +} + +void +main(int argc, char *argv[]) +{ + int i, ok, stdout; + + level = 6; + stdout = 0; + ARGBEGIN{ + case 'D': + debug++; + break; + case 'v': + verbose++; + break; + case 'c': + stdout = 1; + break; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + level = ARGC() - '0'; + break; + default: + usage(); + break; + }ARGEND + + crctab = mkcrctab(GZCRCPOLY); + ok = deflateinit(); + if(ok != FlateOk) + sysfatal("deflateinit failed: %s\n", flateerr(ok)); + + if(argc == 0){ + Binit(&bout, 1, OWRITE); + ok = gzip(nil, time(0), 0, &bout); + Bterm(&bout); + }else{ + ok = 1; + for(i = 0; i < argc; i++) + ok &= gzipf(argv[i], stdout); + } + exits(ok ? nil: "errors"); +} + +static int +gzipf(char *file, int stdout) +{ + Dir *dir; + char ofile[256], *f, *s; + int ifd, ofd, ok; + + ifd = open(file, OREAD); + if(ifd < 0){ + fprint(2, "gzip: can't open %s: %r\n", file); + return 0; + } + dir = dirfstat(ifd); + if(dir == nil){ + fprint(2, "gzip: can't stat %s: %r\n", file); + close(ifd); + return 0; + } + if(dir->mode & DMDIR){ + fprint(2, "gzip: can't compress a directory\n"); + close(ifd); + free(dir); + return 0; + } + + if(stdout){ + ofd = 1; + strcpy(ofile, ""); + }else{ + f = strrchr(file, '/'); + if(f != nil) + f++; + else + f = file; + s = strrchr(f, '.'); + if(s != nil && s != ofile && strcmp(s, ".tar") == 0){ + *s = '\0'; + snprint(ofile, sizeof(ofile), "%s.tgz", f); + }else + snprint(ofile, sizeof(ofile), "%s.gz", f); + ofd = create(ofile, OWRITE, 0666); + if(ofd < 0){ + fprint(2, "gzip: can't open %s: %r\n", ofile); + close(ifd); + return 0; + } + } + + if(verbose) + fprint(2, "compressing %s to %s\n", file, ofile); + + Binit(&bout, ofd, OWRITE); + ok = gzip(file, dir->mtime, ifd, &bout); + if(!ok || Bflush(&bout) < 0){ + fprint(2, "gzip: error writing %s: %r\n", ofile); + if(!stdout) + remove(ofile); + } + Bterm(&bout); + free(dir); + close(ifd); + close(ofd); + return ok; +} + +static int +gzip(char *file, long mtime, int ifd, Biobuf *bout) +{ + int flags, err; + + flags = 0; + Bputc(bout, GZMAGIC1); + Bputc(bout, GZMAGIC2); + Bputc(bout, GZDEFLATE); + + if(file != nil) + flags |= GZFNAME; + Bputc(bout, flags); + + Bputc(bout, mtime); + Bputc(bout, mtime>>8); + Bputc(bout, mtime>>16); + Bputc(bout, mtime>>24); + + Bputc(bout, 0); + Bputc(bout, GZOSINFERNO); + + if(flags & GZFNAME) + Bwrite(bout, file, strlen(file)+1); + + crc = 0; + eof = 0; + totr = 0; + err = deflate(bout, gzwrite, (void*)ifd, crcread, level, debug); + if(err != FlateOk){ + fprint(2, "gzip: deflate failed: %s\n", flateerr(err)); + return 0; + } + + Bputc(bout, crc); + Bputc(bout, crc>>8); + Bputc(bout, crc>>16); + Bputc(bout, crc>>24); + + Bputc(bout, totr); + Bputc(bout, totr>>8); + Bputc(bout, totr>>16); + Bputc(bout, totr>>24); + + return 1; +} + +static int +crcread(void *fd, void *buf, int n) +{ + int nr, m; + + nr = 0; + for(; !eof && n > 0; n -= m){ + m = read((int)fd, (char*)buf+nr, n); + if(m <= 0){ + eof = 1; + if(m < 0) + return -1; + break; + } + nr += m; + } + crc = blockcrc(crctab, crc, buf, nr); + totr += nr; + return nr; +} + +static int +gzwrite(void *bout, void *buf, int n) +{ + if(n != Bwrite(bout, buf, n)){ + eof = 1; + return -1; + } + return n; +} diff --git a/src/cmd/gzip/gzip.h b/src/cmd/gzip/gzip.h new file mode 100644 index 00000000..5b9863b4 --- /dev/null +++ b/src/cmd/gzip/gzip.h @@ -0,0 +1,40 @@ +/* + * gzip header fields + */ +enum +{ + GZMAGIC1 = 0x1f, + GZMAGIC2 = 0x8b, + + GZDEFLATE = 8, + + GZFTEXT = 1 << 0, /* file is text */ + GZFHCRC = 1 << 1, /* crc of header included */ + GZFEXTRA = 1 << 2, /* extra header included */ + GZFNAME = 1 << 3, /* name of file included */ + GZFCOMMENT = 1 << 4, /* header comment included */ + GZFMASK = (1 << 5) -1, /* mask of specified bits */ + + GZXFAST = 2, /* used fast algorithm, little compression */ + GZXBEST = 4, /* used maximum compression algorithm */ + + GZOSFAT = 0, /* FAT file system */ + GZOSAMIGA = 1, /* Amiga */ + GZOSVMS = 2, /* VMS or OpenVMS */ + GZOSUNIX = 3, /* Unix */ + GZOSVMCMS = 4, /* VM/CMS */ + GZOSATARI = 5, /* Atari TOS */ + GZOSHPFS = 6, /* HPFS file system */ + GZOSMAC = 7, /* Macintosh */ + GZOSZSYS = 8, /* Z-System */ + GZOSCPM = 9, /* CP/M */ + GZOSTOPS20 = 10, /* TOPS-20 */ + GZOSNTFS = 11, /* NTFS file system */ + GZOSQDOS = 12, /* QDOS */ + GZOSACORN = 13, /* Acorn RISCOS */ + GZOSUNK = 255, + + GZCRCPOLY = 0xedb88320UL, + + GZOSINFERNO = GZOSUNIX, +}; diff --git a/src/cmd/gzip/mkfile b/src/cmd/gzip/mkfile new file mode 100644 index 00000000..29b8bdb0 --- /dev/null +++ b/src/cmd/gzip/mkfile @@ -0,0 +1,16 @@ +PLAN9=../../.. +<$PLAN9/src/mkhdr + +HFILES=\ + gzip.h\ + zip.h\ + +TARG=\ + gzip\ + gunzip\ + zip\ + unzip\ + +SHORTLIB=flate bio 9 + +<$PLAN9/src/mkmany 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 +#include +#include +#include +#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 = ""; + }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); +} diff --git a/src/cmd/gzip/zip.c b/src/cmd/gzip/zip.c new file mode 100644 index 00000000..0e0d6baa --- /dev/null +++ b/src/cmd/gzip/zip.c @@ -0,0 +1,398 @@ +#include +#include +#include +#include +#include "zip.h" + +enum +{ + HeadAlloc = 64, +}; + +static void zip(Biobuf *bout, char *file, int stdout); +static void zipDir(Biobuf *bout, int fd, ZipHead *zh, int stdout); +static int crcread(void *fd, void *buf, int n); +static int zwrite(void *bout, void *buf, int n); +static void put4(Biobuf *b, ulong v); +static void put2(Biobuf *b, int v); +static void put1(Biobuf *b, int v); +static void header(Biobuf *bout, ZipHead *zh); +static void trailer(Biobuf *bout, ZipHead *zh, vlong off); +static void putCDir(Biobuf *bout); + +static void error(char*, ...); +/* #pragma varargck argpos error 1 */ + +static Biobuf bout; +static ulong crc; +static ulong *crctab; +static int debug; +static int eof; +static int level; +static int nzheads; +static ulong totr; +static ulong totw; +static int verbose; +static int zhalloc; +static ZipHead *zheads; +static jmp_buf zjmp; + +void +usage(void) +{ + fprint(2, "usage: zip [-vD] [-1-9] [-f zipfile] file ...\n"); + exits("usage"); +} + +void +main(int argc, char *argv[]) +{ + char *zfile; + int i, fd, err; + + zfile = nil; + level = 6; + ARGBEGIN{ + case 'D': + debug++; + break; + case 'f': + zfile = ARGF(); + if(zfile == nil) + usage(); + break; + case 'v': + verbose++; + break; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + level = ARGC() - '0'; + break; + default: + usage(); + break; + }ARGEND + + if(argc == 0) + usage(); + + crctab = mkcrctab(ZCrcPoly); + err = deflateinit(); + if(err != FlateOk) + sysfatal("deflateinit failed: %s\n", flateerr(err)); + + if(zfile == nil) + fd = 1; + else{ + fd = create(zfile, OWRITE, 0664); + if(fd < 0) + sysfatal("can't create %s: %r\n", zfile); + } + Binit(&bout, fd, OWRITE); + + if(setjmp(zjmp)){ + if(zfile != nil){ + fprint(2, "zip: removing output file %s\n", zfile); + remove(zfile); + } + exits("errors"); + } + + for(i = 0; i < argc; i++) + zip(&bout, argv[i], zfile == nil); + + putCDir(&bout); + + exits(nil); +} + +static void +zip(Biobuf *bout, char *file, int stdout) +{ + Tm *t; + ZipHead *zh; + Dir *dir; + vlong off; + int fd, err; + + fd = open(file, OREAD); + if(fd < 0) + error("can't open %s: %r", file); + dir = dirfstat(fd); + if(dir == nil) + error("can't stat %s: %r", file); + + /* + * create the header + */ + if(nzheads >= zhalloc){ + zhalloc += HeadAlloc; + zheads = realloc(zheads, zhalloc * sizeof(ZipHead)); + if(zheads == nil) + error("out of memory"); + } + zh = &zheads[nzheads++]; + zh->madeos = ZDos; + zh->madevers = (2 * 10) + 0; + zh->extos = ZDos; + zh->extvers = (2 * 10) + 0; + + t = localtime(dir->mtime); + zh->modtime = (t->hour<<11) | (t->min<<5) | (t->sec>>1); + zh->moddate = ((t->year-80)<<9) | ((t->mon+1)<<5) | t->mday; + + zh->flags = 0; + zh->crc = 0; + zh->csize = 0; + zh->uncsize = 0; + zh->file = strdup(file); + if(zh->file == nil) + error("out of memory"); + zh->iattr = 0; + zh->eattr = ZDArch; + if((dir->mode & 0700) == 0) + zh->eattr |= ZDROnly; + zh->off = Boffset(bout); + + if(dir->mode & DMDIR){ + zh->eattr |= ZDDir; + zh->meth = 0; + zipDir(bout, fd, zh, stdout); + }else{ + zh->meth = 8; + if(stdout) + zh->flags |= ZTrailInfo; + off = Boffset(bout); + header(bout, zh); + + crc = 0; + eof = 0; + totr = 0; + totw = 0; + err = deflate(bout, zwrite, (void*)fd, crcread, level, debug); + if(err != FlateOk) + error("deflate failed: %s: %r", flateerr(err)); + + zh->csize = totw; + zh->uncsize = totr; + zh->crc = crc; + trailer(bout, zh, off); + } + close(fd); + free(dir); +} + +static void +zipDir(Biobuf *bout, int fd, ZipHead *zh, int stdout) +{ + Dir *dirs; + char *file, *pfile; + int i, nf, nd; + + nf = strlen(zh->file) + 1; + if(strcmp(zh->file, ".") == 0){ + nzheads--; + free(zh->file); + pfile = ""; + nf = 1; + }else{ + nf++; + pfile = malloc(nf); + if(pfile == nil) + error("out of memory"); + snprint(pfile, nf, "%s/", zh->file); + free(zh->file); + zh->file = pfile; + header(bout, zh); + } + + nf += 256; /* plenty of room */ + file = malloc(nf); + if(file == nil) + error("out of memory"); + while((nd = dirread(fd, &dirs)) > 0){ + for(i = 0; i < nd; i++){ + snprint(file, nf, "%s%s", pfile, dirs[i].name); + zip(bout, file, stdout); + } + free(dirs); + } +} + +static void +header(Biobuf *bout, ZipHead *zh) +{ + int flen; + + if(verbose) + fprint(2, "adding %s\n", zh->file); + put4(bout, ZHeader); + put1(bout, zh->extvers); + put1(bout, zh->extos); + put2(bout, zh->flags); + put2(bout, zh->meth); + put2(bout, zh->modtime); + put2(bout, zh->moddate); + put4(bout, zh->crc); + put4(bout, zh->csize); + put4(bout, zh->uncsize); + flen = strlen(zh->file); + put2(bout, flen); + put2(bout, 0); + if(Bwrite(bout, zh->file, flen) != flen) + error("write error"); +} + +static void +trailer(Biobuf *bout, ZipHead *zh, vlong off) +{ + vlong coff; + + coff = -1; + if(!(zh->flags & ZTrailInfo)){ + coff = Boffset(bout); + if(Bseek(bout, off + ZHeadCrc, 0) < 0) + error("can't seek in archive"); + } + put4(bout, zh->crc); + put4(bout, zh->csize); + put4(bout, zh->uncsize); + if(!(zh->flags & ZTrailInfo)){ + if(Bseek(bout, coff, 0) < 0) + error("can't seek in archive"); + } +} + +static void +cheader(Biobuf *bout, ZipHead *zh) +{ + int flen; + + put4(bout, ZCHeader); + put1(bout, zh->madevers); + put1(bout, zh->madeos); + put1(bout, zh->extvers); + put1(bout, zh->extos); + put2(bout, zh->flags & ~ZTrailInfo); + put2(bout, zh->meth); + put2(bout, zh->modtime); + put2(bout, zh->moddate); + put4(bout, zh->crc); + put4(bout, zh->csize); + put4(bout, zh->uncsize); + flen = strlen(zh->file); + put2(bout, flen); + put2(bout, 0); + put2(bout, 0); + put2(bout, 0); + put2(bout, zh->iattr); + put4(bout, zh->eattr); + put4(bout, zh->off); + if(Bwrite(bout, zh->file, flen) != flen) + error("write error"); +} + +static void +putCDir(Biobuf *bout) +{ + vlong hoff, ecoff; + int i; + + hoff = Boffset(bout); + + for(i = 0; i < nzheads; i++) + cheader(bout, &zheads[i]); + + ecoff = Boffset(bout); + + if(nzheads >= (1 << 16)) + error("too many entries in zip file: max %d", (1 << 16) - 1); + put4(bout, ZECHeader); + put2(bout, 0); + put2(bout, 0); + put2(bout, nzheads); + put2(bout, nzheads); + put4(bout, ecoff - hoff); + put4(bout, hoff); + put2(bout, 0); +} + +static int +crcread(void *fd, void *buf, int n) +{ + int nr, m; + + nr = 0; + for(; !eof && n > 0; n -= m){ + m = read((int)fd, (char*)buf+nr, n); + if(m <= 0){ + eof = 1; + if(m < 0) +{ +fprint(2, "input error %r\n"); + return -1; +} + break; + } + nr += m; + } + crc = blockcrc(crctab, crc, buf, nr); + totr += nr; + return nr; +} + +static int +zwrite(void *bout, void *buf, int n) +{ + if(n != Bwrite(bout, buf, n)){ + eof = 1; + return -1; + } + totw += n; + return n; +} + +static void +put4(Biobuf *b, ulong v) +{ + int i; + + for(i = 0; i < 4; i++){ + if(Bputc(b, v) < 0) + error("write error"); + v >>= 8; + } +} + +static void +put2(Biobuf *b, int v) +{ + int i; + + for(i = 0; i < 2; i++){ + if(Bputc(b, v) < 0) + error("write error"); + v >>= 8; + } +} + +static void +put1(Biobuf *b, int v) +{ + if(Bputc(b, v)< 0) + error("unexpected eof reading file information"); +} + +static void +error(char *fmt, ...) +{ + va_list arg; + + fprint(2, "zip: "); + va_start(arg, fmt); + vfprint(2, fmt, arg); + va_end(arg); + fprint(2, "\n"); + + longjmp(zjmp, 1); +} diff --git a/src/cmd/gzip/zip.h b/src/cmd/gzip/zip.h new file mode 100644 index 00000000..9b703de2 --- /dev/null +++ b/src/cmd/gzip/zip.h @@ -0,0 +1,83 @@ +typedef struct ZipHead ZipHead; + +enum +{ + /* + * magic numbers + */ + ZHeader = 0x04034b50, + ZCHeader = 0x02014b50, + ZECHeader = 0x06054b50, + + /* + * "general purpose flag" bits + */ + ZEncrypted = 1 << 0, + ZTrailInfo = 1 << 3, /* uncsize, csize, and crc are in trailer */ + ZCompPatch = 1 << 5, /* compression patched data */ + + ZCrcPoly = 0xedb88320, + + /* + * compression method + */ + ZDeflate = 8, + + /* + * internal file attributes + */ + ZIsText = 1 << 0, + + /* + * file attribute interpretation, from high byte of version + */ + ZDos = 0, + ZAmiga = 1, + ZVMS = 2, + ZUnix = 3, + ZVMCMS = 4, + ZAtariST = 5, + ZOS2HPFS = 6, + ZMac = 7, + ZZsys = 8, + ZCPM = 9, + ZNtfs = 10, + + /* + * external attribute flags for ZDos + */ + ZDROnly = 0x01, + ZDHidden = 0x02, + ZDSystem = 0x04, + ZDVLable = 0x08, + ZDDir = 0x10, + ZDArch = 0x20, + + ZHeadSize = 4 + 2 + 2 + 2 + 2 + 2 + 4 + 4 + 4 + 2 + 2, + ZHeadCrc = 4 + 2 + 2 + 2 + 2 + 2, + ZTrailSize = 4 + 4 + 4, + ZCHeadSize = 4 + 2 + 2 + 2 + 2 + 2 + 2 + 4 + 4 + 4 + 2 + 2 + 2 + 2 + 2 + 4 + 4, + ZECHeadSize = 4 + 2 + 2 + 2 + 2 + 4 + 4 + 2, +}; + +/* + * interesting info from a zip header + */ +struct ZipHead +{ + int madeos; /* version made by */ + int madevers; + int extos; /* version needed to extract */ + int extvers; + int flags; /* general purpose bit flag */ + int meth; + int modtime; + int moddate; + ulong crc; + ulong csize; + ulong uncsize; + int iattr; + ulong eattr; + ulong off; + char *file; +}; -- cgit v1.2.3