--- /dev/null
+/* 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 "minilzo.h"
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define LZOP_MAGIC "\x89\x4c\x5a\x4f\x00\x0d\x0a\x1a\x0a"
+#define LZOP_MAGIC_SIZE 9
+#define CHECK_SIZE 4
+#define NEW_LZO_LIB 0x0940
+#define MIN_HEADER_SIZE 0
+#define MAX_HEADER_SIZE 0
+
+/* 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 ucheck; /* XXX Use gcry to validate checks. */
+ int ccheck;
+ 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. Memory allocated by those function is free either
+ * on next read_block_header() or on close() so no risk of leaks. This makes
+ * those function 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)
+{
+ /* 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);
+
+ /* XXX Handle incompressible data case here rather than in uncompress_block.
+ * This will allow to handle switch of ccheck/ucheck easier and also
+ * make uncompress_block() a bit simpler. */
+
+ /* Read data checks. */
+ if (lzopio->ucheck)
+ {
+ if (grub_file_read (lzopio->file, &lzopio->block.ucheck,
+ sizeof (lzopio->block.ucheck)) !=
+ sizeof (lzopio->block.ucheck))
+ return -1;
+ }
+
+ if (lzopio->ccheck)
+ {
+ 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)
+ {
+ /* XXX Validate data checksum here. */
+ }
+
+ return 0;
+}
+
+/* Read block data, uncompressed and also store it in memory. */
+/* XXX Investigate possibility of in-place decompression to reduce memory
+ * footprint. */
+static int
+uncompress_block (struct grub_lzopio *lzopio)
+{
+ lzo_uint usize = lzopio->block.usize;
+
+ if (read_block_data (lzopio) < 0)
+ return -1;
+
+ if (lzopio->block.usize > lzopio->block.csize)
+ {
+ 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)
+ {
+ /* XXX Validate data checksum here. */
+ }
+
+ /* Compressed data can be free now. */
+ grub_free (lzopio->block.cdata);
+ lzopio->block.cdata = NULL;
+ }
+ /* Incompressible block of data. */
+ else if (lzopio->block.usize == lzopio->block.csize)
+ {
+ lzopio->block.udata = lzopio->block.cdata;
+ lzopio->block.cdata = NULL;
+ }
+ else
+ {
+ return -1;
+ }
+
+ 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;
+ }
+
+ lzopio->saved_off += lzopio->block.usize;
+
+ 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 0;
+
+ /* 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 0;
+ }
+
+ file->size = usize_total;
+
+ return 1;
+}
+
+/* XXX Do something with this function, it is insane now:) */
+static int
+test_header (grub_file_t file)
+{
+ grub_lzopio_t lzopio = file->data;
+ unsigned char magic[LZOP_MAGIC_SIZE];
+ grub_uint16_t lzopver, ver, ver_ext;
+ grub_uint8_t method, level, name_len;
+ grub_uint32_t flags, mode, filter, mtime_lo, mtime_hi, checksum;
+ unsigned char *name = NULL;
+
+ if (grub_file_read (lzopio->file, magic, sizeof (magic)) != sizeof (magic))
+ {
+ grub_error (GRUB_ERR_BAD_FILE_TYPE, "no lzop magic found");
+ return 0;
+ }
+
+ if (grub_memcmp (magic, LZOP_MAGIC, LZOP_MAGIC_SIZE) != 0)
+ {
+ grub_error (GRUB_ERR_BAD_FILE_TYPE, "no lzop magic found");
+ return 0;
+ }
+
+ /* LZOP version. */
+ if (grub_file_read (lzopio->file, &lzopver, sizeof (lzopver)) !=
+ sizeof (lzopver))
+ goto CORRUPTED;
+
+ /* LZO lib version. */
+ if (grub_file_read (lzopio->file, &ver, sizeof (ver)) != sizeof (ver))
+ goto CORRUPTED;
+
+ ver = grub_be_to_cpu16 (ver);
+
+ if (ver >= NEW_LZO_LIB)
+ {
+ /* Read version of lib needed to extract data. */
+ if (grub_file_read (lzopio->file, &ver_ext, sizeof (ver_ext)) !=
+ sizeof (ver_ext))
+ goto CORRUPTED;
+
+ /* Too new version, should upgrade minilzo? */
+ if (grub_be_to_cpu16 (ver_ext) > MINILZO_VERSION)
+ {
+ grub_error (GRUB_ERR_BAD_COMPRESSED_DATA,
+ "unsupported LZO version");
+ return 0;
+ }
+ }
+
+ if (grub_file_read (lzopio->file, &method, sizeof (method)) !=
+ sizeof (method))
+ goto CORRUPTED;
+
+ if (ver >= NEW_LZO_LIB)
+ {
+ if (grub_file_read (lzopio->file, &level, sizeof (level)) !=
+ sizeof (level))
+ goto CORRUPTED;
+ }
+
+ if (grub_file_read (lzopio->file, &flags, sizeof (flags)) != sizeof (flags))
+ goto CORRUPTED;
+
+ flags = grub_be_to_cpu32 (flags);
+
+ if (flags & F_CRC32_D)
+ lzopio->ucheck = 1;
+ else if (flags & F_ADLER32_D)
+ lzopio->ucheck = 2;
+
+ if (flags & F_CRC32_C)
+ lzopio->ccheck = 1;
+ else if (flags & F_ADLER32_C)
+ lzopio->ccheck = 2;
+
+ if (flags & F_H_FILTER)
+ {
+ if (grub_file_read (lzopio->file, &filter, sizeof (filter)) !=
+ sizeof (filter))
+ goto CORRUPTED;
+ }
+
+ if (grub_file_read (lzopio->file, &mode, sizeof (mode)) != sizeof (mode))
+ goto CORRUPTED;
+
+ if (grub_file_read (lzopio->file, &mtime_lo, sizeof (mtime_lo)) !=
+ sizeof (mtime_lo))
+ goto CORRUPTED;
+
+ if (ver >= NEW_LZO_LIB)
+ {
+ if (grub_file_read (lzopio->file, &mtime_hi, sizeof (mtime_hi)) !=
+ sizeof (mtime_hi))
+ goto CORRUPTED;
+ }
+
+ if (grub_file_read (lzopio->file, &name_len, sizeof (name_len)) !=
+ sizeof (name_len))
+ goto CORRUPTED;
+
+ if (name_len != 0)
+ {
+ name = grub_malloc (name_len);
+ if (!name)
+ return 0;
+
+ if (grub_file_read (lzopio->file, name, name_len) != name_len)
+ {
+ grub_free (name);
+ goto CORRUPTED;
+ }
+ }
+
+ if (grub_file_read (lzopio->file, &checksum, sizeof (checksum)) !=
+ sizeof (checksum))
+ {
+ grub_free (name);
+ goto CORRUPTED;
+ }
+
+ grub_free (name);
+
+ /* XXX Validate header checksum here. */
+ if (checksum == checksum)
+ {
+ lzopio->start_block_off = grub_file_tell (lzopio->file);
+
+ if (calculate_uncompressed_size (file) < 0)
+ goto CORRUPTED;
+
+ lzopio->saved_off = 0;
+
+ /* 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;
+
+ return 1;
+ }
+
+CORRUPTED:
+ grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "lzop file corrupted");
+ return 0;
+}
+
+static grub_file_t
+grub_lzopio_open (grub_file_t io)
+{
+ grub_file_t file;
+ grub_lzopio_t lzopio;
+
+ 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->offset = 0;
+ file->data = lzopio;
+ file->read_hook = 0;
+ 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;
+
+ /* Backward seek before last read block. */
+ if (lzopio->saved_off > grub_file_tell (file))
+ {
+ lzopio->saved_off = 0;
+ grub_file_seek (lzopio->file, lzopio->start_block_off);
+
+ if (read_block_header (lzopio) < 0)
+ goto CORRUPTED;
+ }
+
+ /* 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;
+ }
+
+ while (len != 0 && lzopio->block.usize != 0)
+ {
+ grub_off_t off = grub_file_tell (file) - lzopio->saved_off;
+ long to_copy;
+
+ /* Block not decompressed yet. */
+ if (!lzopio->block.udata && uncompress_block (lzopio) < 0)
+ goto CORRUPTED;
+
+ /* Copy requested data into buffer. */
+ to_copy = grub_min (lzopio->block.usize - off, len);
+ grub_memcpy (buf, lzopio->block.udata + off, to_copy);
+
+ len -= to_copy;
+ buf += to_copy;
+ ret += to_copy;
+
+ /* Read to next block if needed. */
+ if (len > 0 && read_block_header (lzopio) < 0)
+ goto CORRUPTED;
+ }
+
+ return ret;
+
+CORRUPTED:
+ grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "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;
+ return grub_errno;
+}
+
+static struct grub_fs grub_lzopio_fs = {
+ .name = "lzopio",
+ .dir = 0,
+ .open = 0,
+ .read = grub_lzopio_read,
+ .close = grub_lzopio_close,
+ .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);
+}