diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:54:16 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:54:16 +0000 |
commit | 485f6ecd453d8a2fd8b9b9fadea03159d8b50797 (patch) | |
tree | 32451fa3cdd9321fb2591fada9891b2cb70a9cd1 /grub-core/io | |
parent | Initial commit. (diff) | |
download | grub2-upstream.tar.xz grub2-upstream.zip |
Adding upstream version 2.06.upstream/2.06upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'grub-core/io')
-rw-r--r-- | grub-core/io/bufio.c | 214 | ||||
-rw-r--r-- | grub-core/io/gzio.c | 1444 | ||||
-rw-r--r-- | grub-core/io/lzopio.c | 546 | ||||
-rw-r--r-- | grub-core/io/offset.c | 112 | ||||
-rw-r--r-- | grub-core/io/xzio.c | 346 |
5 files changed, 2662 insertions, 0 deletions
diff --git a/grub-core/io/bufio.c b/grub-core/io/bufio.c new file mode 100644 index 0000000..a458c3a --- /dev/null +++ b/grub-core/io/bufio.c @@ -0,0 +1,214 @@ +/* bufio.c - buffered io access */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 Free Software Foundation, Inc. + * + * GRUB 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 3 of the License, or + * (at your option) any later version. + * + * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/err.h> +#include <grub/types.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/fs.h> +#include <grub/bufio.h> +#include <grub/dl.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define GRUB_BUFIO_DEF_SIZE 8192 +#define GRUB_BUFIO_MAX_SIZE 1048576 + +struct grub_bufio +{ + grub_file_t file; + grub_size_t block_size; + grub_size_t buffer_len; + grub_off_t buffer_at; + char buffer[0]; +}; +typedef struct grub_bufio *grub_bufio_t; + +static struct grub_fs grub_bufio_fs; + +grub_file_t +grub_bufio_open (grub_file_t io, grub_size_t size) +{ + grub_file_t file; + grub_bufio_t bufio = 0; + + file = (grub_file_t) grub_zalloc (sizeof (*file)); + if (! file) + return 0; + + if (size == 0) + size = GRUB_BUFIO_DEF_SIZE; + else if (size > GRUB_BUFIO_MAX_SIZE) + size = GRUB_BUFIO_MAX_SIZE; + + if (size > io->size) + size = ((io->size > GRUB_BUFIO_MAX_SIZE) ? GRUB_BUFIO_MAX_SIZE : + io->size); + + /* + * Round up size to power of 2 which the binary math to + * calculate next_buf in grub_bufio_read() requires. + */ + while (size & (size - 1)) + size = (size | (size - 1)) + 1; + + bufio = grub_zalloc (sizeof (struct grub_bufio) + size); + if (! bufio) + { + grub_free (file); + return 0; + } + + bufio->file = io; + bufio->block_size = size; + + file->device = io->device; + file->size = io->size; + file->data = bufio; + file->fs = &grub_bufio_fs; + file->not_easily_seekable = io->not_easily_seekable; + + return file; +} + +grub_file_t +grub_buffile_open (const char *name, enum grub_file_type type, grub_size_t size) +{ + grub_file_t io, file; + + io = grub_file_open (name, type); + if (! io) + return 0; + + file = grub_bufio_open (io, size); + if (! file) + { + grub_file_close (io); + return 0; + } + + return file; +} + +static grub_ssize_t +grub_bufio_read (grub_file_t file, char *buf, grub_size_t len) +{ + grub_size_t res = 0; + grub_off_t next_buf; + grub_bufio_t bufio = file->data; + grub_ssize_t really_read; + + if (file->size == GRUB_FILE_SIZE_UNKNOWN) + file->size = bufio->file->size; + + /* First part: use whatever we already have in the buffer. */ + if ((file->offset >= bufio->buffer_at) && + (file->offset < bufio->buffer_at + bufio->buffer_len)) + { + grub_size_t n; + grub_uint64_t pos; + + pos = file->offset - bufio->buffer_at; + n = bufio->buffer_len - pos; + if (n > len) + n = len; + + grub_memcpy (buf, &bufio->buffer[pos], n); + len -= n; + res += n; + + buf += n; + } + if (len == 0) + return res; + + /* Need to read some more. */ + next_buf = (file->offset + res + len - 1) & ~((grub_off_t) bufio->block_size - 1); + /* Now read between file->offset + res and bufio->buffer_at. */ + if (file->offset + res < next_buf) + { + grub_size_t read_now; + read_now = next_buf - (file->offset + res); + grub_file_seek (bufio->file, file->offset + res); + really_read = grub_file_read (bufio->file, buf, read_now); + if (really_read < 0) + return -1; + if (file->size == GRUB_FILE_SIZE_UNKNOWN) + file->size = bufio->file->size; + len -= really_read; + buf += really_read; + res += really_read; + + /* Partial read. File ended unexpectedly. Save the last chunk in buffer. + */ + if (really_read != (grub_ssize_t) read_now) + { + bufio->buffer_len = really_read; + if (bufio->buffer_len > bufio->block_size) + bufio->buffer_len = bufio->block_size; + bufio->buffer_at = file->offset + res - bufio->buffer_len; + grub_memcpy (&bufio->buffer[0], buf - bufio->buffer_len, + bufio->buffer_len); + return res; + } + } + + /* Read into buffer. */ + grub_file_seek (bufio->file, next_buf); + really_read = grub_file_read (bufio->file, bufio->buffer, + bufio->block_size); + if (really_read < 0) + return -1; + bufio->buffer_at = next_buf; + bufio->buffer_len = really_read; + + if (file->size == GRUB_FILE_SIZE_UNKNOWN) + file->size = bufio->file->size; + + if (len > bufio->buffer_len) + len = bufio->buffer_len; + grub_memcpy (buf, &bufio->buffer[file->offset + res - next_buf], len); + res += len; + + return res; +} + +static grub_err_t +grub_bufio_close (grub_file_t file) +{ + grub_bufio_t bufio = file->data; + + grub_file_close (bufio->file); + grub_free (bufio); + + file->device = 0; + + return grub_errno; +} + +static struct grub_fs grub_bufio_fs = + { + .name = "bufio", + .fs_dir = 0, + .fs_open = 0, + .fs_read = grub_bufio_read, + .fs_close = grub_bufio_close, + .fs_label = 0, + .next = 0 + }; diff --git a/grub-core/io/gzio.c b/grub-core/io/gzio.c new file mode 100644 index 0000000..aea86a0 --- /dev/null +++ b/grub-core/io/gzio.c @@ -0,0 +1,1444 @@ +/* gzio.c - decompression support for gzip */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2005,2006,2007,2009 Free Software Foundation, Inc. + * + * GRUB 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 3 of the License, or + * (at your option) any later version. + * + * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Most of this file was originally the source file "inflate.c", written + * by Mark Adler. It has been very heavily modified. In particular, the + * original would run through the whole file at once, and this version can + * be stopped and restarted on any boundary during the decompression process. + * + * The license and header comments that file are included here. + */ + +/* inflate.c -- Not copyrighted 1992 by Mark Adler + version c10p1, 10 January 1993 */ + +/* You can do whatever you like with this source file, though I would + prefer that if you modify it and redistribute it that you include + comments to that effect with your name and the date. Thank you. + */ + +#include <grub/err.h> +#include <grub/types.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/fs.h> +#include <grub/file.h> +#include <grub/dl.h> +#include <grub/deflate.h> +#include <grub/i18n.h> +#include <grub/crypto.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* + * Window Size + * + * This must be a power of two, and at least 32K for zip's deflate method + */ + +#define WSIZE 0x8000 + + +#define INBUFSIZ 0x2000 + +/* The state stored in filesystem-specific data. */ +struct grub_gzio +{ + /* The underlying file object. */ + grub_file_t file; + /* If input is in memory following fields are used instead of file. */ + grub_size_t mem_input_size, mem_input_off; + grub_uint8_t *mem_input; + /* The offset at which the data starts in the underlying file. */ + grub_off_t data_offset; + /* The type of current block. */ + int block_type; + /* The length of current block. */ + int block_len; + /* The flag of the last block. */ + int last_block; + /* The flag of codes. */ + int code_state; + /* The length of a copy. */ + unsigned inflate_n; + /* The index of a copy. */ + unsigned inflate_d; + /* The input buffer. */ + grub_uint8_t inbuf[INBUFSIZ]; + int inbuf_d; + /* The bit buffer. */ + unsigned long bb; + /* The bits in the bit buffer. */ + unsigned bk; + /* The sliding window in uncompressed data. */ + grub_uint8_t slide[WSIZE]; + /* Current position in the slide. */ + unsigned wp; + /* The literal/length code table. */ + struct huft *tl; + /* The distance code table. */ + struct huft *td; + /* The checksum algorithm */ + const gcry_md_spec_t *hdesc; + /* The wanted checksum */ + grub_uint32_t orig_checksum; + /* The uncompressed length */ + grub_size_t orig_len; + /* Context for checksum calculation */ + grub_uint8_t *hcontext; + /* The lookup bits for the literal/length code table. */ + int bl; + /* The lookup bits for the distance code table. */ + int bd; + /* The original offset value. */ + grub_off_t saved_offset; +}; +typedef struct grub_gzio *grub_gzio_t; + +/* Declare the filesystem structure for grub_gzio_open. */ +static struct grub_fs grub_gzio_fs; + +/* Function prototypes */ +static void initialize_tables (grub_gzio_t); + +/* Eat variable-length header fields. */ +static int +eat_field (grub_file_t file, int len) +{ + char ch = 1; + int not_retval = 1; + + do + { + if (len >= 0) + { + if (! (len--)) + break; + } + else + { + if (! ch) + break; + } + } + while ((not_retval = grub_file_read (file, &ch, 1)) == 1); + + return ! not_retval; +} + + +/* Little-Endian defines for the 2-byte magic numbers for gzip files. */ +#define GZIP_MAGIC grub_le_to_cpu16 (0x8B1F) +#define OLD_GZIP_MAGIC grub_le_to_cpu16 (0x9E1F) + +/* Compression methods (see algorithm.doc) */ +#define GRUB_GZ_STORED 0 +#define GRUB_GZ_COMPRESSED 1 +#define GRUB_GZ_PACKED 2 +#define GRUB_GZ_LZHED 3 +/* methods 4 to 7 reserved */ +#define GRUB_GZ_DEFLATED 8 +#define GRUB_GZ_MAX_METHODS 9 + +/* gzip flag byte */ +#define GRUB_GZ_ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ +#define GRUB_GZ_CONTINUATION 0x02 /* bit 1 set: continuation of multi-part gzip file */ +#define GRUB_GZ_EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ +#define GRUB_GZ_ORIG_NAME 0x08 /* bit 3 set: original file name present */ +#define GRUB_GZ_COMMENT 0x10 /* bit 4 set: file comment present */ +#define GRUB_GZ_ENCRYPTED 0x20 /* bit 5 set: file is encrypted */ +#define GRUB_GZ_RESERVED 0xC0 /* bit 6,7: reserved */ + +#define GRUB_GZ_UNSUPPORTED_FLAGS (GRUB_GZ_CONTINUATION | GRUB_GZ_ENCRYPTED | GRUB_GZ_RESERVED) + +/* inflate block codes */ +#define INFLATE_STORED 0 +#define INFLATE_FIXED 1 +#define INFLATE_DYNAMIC 2 + +typedef unsigned char uch; +typedef unsigned short ush; +typedef unsigned long ulg; + +static int +test_gzip_header (grub_file_t file) +{ + struct { + grub_uint16_t magic; + grub_uint8_t method; + grub_uint8_t flags; + grub_uint32_t timestamp; + grub_uint8_t extra_flags; + grub_uint8_t os_type; + } hdr; + grub_uint16_t extra_len; + grub_uint32_t crc32; + grub_gzio_t gzio = file->data; + + if (grub_file_tell (gzio->file) != 0) + grub_file_seek (gzio->file, 0); + + /* + * This checks if the file is gzipped. If a problem occurs here + * (other than a real error with the disk) then we don't think it + * is a compressed file, and simply mark it as such. + */ + if (grub_file_read (gzio->file, &hdr, 10) != 10 + || ((hdr.magic != GZIP_MAGIC) + && (hdr.magic != OLD_GZIP_MAGIC))) + return 0; + + /* + * This does consistency checking on the header data. If a + * problem occurs from here on, then we have corrupt or otherwise + * bad data, and the error should be reported to the user. + */ + if (hdr.method != GRUB_GZ_DEFLATED + || (hdr.flags & GRUB_GZ_UNSUPPORTED_FLAGS) + || ((hdr.flags & GRUB_GZ_EXTRA_FIELD) + && (grub_file_read (gzio->file, &extra_len, 2) != 2 + || eat_field (gzio->file, + grub_le_to_cpu16 (extra_len)))) + || ((hdr.flags & GRUB_GZ_ORIG_NAME) && eat_field (gzio->file, -1)) + || ((hdr.flags & GRUB_GZ_COMMENT) && eat_field (gzio->file, -1))) + return 0; + + gzio->data_offset = grub_file_tell (gzio->file); + + /* FIXME: don't do this on not easily seekable files. */ + { + grub_file_seek (gzio->file, grub_file_size (gzio->file) - 8); + if (grub_file_read (gzio->file, &crc32, 4) != 4) + return 0; + gzio->orig_checksum = grub_le_to_cpu32 (crc32); + if (grub_file_read (gzio->file, &gzio->orig_len, 4) != 4) + return 0; + /* FIXME: this does not handle files whose original size is over 4GB. + But how can we know the real original size? */ + file->size = grub_le_to_cpu32 (gzio->orig_len); + } + + initialize_tables (gzio); + + return 1; +} + + +/* Huffman code lookup table entry--this entry is four bytes for machines + that have 16-bit pointers (e.g. PC's in the small or medium model). + Valid extra bits are 0..13. e == 15 is EOB (end of block), e == 16 + means that v is a literal, 16 < e < 32 means that v is a pointer to + the next table, which codes e - 16 bits, and lastly e == 99 indicates + an unused code. If a code with e == 99 is looked up, this implies an + error in the data. */ +struct huft +{ + uch e; /* number of extra bits or operation */ + uch b; /* number of bits in this code or subcode */ + union + { + ush n; /* literal, length base, or distance base */ + struct huft *t; /* pointer to next level of table */ + } + v; +}; + + +/* The inflate algorithm uses a sliding 32K byte window on the uncompressed + stream to find repeated byte strings. This is implemented here as a + circular buffer. The index is updated simply by incrementing and then + and'ing with 0x7fff (32K-1). */ +/* It is left to other modules to supply the 32K area. It is assumed + to be usable as if it were declared "uch slide[32768];" or as just + "uch *slide;" and then malloc'ed in the latter case. The definition + must be in unzip.h, included above. */ + + +/* Tables for deflate from PKZIP's appnote.txt. */ +static unsigned bitorder[] = +{ /* Order of the bit length code lengths */ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; +static ush cplens[] = +{ /* Copy lengths for literal codes 257..285 */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; + /* note: see note #13 above about the 258 in this list. */ +static ush cplext[] = +{ /* Extra bits for literal codes 257..285 */ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99}; /* 99==invalid */ +static ush cpdist[] = +{ /* Copy offsets for distance codes 0..29 */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577}; +static ush cpdext[] = +{ /* Extra bits for distance codes */ + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13}; + + +/* + Huffman code decoding is performed using a multi-level table lookup. + The fastest way to decode is to simply build a lookup table whose + size is determined by the longest code. However, the time it takes + to build this table can also be a factor if the data being decoded + is not very long. The most common codes are necessarily the + shortest codes, so those codes dominate the decoding time, and hence + the speed. The idea is you can have a shorter table that decodes the + shorter, more probable codes, and then point to subsidiary tables for + the longer codes. The time it costs to decode the longer codes is + then traded against the time it takes to make longer tables. + + This results of this trade are in the variables lbits and dbits + below. lbits is the number of bits the first level table for literal/ + length codes can decode in one step, and dbits is the same thing for + the distance codes. Subsequent tables are also less than or equal to + those sizes. These values may be adjusted either when all of the + codes are shorter than that, in which case the longest code length in + bits is used, or when the shortest code is *longer* than the requested + table size, in which case the length of the shortest code in bits is + used. + + There are two different values for the two tables, since they code a + different number of possibilities each. The literal/length table + codes 286 possible values, or in a flat code, a little over eight + bits. The distance table codes 30 possible values, or a little less + than five bits, flat. The optimum values for speed end up being + about one bit more than those, so lbits is 8+1 and dbits is 5+1. + The optimum values may differ though from machine to machine, and + possibly even between compilers. Your mileage may vary. + */ + + +static int lbits = 9; /* bits in base literal/length lookup table */ +static int dbits = 6; /* bits in base distance lookup table */ + + +/* If BMAX needs to be larger than 16, then h and x[] should be ulg. */ +#define BMAX 16 /* maximum bit length of any code (16 for explode) */ +#define N_MAX 288 /* maximum number of codes in any set */ + + +/* Macros for inflate() bit peeking and grabbing. + The usage is: + + NEEDBITS(j) + x = b & mask_bits[j]; + DUMPBITS(j) + + where NEEDBITS makes sure that b has at least j bits in it, and + DUMPBITS removes the bits from b. The macros use the variable k + for the number of bits in b. Normally, b and k are register + variables for speed, and are initialized at the beginning of a + routine that uses these macros from a global bit buffer and count. + + If we assume that EOB will be the longest code, then we will never + ask for bits with NEEDBITS that are beyond the end of the stream. + So, NEEDBITS should not read any more bytes than are needed to + meet the request. Then no bytes need to be "returned" to the buffer + at the end of the last block. + + However, this assumption is not true for fixed blocks--the EOB code + is 7 bits, but the other literal/length codes can be 8 or 9 bits. + (The EOB code is shorter than other codes because fixed blocks are + generally short. So, while a block always has an EOB, many other + literal/length codes have a significantly lower probability of + showing up at all.) However, by making the first table have a + lookup of seven bits, the EOB code will be found in that first + lookup, and so will not require that too many bits be pulled from + the stream. + */ + +static ush mask_bits[] = +{ + 0x0000, + 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, + 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff +}; + +#define NEEDBITS(n) do {while(k<(n)){b|=((ulg)get_byte(gzio))<<k;k+=8;}} while (0) +#define DUMPBITS(n) do {b>>=(n);k-=(n);} while (0) + +static int +get_byte (grub_gzio_t gzio) +{ + if (gzio->mem_input) + { + if (gzio->mem_input_off < gzio->mem_input_size) + return gzio->mem_input[gzio->mem_input_off++]; + return 0; + } + + if (gzio->file && (grub_file_tell (gzio->file) + == (grub_off_t) gzio->data_offset + || gzio->inbuf_d == INBUFSIZ)) + { + gzio->inbuf_d = 0; + grub_file_read (gzio->file, gzio->inbuf, INBUFSIZ); + } + + return gzio->inbuf[gzio->inbuf_d++]; +} + +static void +gzio_seek (grub_gzio_t gzio, grub_off_t off) +{ + if (gzio->mem_input) + { + if (off > gzio->mem_input_size) + grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("attempt to seek outside of the file")); + else + gzio->mem_input_off = off; + } + else + grub_file_seek (gzio->file, off); +} + +/* more function prototypes */ +static int huft_build (unsigned *, unsigned, unsigned, ush *, ush *, + struct huft **, int *); +static int huft_free (struct huft *); +static int inflate_codes_in_window (grub_gzio_t); + + +/* Given a list of code lengths and a maximum table size, make a set of + tables to decode that set of codes. Return zero on success, one if + the given code set is incomplete (the tables are still built in this + case), two if the input is invalid (all zero length codes or an + oversubscribed set of lengths), and three if not enough memory. */ + +static int +huft_build (unsigned *b, /* code lengths in bits (all assumed <= BMAX) */ + unsigned n, /* number of codes (assumed <= N_MAX) */ + unsigned s, /* number of simple-valued codes (0..s-1) */ + ush * d, /* list of base values for non-simple codes */ + ush * e, /* list of extra bits for non-simple codes */ + struct huft **t, /* result: starting table */ + int *m) /* maximum lookup bits, returns actual */ +{ + unsigned a; /* counter for codes of length k */ + unsigned c[BMAX + 1]; /* bit length count table */ + unsigned f; /* i repeats in table every f entries */ + int g; /* maximum code length */ + int h; /* table level */ + register unsigned i; /* counter, current code */ + register unsigned j; /* counter */ + register int k; /* number of bits in current code */ + int l; /* bits per table (returned in m) */ + register unsigned *p; /* pointer into c[], b[], or v[] */ + register struct huft *q; /* points to current table */ + struct huft r; /* table entry for structure assignment */ + struct huft *u[BMAX]; /* table stack */ + unsigned v[N_MAX]; /* values in order of bit length */ + register int w; /* bits before this table == (l * h) */ + unsigned x[BMAX + 1]; /* bit offsets, then code stack */ + unsigned *xp; /* pointer into x */ + int y; /* number of dummy codes added */ + unsigned z; /* number of entries in current table */ + + /* Generate counts for each bit length */ + grub_memset ((char *) c, 0, sizeof (c)); + p = b; + i = n; + do + { + c[*p]++; /* assume all entries <= BMAX */ + p++; /* Can't combine with above line (Solaris bug) */ + } + while (--i); + if (c[0] == n) /* null input--all zero length codes */ + { + *t = (struct huft *) NULL; + *m = 0; + return 0; + } + + /* Find minimum and maximum length, bound *m by those */ + l = *m; + for (j = 1; j <= BMAX; j++) + if (c[j]) + break; + k = j; /* minimum code length */ + if ((unsigned) l < j) + l = j; + for (i = BMAX; i; i--) + if (c[i]) + break; + g = i; /* maximum code length */ + if ((unsigned) l > i) + l = i; + *m = l; + + /* Adjust last length count to fill out codes, if needed */ + for (y = 1 << j; j < i; j++, y <<= 1) + if ((y -= c[j]) < 0) + return 2; /* bad input: more codes than bits */ + if ((y -= c[i]) < 0) + return 2; + c[i] += y; + + /* Generate starting offsets into the value table for each length */ + x[1] = j = 0; + p = c + 1; + xp = x + 2; + while (--i) + { /* note that i == g from above */ + *xp++ = (j += *p++); + } + + /* Make a table of values in order of bit lengths */ + grub_memset (v, N_MAX, ARRAY_SIZE (v)); + p = b; + i = 0; + do + { + if ((j = *p++) != 0) + v[x[j]++] = i; + } + while (++i < n); + + /* Generate the Huffman codes and for each, make the table entries */ + x[0] = i = 0; /* first Huffman code is zero */ + p = v; /* grab values in bit order */ + h = -1; /* no tables yet--level -1 */ + w = -l; /* bits decoded == (l * h) */ + u[0] = (struct huft *) NULL; /* just to keep compilers happy */ + q = (struct huft *) NULL; /* ditto */ + z = 0; /* ditto */ + + /* go through the bit lengths (k already is bits in shortest code) */ + for (; k <= g; k++) + { + a = c[k]; + while (a--) + { + /* here i is the Huffman code of length k bits for value *p */ + /* make tables up to required level */ + while (k > w + l) + { + h++; + w += l; /* previous table always l bits */ + + /* compute minimum size table less than or equal to l bits */ + z = (z = (unsigned) (g - w)) > (unsigned) l ? (unsigned) l : z; /* upper limit on table size */ + if ((f = 1 << (j = k - w)) > a + 1) /* try a k-w bit table */ + { /* too few codes for k-w bit table */ + f -= a + 1; /* deduct codes from patterns left */ + xp = c + k; + while (++j < z) /* try smaller tables up to z bits */ + { + if ((f <<= 1) <= *++xp) + break; /* enough codes to use up j bits */ + f -= *xp; /* else deduct codes from patterns */ + } + } + z = 1 << j; /* table entries for j-bit table */ + + /* allocate and link in new table */ + q = (struct huft *) grub_calloc (z + 1, sizeof (struct huft)); + if (! q) + { + if (h) + huft_free (u[0]); + return 3; + } + + *t = q + 1; /* link to list for huft_free() */ + *(t = &(q->v.t)) = (struct huft *) NULL; + u[h] = ++q; /* table starts after link */ + + /* connect to last table, if there is one */ + if (h) + { + x[h] = i; /* save pattern for backing up */ + r.b = (uch) l; /* bits to dump before this table */ + r.e = (uch) (16 + j); /* bits in this table */ + r.v.t = q; /* pointer to this table */ + j = i >> (w - l); /* (get around Turbo C bug) */ + u[h - 1][j] = r; /* connect to last table */ + } + } + + /* set up table entry in r */ + r.b = (uch) (k - w); + if (p >= v + n) + r.e = 99; /* out of values--invalid code */ + else if (*p < s) + { + r.e = (uch) (*p < 256 ? 16 : 15); /* 256 is end-of-block code */ + r.v.n = (ush) (*p); /* simple code is just the value */ + p++; /* one compiler does not like *p++ */ + } + else if (*p < N_MAX) + { + r.e = (uch) e[*p - s]; /* non-simple--look up in lists */ + r.v.n = d[*p++ - s]; + } + else + { + /* Detected an uninitialised value, abort. */ + if (h) + huft_free (u[0]); + return 2; + } + + /* fill code-like entries with r */ + f = 1 << (k - w); + for (j = i >> w; j < z; j += f) + q[j] = r; + + /* backwards increment the k-bit code i */ + for (j = 1 << (k - 1); i & j; j >>= 1) + i ^= j; + i ^= j; + + /* backup over finished tables */ + while ((i & ((1 << w) - 1)) != x[h]) + { + h--; /* don't need to update q */ + w -= l; + } + } + } + + /* Return true (1) if we were given an incomplete table */ + return y != 0 && g != 1; +} + + +/* Free the malloc'ed tables built by huft_build(), which makes a linked + list of the tables it made, with the links in a dummy first entry of + each table. */ +static int +huft_free (struct huft *t) +{ + register struct huft *p, *q; + + + /* Go through linked list, freeing from the malloced (t[-1]) address. */ + p = t; + while (p != (struct huft *) NULL) + { + q = (--p)->v.t; + grub_free ((char *) p); + p = q; + } + return 0; +} + + +/* + * inflate (decompress) the codes in a deflated (compressed) block. + * Return an error code or zero if it all goes ok. + */ + +static int +inflate_codes_in_window (grub_gzio_t gzio) +{ + register unsigned e; /* table entry flag/number of extra bits */ + unsigned n, d; /* length and index for copy */ + unsigned w; /* current window position */ + struct huft *t; /* pointer to table entry */ + unsigned ml, md; /* masks for bl and bd bits */ + register ulg b; /* bit buffer */ + register unsigned k; /* number of bits in bit buffer */ + + /* make local copies of globals */ + d = gzio->inflate_d; + n = gzio->inflate_n; + b = gzio->bb; /* initialize bit buffer */ + k = gzio->bk; + w = gzio->wp; /* initialize window position */ + + /* inflate the coded data */ + ml = mask_bits[gzio->bl]; /* precompute masks for speed */ + md = mask_bits[gzio->bd]; + for (;;) /* do until end of block */ + { + if (! gzio->code_state) + { + + if (gzio->tl == NULL) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "NULL gzio->tl"); + return 1; + } + + NEEDBITS ((unsigned) gzio->bl); + if ((e = (t = gzio->tl + ((unsigned) b & ml))->e) > 16) + do + { + if (e == 99) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + "an unused code found"); + return 1; + } + DUMPBITS (t->b); + e -= 16; + NEEDBITS (e); + } + while ((e = (t = t->v.t + ((unsigned) b & mask_bits[e]))->e) > 16); + DUMPBITS (t->b); + + if (e == 16) /* then it's a literal */ + { + gzio->slide[w++] = (uch) t->v.n; + if (w == WSIZE) + break; + } + else + /* it's an EOB or a length */ + { + /* exit if end of block */ + if (e == 15) + { + gzio->block_len = 0; + break; + } + + /* get length of block to copy */ + NEEDBITS (e); + n = t->v.n + ((unsigned) b & mask_bits[e]); + DUMPBITS (e); + + if (gzio->td == NULL) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "NULL gzio->td"); + return 1; + } + + /* decode distance of block to copy */ + NEEDBITS ((unsigned) gzio->bd); + if ((e = (t = gzio->td + ((unsigned) b & md))->e) > 16) + do + { + if (e == 99) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + "an unused code found"); + return 1; + } + DUMPBITS (t->b); + e -= 16; + NEEDBITS (e); + } + while ((e = (t = t->v.t + ((unsigned) b & mask_bits[e]))->e) + > 16); + DUMPBITS (t->b); + NEEDBITS (e); + d = w - t->v.n - ((unsigned) b & mask_bits[e]); + DUMPBITS (e); + gzio->code_state++; + } + } + + if (gzio->code_state) + { + /* do the copy */ + do + { + n -= (e = (e = WSIZE - ((d &= WSIZE - 1) > w ? d : w)) > n ? n + : e); + + if (w - d >= e) + { + grub_memmove (gzio->slide + w, gzio->slide + d, e); + w += e; + d += e; + } + else + /* purposefully use the overlap for extra copies here!! */ + { + while (e--) + gzio->slide[w++] = gzio->slide[d++]; + } + + if (w == WSIZE) + break; + } + while (n); + + if (! n) + gzio->code_state--; + + /* did we break from the loop too soon? */ + if (w == WSIZE) + break; + } + } + + /* restore the globals from the locals */ + gzio->inflate_d = d; + gzio->inflate_n = n; + gzio->wp = w; /* restore global window pointer */ + gzio->bb = b; /* restore global bit buffer */ + gzio->bk = k; + + return ! gzio->block_len; +} + + +/* get header for an inflated type 0 (stored) block. */ + +static void +init_stored_block (grub_gzio_t gzio) +{ + register ulg b; /* bit buffer */ + register unsigned k; /* number of bits in bit buffer */ + + /* make local copies of globals */ + b = gzio->bb; /* initialize bit buffer */ + k = gzio->bk; + + /* go to byte boundary */ + DUMPBITS (k & 7); + + /* get the length and its complement */ + NEEDBITS (16); + gzio->block_len = ((unsigned) b & 0xffff); + DUMPBITS (16); + NEEDBITS (16); + if (gzio->block_len != (int) ((~b) & 0xffff)) + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + "the length of a stored block does not match"); + DUMPBITS (16); + + /* restore global variables */ + gzio->bb = b; + gzio->bk = k; +} + + +/* get header for an inflated type 1 (fixed Huffman codes) block. We should + either replace this with a custom decoder, or at least precompute the + Huffman tables. */ + +static void +init_fixed_block (grub_gzio_t gzio) +{ + int i; /* temporary variable */ + unsigned l[288]; /* length list for huft_build */ + + /* set up literal table */ + for (i = 0; i < 144; i++) + l[i] = 8; + for (; i < 256; i++) + l[i] = 9; + for (; i < 280; i++) + l[i] = 7; + for (; i < 288; i++) /* make a complete, but wrong code set */ + l[i] = 8; + gzio->bl = 7; + if (huft_build (l, 288, 257, cplens, cplext, &gzio->tl, &gzio->bl) != 0) + { + if (grub_errno == GRUB_ERR_NONE) + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + "failed in building a Huffman code table"); + return; + } + + /* set up distance table */ + for (i = 0; i < 30; i++) /* make an incomplete code set */ + l[i] = 5; + gzio->bd = 5; + if (huft_build (l, 30, 0, cpdist, cpdext, &gzio->td, &gzio->bd) > 1) + { + if (grub_errno == GRUB_ERR_NONE) + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + "failed in building a Huffman code table"); + huft_free (gzio->tl); + gzio->tl = 0; + return; + } + + /* indicate we're now working on a block */ + gzio->code_state = 0; + gzio->block_len++; +} + + +/* get header for an inflated type 2 (dynamic Huffman codes) block. */ + +static void +init_dynamic_block (grub_gzio_t gzio) +{ + int i; /* temporary variables */ + unsigned j; + unsigned l; /* last length */ + unsigned m; /* mask for bit lengths table */ + unsigned n; /* number of lengths to get */ + unsigned nb; /* number of bit length codes */ + unsigned nl; /* number of literal/length codes */ + unsigned nd; /* number of distance codes */ + unsigned ll[286 + 30]; /* literal/length and distance code lengths */ + register ulg b; /* bit buffer */ + register unsigned k; /* number of bits in bit buffer */ + + /* make local bit buffer */ + b = gzio->bb; + k = gzio->bk; + + /* read in table lengths */ + NEEDBITS (5); + nl = 257 + ((unsigned) b & 0x1f); /* number of literal/length codes */ + DUMPBITS (5); + NEEDBITS (5); + nd = 1 + ((unsigned) b & 0x1f); /* number of distance codes */ + DUMPBITS (5); + NEEDBITS (4); + nb = 4 + ((unsigned) b & 0xf); /* number of bit length codes */ + DUMPBITS (4); + if (nl > 286 || nd > 30) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "too much data"); + return; + } + + /* read in bit-length-code lengths */ + for (j = 0; j < nb; j++) + { + NEEDBITS (3); + ll[bitorder[j]] = (unsigned) b & 7; + DUMPBITS (3); + } + for (; j < 19; j++) + ll[bitorder[j]] = 0; + + /* build decoding table for trees--single level, 7 bit lookup */ + gzio->bl = 7; + if (huft_build (ll, 19, 19, NULL, NULL, &gzio->tl, &gzio->bl) != 0) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + "failed in building a Huffman code table"); + return; + } + + /* read in literal and distance code lengths */ + n = nl + nd; + m = mask_bits[gzio->bl]; + i = l = 0; + + if (gzio->tl == NULL) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "NULL gzio->tl"); + return; + } + + while ((unsigned) i < n) + { + NEEDBITS ((unsigned) gzio->bl); + j = (gzio->td = gzio->tl + ((unsigned) b & m))->b; + DUMPBITS (j); + j = gzio->td->v.n; + if (j < 16) /* length of code in bits (0..15) */ + ll[i++] = l = j; /* save last length in l */ + else if (j == 16) /* repeat last length 3 to 6 times */ + { + NEEDBITS (2); + j = 3 + ((unsigned) b & 3); + DUMPBITS (2); + if ((unsigned) i + j > n) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "too many codes found"); + goto fail; + } + while (j--) + ll[i++] = l; + } + else if (j == 17) /* 3 to 10 zero length codes */ + { + NEEDBITS (3); + j = 3 + ((unsigned) b & 7); + DUMPBITS (3); + if ((unsigned) i + j > n) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "too many codes found"); + goto fail; + } + while (j--) + ll[i++] = 0; + l = 0; + } + else + /* j == 18: 11 to 138 zero length codes */ + { + NEEDBITS (7); + j = 11 + ((unsigned) b & 0x7f); + DUMPBITS (7); + if ((unsigned) i + j > n) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "too many codes found"); + goto fail; + } + while (j--) + ll[i++] = 0; + l = 0; + } + } + + /* free decoding table for trees */ + huft_free (gzio->tl); + gzio->td = 0; + gzio->tl = 0; + + /* restore the global bit buffer */ + gzio->bb = b; + gzio->bk = k; + + /* build the decoding tables for literal/length and distance codes */ + gzio->bl = lbits; + if (huft_build (ll, nl, 257, cplens, cplext, &gzio->tl, &gzio->bl) != 0) + { + gzio->tl = 0; + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + "failed in building a Huffman code table"); + return; + } + gzio->bd = dbits; + if (huft_build (ll + nl, nd, 0, cpdist, cpdext, &gzio->td, &gzio->bd) != 0) + { + huft_free (gzio->tl); + gzio->tl = 0; + gzio->td = 0; + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + "failed in building a Huffman code table"); + return; + } + + /* indicate we're now working on a block */ + gzio->code_state = 0; + gzio->block_len++; + return; + + fail: + huft_free (gzio->tl); + gzio->td = NULL; + gzio->tl = NULL; +} + + +static void +get_new_block (grub_gzio_t gzio) +{ + register ulg b; /* bit buffer */ + register unsigned k; /* number of bits in bit buffer */ + + /* make local bit buffer */ + b = gzio->bb; + k = gzio->bk; + + /* read in last block bit */ + NEEDBITS (1); + gzio->last_block = (int) b & 1; + DUMPBITS (1); + + /* read in block type */ + NEEDBITS (2); + gzio->block_type = (unsigned) b & 3; + DUMPBITS (2); + + /* restore the global bit buffer */ + gzio->bb = b; + gzio->bk = k; + + switch (gzio->block_type) + { + case INFLATE_STORED: + init_stored_block (gzio); + break; + case INFLATE_FIXED: + init_fixed_block (gzio); + break; + case INFLATE_DYNAMIC: + init_dynamic_block (gzio); + break; + default: + break; + } +} + + +static void +inflate_window (grub_gzio_t gzio) +{ + /* initialize window */ + gzio->wp = 0; + + /* + * Main decompression loop. + */ + + while (gzio->wp < WSIZE && grub_errno == GRUB_ERR_NONE) + { + if (! gzio->block_len) + { + if (gzio->last_block) + break; + + get_new_block (gzio); + } + + if (gzio->block_type > INFLATE_DYNAMIC) + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + "unknown block type %d", gzio->block_type); + + if (grub_errno != GRUB_ERR_NONE) + return; + + /* + * Expand stored block here. + */ + if (gzio->block_type == INFLATE_STORED) + { + int w = gzio->wp; + + /* + * This is basically a glorified pass-through + */ + + while (gzio->block_len && w < WSIZE && grub_errno == GRUB_ERR_NONE) + { + gzio->slide[w++] = get_byte (gzio); + gzio->block_len--; + } + + gzio->wp = w; + + continue; + } + + /* + * Expand other kind of block. + */ + + if (inflate_codes_in_window (gzio)) + { + huft_free (gzio->tl); + huft_free (gzio->td); + gzio->tl = 0; + gzio->td = 0; + } + } + + gzio->saved_offset += gzio->wp; + + if (gzio->hcontext) + { + gzio->hdesc->write (gzio->hcontext, gzio->slide, gzio->wp); + + if (gzio->saved_offset == gzio->orig_len) + { + grub_uint32_t csum; + + gzio->hdesc->final (gzio->hcontext); + csum = grub_get_unaligned32 (gzio->hdesc->read (gzio->hcontext)); + csum = grub_be_to_cpu32 (csum); + if (csum != gzio->orig_checksum) + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + "checksum mismatch %08x/%08x", + gzio->orig_checksum, csum); + } + } +} + + +static void +initialize_tables (grub_gzio_t gzio) +{ + gzio->saved_offset = 0; + gzio_seek (gzio, gzio->data_offset); + + /* Initialize the bit buffer. */ + gzio->bk = 0; + gzio->bb = 0; + + /* Reset partial decompression code. */ + gzio->last_block = 0; + gzio->block_len = 0; + + /* Reset memory allocation stuff. */ + huft_free (gzio->tl); + huft_free (gzio->td); + gzio->tl = NULL; + gzio->td = NULL; + + if (gzio->hcontext) + gzio->hdesc->init(gzio->hcontext); +} + + +/* Open a new decompressing object on the top of IO. If TRANSPARENT is true, + even if IO does not contain data compressed by gzip, return a valid file + object. Note that this function won't close IO, even if an error occurs. */ +static grub_file_t +grub_gzio_open (grub_file_t io, enum grub_file_type type) +{ + grub_file_t file; + grub_gzio_t gzio = 0; + + if (type & GRUB_FILE_TYPE_NO_DECOMPRESS) + return io; + + file = (grub_file_t) grub_zalloc (sizeof (*file)); + if (! file) + return 0; + + gzio = grub_zalloc (sizeof (*gzio)); + if (! gzio) + { + grub_free (file); + return 0; + } + + gzio->file = io; + + gzio->hdesc = GRUB_MD_CRC32; + gzio->hcontext = grub_malloc(gzio->hdesc->contextsize); + + file->device = io->device; + file->data = gzio; + file->fs = &grub_gzio_fs; + file->not_easily_seekable = 1; + + if (! test_gzip_header (file)) + { + grub_errno = GRUB_ERR_NONE; + grub_free (gzio->hcontext); + grub_free (gzio); + grub_free (file); + grub_file_seek (io, 0); + + return io; + } + + return file; +} + +static grub_uint8_t +mod_31 (grub_uint16_t v) +{ + /* At most 2 iterations for any number that + we can get here. + In any case faster than real division. */ + while (v > 0x1f) + v = (v & 0x1f) + (v >> 5); + if (v == 0x1f) + return 0; + return v; +} + +static int +test_zlib_header (grub_gzio_t gzio) +{ + grub_uint8_t cmf, flg; + + cmf = get_byte (gzio); + flg = get_byte (gzio); + + /* Check that compression method is DEFLATE. */ + if ((cmf & 0xf) != GRUB_GZ_DEFLATED) + { + /* TRANSLATORS: It's about given file having some strange format, not + complete lack of gzip support. */ + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, N_("unsupported gzip format")); + return 0; + } + + /* Usually it would be: (cmf * 256 + flg) % 31 != 0. */ + /* But 256 == 8 (31). */ + /* By multiplying by 4 and using 32 == 1 (31). We get our formula. */ + if (mod_31 (cmf + flg * 4) != 0) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, N_("unsupported gzip format")); + return 0; + } + + /* Dictionary isn't supported. */ + if (flg & 0x20) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, N_("unsupported gzip format")); + return 0; + } + + gzio->data_offset = 2; + initialize_tables (gzio); + + return 1; +} + +static grub_ssize_t +grub_gzio_read_real (grub_gzio_t gzio, grub_off_t offset, + char *buf, grub_size_t len) +{ + grub_ssize_t ret = 0; + + /* Do we reset decompression to the beginning of the file? */ + if (gzio->saved_offset > offset + WSIZE) + initialize_tables (gzio); + + /* + * This loop operates upon uncompressed data only. The only + * special thing it does is to make sure the decompression + * window is within the range of data it needs. + */ + + while (len > 0 && grub_errno == GRUB_ERR_NONE) + { + register grub_size_t size; + register char *srcaddr; + + while (offset >= gzio->saved_offset) + { + inflate_window (gzio); + if (gzio->wp == 0) + goto out; + } + + if (gzio->wp == 0) + goto out; + + srcaddr = (char *) ((offset & (WSIZE - 1)) + gzio->slide); + size = gzio->saved_offset - offset; + if (size > len) + size = len; + + grub_memmove (buf, srcaddr, size); + + buf += size; + len -= size; + ret += size; + offset += size; + } + + out: + if (grub_errno != GRUB_ERR_NONE) + ret = -1; + + return ret; +} + +static grub_ssize_t +grub_gzio_read (grub_file_t file, char *buf, grub_size_t len) +{ + grub_ssize_t ret; + ret = grub_gzio_read_real (file->data, file->offset, buf, len); + + if (!grub_errno && ret != (grub_ssize_t) len) + { + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "premature end of compressed"); + ret = -1; + } + return ret; +} + +/* Release everything, including the underlying file object. */ +static grub_err_t +grub_gzio_close (grub_file_t file) +{ + grub_gzio_t gzio = file->data; + + grub_file_close (gzio->file); + huft_free (gzio->tl); + huft_free (gzio->td); + grub_free (gzio->hcontext); + grub_free (gzio); + + /* No need to close the same device twice. */ + file->device = 0; + file->name = 0; + + return grub_errno; +} + +grub_ssize_t +grub_zlib_decompress (char *inbuf, grub_size_t insize, grub_off_t off, + char *outbuf, grub_size_t outsize) +{ + grub_gzio_t gzio = 0; + grub_ssize_t ret; + + gzio = grub_zalloc (sizeof (*gzio)); + if (! gzio) + return -1; + gzio->mem_input = (grub_uint8_t *) inbuf; + gzio->mem_input_size = insize; + gzio->mem_input_off = 0; + + if (!test_zlib_header (gzio)) + { + grub_free (gzio); + return -1; + } + + ret = grub_gzio_read_real (gzio, off, outbuf, outsize); + grub_free (gzio); + + /* FIXME: Check Adler. */ + return ret; +} + +grub_ssize_t +grub_deflate_decompress (char *inbuf, grub_size_t insize, grub_off_t off, + char *outbuf, grub_size_t outsize) +{ + grub_gzio_t gzio = 0; + grub_ssize_t ret; + + gzio = grub_zalloc (sizeof (*gzio)); + if (! gzio) + return -1; + gzio->mem_input = (grub_uint8_t *) inbuf; + gzio->mem_input_size = insize; + gzio->mem_input_off = 0; + + initialize_tables (gzio); + + ret = grub_gzio_read_real (gzio, off, outbuf, outsize); + grub_free (gzio); + + return ret; +} + + + +static struct grub_fs grub_gzio_fs = + { + .name = "gzio", + .fs_dir = 0, + .fs_open = 0, + .fs_read = grub_gzio_read, + .fs_close = grub_gzio_close, + .fs_label = 0, + .next = 0 + }; + +GRUB_MOD_INIT(gzio) +{ + grub_file_filter_register (GRUB_FILE_FILTER_GZIO, grub_gzio_open); +} + +GRUB_MOD_FINI(gzio) +{ + grub_file_filter_unregister (GRUB_FILE_FILTER_GZIO); +} diff --git a/grub-core/io/lzopio.c b/grub-core/io/lzopio.c new file mode 100644 index 0000000..a7d4425 --- /dev/null +++ b/grub-core/io/lzopio.c @@ -0,0 +1,546 @@ +/* lzopio.c - decompression support for lzop */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2011 Free Software Foundation, Inc. + * + * GRUB 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 3 of the License, or + * (at your option) any later version. + * + * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/err.h> +#include <grub/mm.h> +#include <grub/file.h> +#include <grub/fs.h> +#include <grub/dl.h> +#include <grub/crypto.h> +#include <minilzo.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define LZOP_MAGIC "\x89\x4c\x5a\x4f\x00\x0d\x0a\x1a\x0a" +#define LZOP_MAGIC_SIZE 9 +#define LZOP_CHECK_SIZE 4 +#define LZOP_NEW_LIB 0x0940 + +/* Header flags - copied from conf.h of LZOP source code. */ +#define F_ADLER32_D 0x00000001L +#define F_ADLER32_C 0x00000002L +#define F_STDIN 0x00000004L +#define F_STDOUT 0x00000008L +#define F_NAME_DEFAULT 0x00000010L +#define F_DOSISH 0x00000020L +#define F_H_EXTRA_FIELD 0x00000040L +#define F_H_GMTDIFF 0x00000080L +#define F_CRC32_D 0x00000100L +#define F_CRC32_C 0x00000200L +#define F_MULTIPART 0x00000400L +#define F_H_FILTER 0x00000800L +#define F_H_CRC32 0x00001000L +#define F_H_PATH 0x00002000L +#define F_MASK 0x00003FFFL + +struct block_header +{ + grub_uint32_t usize; + grub_uint32_t csize; + grub_uint32_t ucheck; + grub_uint32_t ccheck; + unsigned char *cdata; + unsigned char *udata; +}; + +struct grub_lzopio +{ + grub_file_t file; + int has_ccheck; + int has_ucheck; + const gcry_md_spec_t *ucheck_fun; + const gcry_md_spec_t *ccheck_fun; + grub_off_t saved_off; /* Rounded down to block boundary. */ + grub_off_t start_block_off; + struct block_header block; +}; + +typedef struct grub_lzopio *grub_lzopio_t; +static struct grub_fs grub_lzopio_fs; + +/* Some helper functions. On errors memory allocated by those function is free + * either on close() so no risk of leaks. This makes functions simpler. */ + +/* Read block header from file, after successful exit file points to + * beginning of block data. */ +static int +read_block_header (struct grub_lzopio *lzopio) +{ + lzopio->saved_off += lzopio->block.usize; + + /* Free cached block data if any. */ + grub_free (lzopio->block.udata); + grub_free (lzopio->block.cdata); + lzopio->block.udata = NULL; + lzopio->block.cdata = NULL; + + if (grub_file_read (lzopio->file, &lzopio->block.usize, + sizeof (lzopio->block.usize)) != + sizeof (lzopio->block.usize)) + return -1; + + lzopio->block.usize = grub_be_to_cpu32 (lzopio->block.usize); + + /* Last block has uncompressed data size == 0 and no other fields. */ + if (lzopio->block.usize == 0) + { + if (grub_file_tell (lzopio->file) == grub_file_size (lzopio->file)) + return 0; + else + return -1; + } + + /* Read compressed data block size. */ + if (grub_file_read (lzopio->file, &lzopio->block.csize, + sizeof (lzopio->block.csize)) != + sizeof (lzopio->block.csize)) + return -1; + + lzopio->block.csize = grub_be_to_cpu32 (lzopio->block.csize); + + /* Corrupted. */ + if (lzopio->block.csize > lzopio->block.usize) + return -1; + + /* Read checksum of uncompressed data. */ + if (lzopio->has_ucheck) + { + if (grub_file_read (lzopio->file, &lzopio->block.ucheck, + sizeof (lzopio->block.ucheck)) != + sizeof (lzopio->block.ucheck)) + return -1; + } + + /* Read checksum of compressed data. */ + if (lzopio->has_ccheck) + { + /* Incompressible data block. */ + if (lzopio->block.csize == lzopio->block.usize) + { + lzopio->block.ccheck = lzopio->block.ucheck; + } + else + { + if (grub_file_read (lzopio->file, &lzopio->block.ccheck, + sizeof (lzopio->block.ccheck)) != + sizeof (lzopio->block.ccheck)) + return -1; + } + } + + return 0; +} + +/* Read block data into memory. File must be set to beginning of block data. + * Can't be called on last block. */ +static int +read_block_data (struct grub_lzopio *lzopio) +{ + lzopio->block.cdata = grub_malloc (lzopio->block.csize); + if (!lzopio->block.cdata) + return -1; + + if (grub_file_read (lzopio->file, lzopio->block.cdata, lzopio->block.csize) + != (grub_ssize_t) lzopio->block.csize) + return -1; + + if (lzopio->ccheck_fun) + { + grub_uint8_t computed_hash[GRUB_CRYPTO_MAX_MDLEN]; + + if (lzopio->ccheck_fun->mdlen > GRUB_CRYPTO_MAX_MDLEN) + return -1; + + grub_crypto_hash (lzopio->ccheck_fun, computed_hash, + lzopio->block.cdata, + lzopio->block.csize); + + if (grub_memcmp + (computed_hash, &lzopio->block.ccheck, + sizeof (lzopio->block.ccheck)) != 0) + return -1; + } + + return 0; +} + +/* Read block data, uncompressed and also store it in memory. */ +/* XXX Investigate possibility of in-place decompression to reduce memory + * footprint. Or try to uncompress directly to buf if possible. */ +static int +uncompress_block (struct grub_lzopio *lzopio) +{ + lzo_uint usize = lzopio->block.usize; + + if (read_block_data (lzopio) < 0) + return -1; + + /* Incompressible data. */ + if (lzopio->block.csize == lzopio->block.usize) + { + lzopio->block.udata = lzopio->block.cdata; + lzopio->block.cdata = NULL; + } + else + { + lzopio->block.udata = grub_malloc (lzopio->block.usize); + if (!lzopio->block.udata) + return -1; + + if (lzo1x_decompress_safe (lzopio->block.cdata, lzopio->block.csize, + lzopio->block.udata, &usize, NULL) + != LZO_E_OK) + return -1; + + if (lzopio->ucheck_fun) + { + grub_uint8_t computed_hash[GRUB_CRYPTO_MAX_MDLEN]; + + if (lzopio->ucheck_fun->mdlen > GRUB_CRYPTO_MAX_MDLEN) + return -1; + + grub_crypto_hash (lzopio->ucheck_fun, computed_hash, + lzopio->block.udata, + lzopio->block.usize); + + if (grub_memcmp + (computed_hash, &lzopio->block.ucheck, + sizeof (lzopio->block.ucheck)) != 0) + return -1; + } + + /* Compressed data can be free now. */ + grub_free (lzopio->block.cdata); + lzopio->block.cdata = NULL; + } + + return 0; +} + +/* Jump to next block and read its header. */ +static int +jump_block (struct grub_lzopio *lzopio) +{ + /* only jump if block was not decompressed (and read from disk) */ + if (!lzopio->block.udata) + { + grub_off_t off = grub_file_tell (lzopio->file) + lzopio->block.csize; + + if (grub_file_seek (lzopio->file, off) == ((grub_off_t) - 1)) + return -1; + } + + return read_block_header (lzopio); +} + +static int +calculate_uncompressed_size (grub_file_t file) +{ + grub_lzopio_t lzopio = file->data; + grub_off_t usize_total = 0; + + if (read_block_header (lzopio) < 0) + return -1; + + /* FIXME: Don't do this for not easily seekable files. */ + while (lzopio->block.usize != 0) + { + usize_total += lzopio->block.usize; + + if (jump_block (lzopio) < 0) + return -1; + } + + file->size = usize_total; + + return 0; +} + +struct lzop_header +{ + grub_uint8_t magic[LZOP_MAGIC_SIZE]; + grub_uint16_t lzop_version; + grub_uint16_t lib_version; + grub_uint16_t lib_version_ext; + grub_uint8_t method; + grub_uint8_t level; + grub_uint32_t flags; + /* grub_uint32_t filter; */ /* No filters support. Rarely used anyway. */ + grub_uint32_t mode; + grub_uint32_t mtime_lo; + grub_uint32_t mtime_hi; + grub_uint8_t name_len; +} GRUB_PACKED; + +static int +test_header (grub_file_t file) +{ + grub_lzopio_t lzopio = file->data; + struct lzop_header header; + grub_uint32_t flags, checksum; + const gcry_md_spec_t *hcheck; + grub_uint8_t *context = NULL; + grub_uint8_t *name = NULL; + + if (grub_file_read (lzopio->file, &header, sizeof (header)) != sizeof (header)) + return 0; + + if (grub_memcmp (header.magic, LZOP_MAGIC, LZOP_MAGIC_SIZE) != 0) + return 0; + + if (grub_be_to_cpu16(header.lib_version) < LZOP_NEW_LIB) + return 0; + + /* Too new version, should upgrade minilzo? */ + if (grub_be_to_cpu16 (header.lib_version_ext) > MINILZO_VERSION) + return 0; + + flags = grub_be_to_cpu32 (header.flags); + + if (flags & F_CRC32_D) + { + lzopio->has_ucheck = 1; + lzopio->ucheck_fun = grub_crypto_lookup_md_by_name ("crc32"); + } + else if (flags & F_ADLER32_D) + { + lzopio->has_ucheck = 1; + lzopio->ucheck_fun = grub_crypto_lookup_md_by_name ("adler32"); + } + + if (flags & F_CRC32_C) + { + lzopio->has_ccheck = 1; + lzopio->ccheck_fun = grub_crypto_lookup_md_by_name ("crc32"); + } + else if (flags & F_ADLER32_C) + { + lzopio->has_ccheck = 1; + lzopio->ccheck_fun = grub_crypto_lookup_md_by_name ("adler32"); + } + + if (flags & F_H_CRC32) + hcheck = grub_crypto_lookup_md_by_name ("crc32"); + else + hcheck = grub_crypto_lookup_md_by_name ("adler32"); + + if (hcheck) { + context = grub_malloc(hcheck->contextsize); + if (! context) + return 0; + + hcheck->init(context); + + /* MAGIC is not included in check calculation. */ + hcheck->write(context, &header.lzop_version, sizeof(header)- LZOP_MAGIC_SIZE); + } + + if (header.name_len != 0) + { + name = grub_malloc (header.name_len); + if (! name) + { + grub_free (context); + return 0; + } + + if (grub_file_read (lzopio->file, name, header.name_len) != + header.name_len) + { + grub_free(name); + goto CORRUPTED; + } + + if (hcheck) + hcheck->write(context, name, header.name_len); + + grub_free(name); + } + + if (hcheck) + hcheck->final(context); + + if (grub_file_read (lzopio->file, &checksum, sizeof (checksum)) != + sizeof (checksum)) + goto CORRUPTED; + + if (hcheck && grub_memcmp (&checksum, hcheck->read(context), sizeof(checksum)) != 0) + goto CORRUPTED; + + lzopio->start_block_off = grub_file_tell (lzopio->file); + + if (calculate_uncompressed_size (file) < 0) + goto CORRUPTED; + + /* Get back to start block. */ + grub_file_seek (lzopio->file, lzopio->start_block_off); + + /* Read first block - grub_lzopio_read() expects valid block. */ + if (read_block_header (lzopio) < 0) + goto CORRUPTED; + + lzopio->saved_off = 0; + return 1; + +CORRUPTED: + return 0; +} + +static grub_file_t +grub_lzopio_open (grub_file_t io, enum grub_file_type type) +{ + grub_file_t file; + grub_lzopio_t lzopio; + + if (type & GRUB_FILE_TYPE_NO_DECOMPRESS) + return io; + + file = (grub_file_t) grub_zalloc (sizeof (*file)); + if (!file) + return 0; + + lzopio = grub_zalloc (sizeof (*lzopio)); + if (!lzopio) + { + grub_free (file); + return 0; + } + + lzopio->file = io; + + file->device = io->device; + file->data = lzopio; + file->fs = &grub_lzopio_fs; + file->size = GRUB_FILE_SIZE_UNKNOWN; + file->not_easily_seekable = 1; + + if (grub_file_tell (lzopio->file) != 0) + grub_file_seek (lzopio->file, 0); + + if (!test_header (file)) + { + grub_errno = GRUB_ERR_NONE; + grub_file_seek (io, 0); + grub_free (lzopio); + grub_free (file); + + return io; + } + + return file; +} + +static grub_ssize_t +grub_lzopio_read (grub_file_t file, char *buf, grub_size_t len) +{ + grub_lzopio_t lzopio = file->data; + grub_ssize_t ret = 0; + grub_off_t off; + + /* Backward seek before last read block. */ + if (lzopio->saved_off > grub_file_tell (file)) + { + grub_file_seek (lzopio->file, lzopio->start_block_off); + + if (read_block_header (lzopio) < 0) + goto CORRUPTED; + + lzopio->saved_off = 0; + } + + /* Forward to first block with requested data. */ + while (lzopio->saved_off + lzopio->block.usize <= grub_file_tell (file)) + { + /* EOF, could be possible files with unknown size. */ + if (lzopio->block.usize == 0) + return 0; + + if (jump_block (lzopio) < 0) + goto CORRUPTED; + } + + off = grub_file_tell (file) - lzopio->saved_off; + + while (len != 0 && lzopio->block.usize != 0) + { + grub_size_t to_copy; + + /* Block not decompressed yet. */ + if (!lzopio->block.udata && uncompress_block (lzopio) < 0) + goto CORRUPTED; + + /* Copy requested data into buffer. */ + to_copy = lzopio->block.usize - off; + if (to_copy > len) + to_copy = len; + grub_memcpy (buf, lzopio->block.udata + off, to_copy); + + len -= to_copy; + buf += to_copy; + ret += to_copy; + off = 0; + + /* Read next block if needed. */ + if (len > 0 && read_block_header (lzopio) < 0) + goto CORRUPTED; + } + + return ret; + +CORRUPTED: + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, N_("lzop file corrupted")); + return -1; +} + +/* Release everything, including the underlying file object. */ +static grub_err_t +grub_lzopio_close (grub_file_t file) +{ + grub_lzopio_t lzopio = file->data; + + grub_file_close (lzopio->file); + grub_free (lzopio->block.cdata); + grub_free (lzopio->block.udata); + grub_free (lzopio); + + /* Device must not be closed twice. */ + file->device = 0; + file->name = 0; + return grub_errno; +} + +static struct grub_fs grub_lzopio_fs = { + .name = "lzopio", + .fs_dir = 0, + .fs_open = 0, + .fs_read = grub_lzopio_read, + .fs_close = grub_lzopio_close, + .fs_label = 0, + .next = 0 +}; + +GRUB_MOD_INIT (lzopio) +{ + grub_file_filter_register (GRUB_FILE_FILTER_LZOPIO, grub_lzopio_open); +} + +GRUB_MOD_FINI (lzopio) +{ + grub_file_filter_unregister (GRUB_FILE_FILTER_LZOPIO); +} diff --git a/grub-core/io/offset.c b/grub-core/io/offset.c new file mode 100644 index 0000000..7e2db4a --- /dev/null +++ b/grub-core/io/offset.c @@ -0,0 +1,112 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2013 Free Software Foundation, Inc. + * + * GRUB 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 3 of the License, or + * (at your option) any later version. + * + * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/file.h> +#include <grub/dl.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +struct grub_offset_file +{ + grub_file_t parent; + grub_off_t off; +}; + +static grub_ssize_t +grub_offset_read (grub_file_t file, char *buf, grub_size_t len) +{ + struct grub_offset_file *data = file->data; + if (grub_file_seek (data->parent, data->off + file->offset) == (grub_off_t) -1) + return -1; + return grub_file_read (data->parent, buf, len); +} + +static grub_err_t +grub_offset_close (grub_file_t file) +{ + struct grub_offset_file *data = file->data; + + if (data->parent) + grub_file_close (data->parent); + + /* No need to close the same device twice. */ + file->device = 0; + + return 0; +} + +static struct grub_fs grub_offset_fs = { + .name = "offset", + .fs_dir = 0, + .fs_open = 0, + .fs_read = grub_offset_read, + .fs_close = grub_offset_close, + .fs_label = 0, + .next = 0 +}; + +void +grub_file_offset_close (grub_file_t file) +{ + struct grub_offset_file *off_data = file->data; + off_data->parent = NULL; + grub_file_close (file); +} + +grub_file_t +grub_file_offset_open (grub_file_t parent, enum grub_file_type type, + grub_off_t start, grub_off_t size) +{ + struct grub_offset_file *off_data; + grub_file_t off_file, last_off_file; + grub_file_filter_id_t filter; + + off_file = grub_zalloc (sizeof (*off_file)); + off_data = grub_zalloc (sizeof (*off_data)); + if (!off_file || !off_data) + { + grub_free (off_file); + grub_free (off_data); + return 0; + } + + off_data->off = start; + off_data->parent = parent; + + off_file->device = parent->device; + off_file->data = off_data; + off_file->fs = &grub_offset_fs; + off_file->size = size; + + last_off_file = NULL; + for (filter = GRUB_FILE_FILTER_COMPRESSION_FIRST; + off_file && filter <= GRUB_FILE_FILTER_COMPRESSION_LAST; filter++) + if (grub_file_filters[filter]) + { + last_off_file = off_file; + off_file = grub_file_filters[filter] (off_file, type); + } + + if (!off_file) + { + off_data->parent = NULL; + grub_file_close (last_off_file); + return 0; + } + return off_file; +} diff --git a/grub-core/io/xzio.c b/grub-core/io/xzio.c new file mode 100644 index 0000000..516c4df --- /dev/null +++ b/grub-core/io/xzio.c @@ -0,0 +1,346 @@ +/* xzio.c - decompression support for xz */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010 Free Software Foundation, Inc. + * + * GRUB 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 3 of the License, or + * (at your option) any later version. + * + * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/err.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/file.h> +#include <grub/fs.h> +#include <grub/dl.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#include "xz.h" +#include "xz_stream.h" + +#define XZBUFSIZ 0x2000 +#define VLI_MAX_DIGITS 9 +#define XZ_STREAM_FOOTER_SIZE 12 + +struct grub_xzio +{ + grub_file_t file; + struct xz_buf buf; + struct xz_dec *dec; + grub_uint8_t inbuf[XZBUFSIZ]; + grub_uint8_t outbuf[XZBUFSIZ]; + grub_off_t saved_offset; +}; + +typedef struct grub_xzio *grub_xzio_t; +static struct grub_fs grub_xzio_fs; + +static grub_size_t +decode_vli (const grub_uint8_t buf[], grub_size_t size_max, + grub_uint64_t *num) +{ + if (size_max == 0) + return 0; + + if (size_max > VLI_MAX_DIGITS) + size_max = VLI_MAX_DIGITS; + + *num = buf[0] & 0x7F; + grub_size_t i = 0; + + while (buf[i++] & 0x80) + { + if (i >= size_max || buf[i] == 0x00) + return 0; + + *num |= (uint64_t) (buf[i] & 0x7F) << (i * 7); + } + + return i; +} + +static grub_ssize_t +read_vli (grub_file_t file, grub_uint64_t *num) +{ + grub_uint8_t buf[VLI_MAX_DIGITS]; + grub_ssize_t read_bytes; + grub_size_t dec; + + read_bytes = grub_file_read (file, buf, VLI_MAX_DIGITS); + if (read_bytes < 0) + return -1; + + dec = decode_vli (buf, read_bytes, num); + grub_file_seek (file, file->offset - (read_bytes - dec)); + return dec; +} + +/* Function xz_dec_run() should consume header and ask for more (XZ_OK) + * else file is corrupted (or options not supported) or not xz. */ +static int +test_header (grub_file_t file) +{ + grub_xzio_t xzio = file->data; + enum xz_ret ret; + + xzio->buf.in_size = grub_file_read (xzio->file, xzio->inbuf, + STREAM_HEADER_SIZE); + + if (xzio->buf.in_size != STREAM_HEADER_SIZE) + return 0; + + ret = xz_dec_run (xzio->dec, &xzio->buf); + + if (ret == XZ_FORMAT_ERROR) + return 0; + + if (ret != XZ_OK) + return 0; + + return 1; +} + +/* Try to find out size of uncompressed data, + * also do some footer sanity checks. */ +static int +test_footer (grub_file_t file) +{ + grub_xzio_t xzio = file->data; + grub_uint8_t footer[FOOTER_MAGIC_SIZE]; + grub_uint32_t backsize; + grub_uint8_t imarker; + grub_uint64_t uncompressed_size_total = 0; + grub_uint64_t uncompressed_size; + grub_uint64_t records; + + grub_file_seek (xzio->file, xzio->file->size - FOOTER_MAGIC_SIZE); + if (grub_file_read (xzio->file, footer, FOOTER_MAGIC_SIZE) + != FOOTER_MAGIC_SIZE + || grub_memcmp (footer, FOOTER_MAGIC, FOOTER_MAGIC_SIZE) != 0) + goto ERROR; + + grub_file_seek (xzio->file, xzio->file->size - 8); + if (grub_file_read (xzio->file, &backsize, sizeof (backsize)) + != sizeof (backsize)) + goto ERROR; + + /* Calculate real backward size. */ + backsize = (grub_le_to_cpu32 (backsize) + 1) * 4; + + /* Set file to the beginning of stream index. */ + grub_file_seek (xzio->file, + xzio->file->size - XZ_STREAM_FOOTER_SIZE - backsize); + + /* Test index marker. */ + if (grub_file_read (xzio->file, &imarker, sizeof (imarker)) + != sizeof (imarker) && imarker != 0x00) + goto ERROR; + + if (read_vli (xzio->file, &records) <= 0) + goto ERROR; + + for (; records != 0; records--) + { + if (read_vli (xzio->file, &uncompressed_size) <= 0) /* Ignore unpadded. */ + goto ERROR; + if (read_vli (xzio->file, &uncompressed_size) <= 0) /* Uncompressed. */ + goto ERROR; + + uncompressed_size_total += uncompressed_size; + } + + file->size = uncompressed_size_total; + grub_file_seek (xzio->file, STREAM_HEADER_SIZE); + return 1; + +ERROR: + return 0; +} + +static grub_file_t +grub_xzio_open (grub_file_t io, enum grub_file_type type) +{ + grub_file_t file; + grub_xzio_t xzio; + + if (type & GRUB_FILE_TYPE_NO_DECOMPRESS) + return io; + + file = (grub_file_t) grub_zalloc (sizeof (*file)); + if (!file) + return 0; + + xzio = grub_zalloc (sizeof (*xzio)); + if (!xzio) + { + grub_free (file); + return 0; + } + + xzio->file = io; + + file->device = io->device; + file->data = xzio; + file->fs = &grub_xzio_fs; + file->size = GRUB_FILE_SIZE_UNKNOWN; + file->not_easily_seekable = 1; + + if (grub_file_tell (xzio->file) != 0) + grub_file_seek (xzio->file, 0); + + /* Allocated 64KiB for dictionary. + * Decoder will relocate if bigger is needed. */ + xzio->dec = xz_dec_init (1 << 16); + if (!xzio->dec) + { + grub_free (file); + grub_free (xzio); + return 0; + } + + xzio->buf.in = xzio->inbuf; + xzio->buf.out = xzio->outbuf; + xzio->buf.out_size = XZBUFSIZ; + + /* FIXME: don't test footer on not easily seekable files. */ + if (!test_header (file) || !test_footer (file)) + { + grub_errno = GRUB_ERR_NONE; + grub_file_seek (io, 0); + xz_dec_end (xzio->dec); + grub_free (xzio); + grub_free (file); + + return io; + } + + return file; +} + +static grub_ssize_t +grub_xzio_read (grub_file_t file, char *buf, grub_size_t len) +{ + grub_ssize_t ret = 0; + grub_ssize_t readret; + enum xz_ret xzret; + grub_xzio_t xzio = file->data; + grub_off_t current_offset; + + /* If seek backward need to reset decoder and start from beginning of file. + TODO Possible improvement by jumping blocks. */ + if (file->offset < xzio->saved_offset) + { + xz_dec_reset (xzio->dec); + xzio->saved_offset = 0; + xzio->buf.out_pos = 0; + xzio->buf.in_pos = 0; + xzio->buf.in_size = 0; + grub_file_seek (xzio->file, 0); + } + + current_offset = xzio->saved_offset; + + while (len > 0) + { + xzio->buf.out_size = file->offset + ret + len - current_offset; + if (xzio->buf.out_size > XZBUFSIZ) + xzio->buf.out_size = XZBUFSIZ; + /* Feed input. */ + if (xzio->buf.in_pos == xzio->buf.in_size) + { + readret = grub_file_read (xzio->file, xzio->inbuf, XZBUFSIZ); + if (readret < 0) + return -1; + xzio->buf.in_size = readret; + xzio->buf.in_pos = 0; + } + + xzret = xz_dec_run (xzio->dec, &xzio->buf); + switch (xzret) + { + case XZ_MEMLIMIT_ERROR: + case XZ_FORMAT_ERROR: + case XZ_OPTIONS_ERROR: + case XZ_DATA_ERROR: + case XZ_BUF_ERROR: + grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, + N_("xz file corrupted or unsupported block options")); + return -1; + default: + break; + } + + { + grub_off_t new_offset = current_offset + xzio->buf.out_pos; + + if (file->offset <= new_offset) + /* Store first chunk of data in buffer. */ + { + grub_size_t delta = new_offset - (file->offset + ret); + grub_memmove (buf, xzio->buf.out + (xzio->buf.out_pos - delta), + delta); + len -= delta; + buf += delta; + ret += delta; + } + current_offset = new_offset; + } + xzio->buf.out_pos = 0; + + if (xzret == XZ_STREAM_END) /* Stream end, EOF. */ + break; + } + + if (ret >= 0) + xzio->saved_offset = file->offset + ret; + + return ret; +} + +/* Release everything, including the underlying file object. */ +static grub_err_t +grub_xzio_close (grub_file_t file) +{ + grub_xzio_t xzio = file->data; + + xz_dec_end (xzio->dec); + + grub_file_close (xzio->file); + grub_free (xzio); + + /* Device must not be closed twice. */ + file->device = 0; + file->name = 0; + return grub_errno; +} + +static struct grub_fs grub_xzio_fs = { + .name = "xzio", + .fs_dir = 0, + .fs_open = 0, + .fs_read = grub_xzio_read, + .fs_close = grub_xzio_close, + .fs_label = 0, + .next = 0 +}; + +GRUB_MOD_INIT (xzio) +{ + grub_file_filter_register (GRUB_FILE_FILTER_XZIO, grub_xzio_open); +} + +GRUB_MOD_FINI (xzio) +{ + grub_file_filter_unregister (GRUB_FILE_FILTER_XZIO); +} |