Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <flate.h>
#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", flateerr(ok));

        if(argc == 0){
                Binit(&bin, 0, OREAD);
                settimes = 0;
                infile = "<stdin>";
                ok = gunzip(1, "<stdout>", &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, "<stdout>");
        }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);
        }
}

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("<unnamed file>");

        /* 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)(uintptr)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);
}