diff options
Diffstat (limited to 'store-gzip.c')
-rw-r--r-- | store-gzip.c | 366 |
1 files changed, 366 insertions, 0 deletions
diff --git a/store-gzip.c b/store-gzip.c new file mode 100644 index 000000000..66e479059 --- /dev/null +++ b/store-gzip.c @@ -0,0 +1,366 @@ +/* Gzip store backend. + + Copyright (C) 1995,96,97,99,2000,01, 02 Free Software Foundation, Inc. + Written by Ludovic Courtes <ludovic.courtes@utbm.fr> + This file is part of the GNU Hurd. + + The GNU Hurd is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + The GNU Hurd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */ + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <sys/mman.h> +#include <zlib.h> +#include <error.h> + +#include <hurd.h> +#include <hurd/store.h> + +#include "zipstores.h" + +#ifndef DEBUG_ZIP +# undef DEBUG +#endif +#include "debug.h" + +/* A simple gzip header, inspired by zlib's gzio:check_header (). */ +struct gzip_header +{ + /* Regular header: 10 bytes */ + char magic[2]; + char method; /* Compression method */ + char flags; + char unused[6]; /* time, xflags and OS code */ +}; + +#define GZIP_HEADER_SIZE (sizeof (struct gzip_header)) + +/* Gzip magic header */ +static char gzip_magic[2] = { 0x1f, 0x8b }; + +/* Gzip flag byte */ +#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ +#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */ +#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ +#define ORIG_NAME 0x08 /* bit 3 set: original file name present */ +#define COMMENT 0x10 /* bit 4 set: file comment present */ +#define RESERVED 0xE0 /* bits 5..7: reserved */ + + +static inline error_t gzip_error (z_stream *stream, int zerr); + +static error_t gzip_read_header (struct store *store, + store_offset_t *end_of_header, + struct gzip_header *hdr); + +static error_t gzip_write_header (error_t (* write) (char *buf, size_t amount)); + +static error_t gzip_verify_crc (z_stream *stream, uLong crc); + +static error_t gzip_write_suffix (z_stream *stream, uLong crc, + error_t (* write) (char *buf, size_t amount)); + + +/* The following macros are defined to be then used by the zip store generic + code included below. */ +#define ZIP_TYPE gzip + +#define ZIP_DECOMPRESS(Stream) inflate ((Stream), Z_SYNC_FLUSH) + +/* windowBits is passed < 0 to tell that there is no zlib header. + Note that in this case inflate *requires* an extra "dummy" byte + after the compressed stream in order to complete decompression and + return Z_STREAM_END. Here the gzip CRC32 ensures that 4 bytes are + present after the compressed stream. */ +#define ZIP_DECOMPRESS_INIT(Stream) inflateInit2 ((Stream), -MAX_WBITS) + +#define ZIP_DECOMPRESS_END(Stream) inflateEnd ((Stream)) + +#define ZIP_DECOMPRESS_RESET(Stream) inflateReset ((Stream)) + +#define ZIP_COMPRESS(Stream) deflate ((Stream), Z_NO_FLUSH) + +#define ZIP_COMPRESS_FINISH(Stream) deflate ((Stream), Z_FINISH) + +/* windowBits is passed < 0 to suppress zlib header */ +#define ZIP_COMPRESS_INIT(Stream) deflateInit2 ((Stream), \ + Z_DEFAULT_COMPRESSION, \ + Z_DEFLATED, \ + -MAX_WBITS, \ + 8, \ + Z_DEFAULT_STRATEGY) + +#define ZIP_COMPRESS_END(Stream) deflateEnd ((Stream)) + +#define ZIP_CRC_UPDATE(Crc, Buf, Len) crc32 (Crc, Buf, Len) + +#define ZIP_CRC_VERIFY(Stream, Crc) gzip_verify_crc (Stream, Crc) + +/* Zlib constants */ +#define ZIP_HAS_HEADER +#define ZIP_STREAM z_stream +#define ZIP_STREAM_END Z_STREAM_END + +#include "zipstores.c" + + +/* Convert a zlib error into a libc error. */ +static inline error_t +gzip_error (z_stream *stream, int zerr) +{ + error_t err; + + /* Z_BUF_ERROR should not happen since we try to always maintain + the `next_in' and `next_out' buffers up-to-date. */ + assert (zerr != Z_BUF_ERROR); + + if (stream->msg) + { + error (1, 0, "zlib error: %s", stream->msg); + free (stream->msg); + stream->msg = NULL; + } + + switch (zerr) + { + case Z_OK: + return 0; + case Z_ERRNO: + err = errno; + break; + case Z_MEM_ERROR: + err = ENOMEM; + break; + case Z_VERSION_ERROR: + err = EFTYPE; + break; + case Z_STREAM_ERROR: + err = EINVAL; + break; + case Z_DATA_ERROR: + case Z_STREAM_END: + err = EIO; + break; + default: + err = EAGAIN; /* Shoudn't happen */ + } + + debug (("zlib error: %s", zError (zerr))); + + return err; +} + + +/* Looks for a gzip header in STORE, starting at its beginning. + Returns the position of the first byte available after the header + in END_OF_HEADER, and returns the header read in HEADER. */ +static error_t +gzip_read_header (struct store *store, + store_offset_t *end_of_header, struct gzip_header *hdr) +{ + error_t err; + char buf[ZIP_BUFSIZE]; + char *p = buf; + size_t amount, index = 0; + + /* Load next block and continue */ + static inline + error_t read_next () + { + error_t err; + size_t len; + err = store_simple_read (store, index * ZIP_BUFSIZE, + ZIP_BUFSIZE, buf, &len); + if (err) + return err; + + index++; + + if (len != ZIP_BUFSIZE) + return EIO; + + p = buf; + + return err; + } + + /* Reads from STORE. */ + err = store_simple_read (store, 0, ZIP_BUFSIZE, buf, &amount); + if (err) + return err; + + p += GZIP_HEADER_SIZE; + memcpy (hdr, buf, GZIP_HEADER_SIZE); + *end_of_header = GZIP_HEADER_SIZE; + + debug (("Gzip compression method: 0x%02x", hdr->method)); + + /* Make sure this is a gzip header. */ + if (strncmp (hdr->magic, gzip_magic, sizeof (gzip_magic))) + { + error (0, 0, "Invalid gzip header"); + return EFTYPE; + } + + /* Parse the header and skip unused information. */ + if (hdr->method != Z_DEFLATED || (hdr->flags & RESERVED) != 0) + { + return EIO; /* Z_DATA_ERROR */ + } + + if ((hdr->flags & EXTRA_FIELD) != 0) + { /* skip the extra field */ + size_t size = 0; + size = *(p++); + size += (*(p++)) << 8; + debug (("gzip extra field size: %u", size)); + /* size is garbage if EOF but the loop below will quit anyway */ + while (size--) + if ((p++) - buf >= ZIP_BUFSIZE) + { + err = read_next (); + if (err) + return err; + } + } + if ((hdr->flags & ORIG_NAME) != 0) + { /* skip the original file name */ + debug (("gzip origname: %s", p)); /* XXX: might segfault */ + while (*p) + if ((p++) - buf >= ZIP_BUFSIZE) + { + err = read_next (); + if (err) + return err; + } + + p++; + } + if ((hdr->flags & COMMENT) != 0) + { /* skip the .gz file comment */ + debug (("gzip comment: %s", p)); /* XXX: might segfault */ + while (*p) + if ((p++) - buf >= ZIP_BUFSIZE) + { + err = read_next (); + if (err) + return err; + } + + p++; + } + if ((hdr->flags & HEAD_CRC) != 0) + { /* skip the header crc */ + p += 2; + } + + *end_of_header = p - buf + (index * ZIP_BUFSIZE); + + return 0; +} + +/* Compute a CRC and compare it with the last 4 bytes of the gzip file. */ +static error_t +gzip_verify_crc (z_stream *stream, uLong crc) +{ + error_t err = 0; + uLong read_crc; /* The crc that we read from file */ + Bytef *buf = stream->next_in; + + if (stream->avail_in < 4) + { + error (0, 0, "Unexpected end of gzip file (no CRC)"); + return EIO; + } + + /* Check CRC first */ + read_crc = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + if (read_crc != crc) + { + debug (("Invalid CRC: 0x%lx instead of 0x%lx", read_crc, crc)); + error (0, 0, "Invalid gzip CRC"); + err = EIO; + } + else + debug (("Valid gzip CRC")); + + /* Check uncompressed stream length */ + stream->next_in += 4, stream->avail_in -= 4, + buf = stream->next_in; + if (stream->avail_in < 4) + { + error (0, 0, "Unexpected end of gzip file (no length)"); + return EIO; + } + + read_crc = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + if (read_crc != stream->total_out) + { + debug (("Got length=%lu instead of %lu", read_crc, stream->total_out)); + error (0, 0, "Invalid gzip file length"); + err = EIO; + } + else + debug (("Valid gzip uncompressed stream size (%lu)", read_crc)); + + stream->next_in += 4, stream->avail_in -= 4; + if (stream->avail_in > 0) + { + debug (("%u bytes left", stream->avail_in)); + error (0, 0, "Trailing characters at end of file"); + } + + return err; +} + +/* Write a gzip suffix: CRC (4 bytes) and uncompressed stream length + (4 bytes). WRITE is called to actually write the suffix. Assume that + STREAM is opened for compression. */ +static error_t +gzip_write_suffix (z_stream *stream, uLong crc, + error_t (* write) (char *buf, size_t amount)) +{ + char buf[8]; + size_t total = stream->total_in; + + buf[0] = (crc & 0xff); + buf[1] = (crc >> 8) & 0xff; + buf[2] = (crc >> 16) & 0xff; + buf[3] = (crc >> 24) & 0xff; + buf[4] = (total & 0xff); + buf[5] = (total >> 8) & 0xff; + buf[6] = (total >> 16) & 0xff; + buf[7] = (total >> 24) & 0xff; + + return write (buf, 8); +} + +/* Write a simple gzip header. WRITE is the method called to actually + write the header. Note: The header format being almost + completely undocumented: FIXME. */ +static error_t +gzip_write_header (error_t (* write) (char *buf, size_t amount)) +{ + struct gzip_header hdr; + + bzero (&hdr, GZIP_HEADER_SIZE); + hdr.magic[0] = gzip_magic[0]; + hdr.magic[1] = gzip_magic[1]; + hdr.method = Z_DEFLATED; + + return write ((char *)&hdr, GZIP_HEADER_SIZE); +} |