2N/A/* lzopio.c - decompression support for lzop */
2N/A/*
2N/A * GRUB -- GRand Unified Bootloader
2N/A * Copyright (C) 2011 Free Software Foundation, Inc.
2N/A *
2N/A * GRUB is free software: you can redistribute it and/or modify
2N/A * it under the terms of the GNU General Public License as published by
2N/A * the Free Software Foundation, either version 3 of the License, or
2N/A * (at your option) any later version.
2N/A *
2N/A * GRUB is distributed in the hope that it will be useful,
2N/A * but WITHOUT ANY WARRANTY; without even the implied warranty of
2N/A * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2N/A * GNU General Public License for more details.
2N/A *
2N/A * You should have received a copy of the GNU General Public License
2N/A * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
2N/A */
2N/A
2N/A#include <grub/err.h>
2N/A#include <grub/mm.h>
2N/A#include <grub/file.h>
2N/A#include <grub/fs.h>
2N/A#include <grub/dl.h>
2N/A#include <grub/crypto.h>
2N/A#include <minilzo.h>
2N/A
2N/AGRUB_MOD_LICENSE ("GPLv3+");
2N/A
2N/A#define LZOP_MAGIC "\x89\x4c\x5a\x4f\x00\x0d\x0a\x1a\x0a"
2N/A#define LZOP_MAGIC_SIZE 9
2N/A#define LZOP_CHECK_SIZE 4
2N/A#define LZOP_NEW_LIB 0x0940
2N/A
2N/A/* Header flags - copied from conf.h of LZOP source code. */
2N/A#define F_ADLER32_D 0x00000001L
2N/A#define F_ADLER32_C 0x00000002L
2N/A#define F_STDIN 0x00000004L
2N/A#define F_STDOUT 0x00000008L
2N/A#define F_NAME_DEFAULT 0x00000010L
2N/A#define F_DOSISH 0x00000020L
2N/A#define F_H_EXTRA_FIELD 0x00000040L
2N/A#define F_H_GMTDIFF 0x00000080L
2N/A#define F_CRC32_D 0x00000100L
2N/A#define F_CRC32_C 0x00000200L
2N/A#define F_MULTIPART 0x00000400L
2N/A#define F_H_FILTER 0x00000800L
2N/A#define F_H_CRC32 0x00001000L
2N/A#define F_H_PATH 0x00002000L
2N/A#define F_MASK 0x00003FFFL
2N/A
2N/Astruct block_header
2N/A{
2N/A grub_uint32_t usize;
2N/A grub_uint32_t csize;
2N/A grub_uint32_t ucheck;
2N/A grub_uint32_t ccheck;
2N/A unsigned char *cdata;
2N/A unsigned char *udata;
2N/A};
2N/A
2N/Astruct grub_lzopio
2N/A{
2N/A grub_file_t file;
2N/A int has_ccheck;
2N/A int has_ucheck;
2N/A const gcry_md_spec_t *ucheck_fun;
2N/A const gcry_md_spec_t *ccheck_fun;
2N/A grub_off_t saved_off; /* Rounded down to block boundary. */
2N/A grub_off_t start_block_off;
2N/A struct block_header block;
2N/A};
2N/A
2N/Atypedef struct grub_lzopio *grub_lzopio_t;
2N/Astatic struct grub_fs grub_lzopio_fs;
2N/A
2N/A/* Some helper functions. On errors memory allocated by those function is free
2N/A * either on close() so no risk of leaks. This makes functions simpler. */
2N/A
2N/A/* Read block header from file, after successful exit file points to
2N/A * beginning of block data. */
2N/Astatic int
2N/Aread_block_header (struct grub_lzopio *lzopio)
2N/A{
2N/A lzopio->saved_off += lzopio->block.usize;
2N/A
2N/A /* Free cached block data if any. */
2N/A grub_free (lzopio->block.udata);
2N/A grub_free (lzopio->block.cdata);
2N/A lzopio->block.udata = NULL;
2N/A lzopio->block.cdata = NULL;
2N/A
2N/A if (grub_file_read (lzopio->file, &lzopio->block.usize,
2N/A sizeof (lzopio->block.usize)) !=
2N/A sizeof (lzopio->block.usize))
2N/A return -1;
2N/A
2N/A lzopio->block.usize = grub_be_to_cpu32 (lzopio->block.usize);
2N/A
2N/A /* Last block has uncompressed data size == 0 and no other fields. */
2N/A if (lzopio->block.usize == 0)
2N/A {
2N/A if (grub_file_tell (lzopio->file) == grub_file_size (lzopio->file))
2N/A return 0;
2N/A else
2N/A return -1;
2N/A }
2N/A
2N/A /* Read compressed data block size. */
2N/A if (grub_file_read (lzopio->file, &lzopio->block.csize,
2N/A sizeof (lzopio->block.csize)) !=
2N/A sizeof (lzopio->block.csize))
2N/A return -1;
2N/A
2N/A lzopio->block.csize = grub_be_to_cpu32 (lzopio->block.csize);
2N/A
2N/A /* Corrupted. */
2N/A if (lzopio->block.csize > lzopio->block.usize)
2N/A return -1;
2N/A
2N/A /* Read checksum of uncompressed data. */
2N/A if (lzopio->has_ucheck)
2N/A {
2N/A if (grub_file_read (lzopio->file, &lzopio->block.ucheck,
2N/A sizeof (lzopio->block.ucheck)) !=
2N/A sizeof (lzopio->block.ucheck))
2N/A return -1;
2N/A
2N/A lzopio->block.ucheck = grub_be_to_cpu32 (lzopio->block.ucheck);
2N/A }
2N/A
2N/A /* Read checksum of compressed data. */
2N/A if (lzopio->has_ccheck)
2N/A {
2N/A /* Incompressible data block. */
2N/A if (lzopio->block.csize == lzopio->block.usize)
2N/A {
2N/A lzopio->block.ccheck = lzopio->block.ucheck;
2N/A }
2N/A else
2N/A {
2N/A if (grub_file_read (lzopio->file, &lzopio->block.ccheck,
2N/A sizeof (lzopio->block.ccheck)) !=
2N/A sizeof (lzopio->block.ccheck))
2N/A return -1;
2N/A
2N/A lzopio->block.ccheck = grub_be_to_cpu32 (lzopio->block.ccheck);
2N/A }
2N/A }
2N/A
2N/A return 0;
2N/A}
2N/A
2N/A/* Read block data into memory. File must be set to beginning of block data.
2N/A * Can't be called on last block. */
2N/Astatic int
2N/Aread_block_data (struct grub_lzopio *lzopio)
2N/A{
2N/A lzopio->block.cdata = grub_malloc (lzopio->block.csize);
2N/A if (!lzopio->block.cdata)
2N/A return -1;
2N/A
2N/A if (grub_file_read (lzopio->file, lzopio->block.cdata, lzopio->block.csize)
2N/A != (grub_ssize_t) lzopio->block.csize)
2N/A return -1;
2N/A
2N/A if (lzopio->ccheck_fun)
2N/A {
2N/A grub_uint64_t context[(lzopio->ccheck_fun->contextsize + 7) / 8];
2N/A
2N/A lzopio->ccheck_fun->init (context);
2N/A lzopio->ccheck_fun->write (context, lzopio->block.cdata,
2N/A lzopio->block.csize);
2N/A lzopio->ccheck_fun->final (context);
2N/A
2N/A if (grub_memcmp
2N/A (lzopio->ccheck_fun->read (context), &lzopio->block.ccheck,
2N/A sizeof (lzopio->block.ccheck)) != 0)
2N/A return -1;
2N/A }
2N/A
2N/A return 0;
2N/A}
2N/A
2N/A/* Read block data, uncompressed and also store it in memory. */
2N/A/* XXX Investigate possibility of in-place decompression to reduce memory
2N/A * footprint. Or try to uncompress directly to buf if possible. */
2N/Astatic int
2N/Auncompress_block (struct grub_lzopio *lzopio)
2N/A{
2N/A lzo_uint usize = lzopio->block.usize;
2N/A
2N/A if (read_block_data (lzopio) < 0)
2N/A return -1;
2N/A
2N/A /* Incompressible data. */
2N/A if (lzopio->block.csize == lzopio->block.usize)
2N/A {
2N/A lzopio->block.udata = lzopio->block.cdata;
2N/A lzopio->block.cdata = NULL;
2N/A }
2N/A else
2N/A {
2N/A lzopio->block.udata = grub_malloc (lzopio->block.usize);
2N/A if (!lzopio->block.udata)
2N/A return -1;
2N/A
2N/A if (lzo1x_decompress_safe (lzopio->block.cdata, lzopio->block.csize,
2N/A lzopio->block.udata, &usize, NULL)
2N/A != LZO_E_OK)
2N/A return -1;
2N/A
2N/A if (lzopio->ucheck_fun)
2N/A {
2N/A grub_uint64_t context[(lzopio->ccheck_fun->contextsize + 7) / 8];
2N/A
2N/A lzopio->ucheck_fun->init (context);
2N/A lzopio->ucheck_fun->write (context, lzopio->block.udata,
2N/A lzopio->block.usize);
2N/A lzopio->ucheck_fun->final (context);
2N/A
2N/A if (grub_memcmp
2N/A (lzopio->ucheck_fun->read (context), &lzopio->block.ucheck,
2N/A sizeof (lzopio->block.ucheck)) != 0)
2N/A return -1;
2N/A }
2N/A
2N/A /* Compressed data can be free now. */
2N/A grub_free (lzopio->block.cdata);
2N/A lzopio->block.cdata = NULL;
2N/A }
2N/A
2N/A return 0;
2N/A}
2N/A
2N/A/* Jump to next block and read its header. */
2N/Astatic int
2N/Ajump_block (struct grub_lzopio *lzopio)
2N/A{
2N/A /* only jump if block was not decompressed (and read from disk) */
2N/A if (!lzopio->block.udata)
2N/A {
2N/A grub_off_t off = grub_file_tell (lzopio->file) + lzopio->block.csize;
2N/A
2N/A if (grub_file_seek (lzopio->file, off) == ((grub_off_t) - 1))
2N/A return -1;
2N/A }
2N/A
2N/A return read_block_header (lzopio);
2N/A}
2N/A
2N/Astatic int
2N/Acalculate_uncompressed_size (grub_file_t file)
2N/A{
2N/A grub_lzopio_t lzopio = file->data;
2N/A grub_off_t usize_total = 0;
2N/A
2N/A if (read_block_header (lzopio) < 0)
2N/A return -1;
2N/A
2N/A /* FIXME: Don't do this for not easily seekable files. */
2N/A while (lzopio->block.usize != 0)
2N/A {
2N/A usize_total += lzopio->block.usize;
2N/A
2N/A if (jump_block (lzopio) < 0)
2N/A return -1;
2N/A }
2N/A
2N/A file->size = usize_total;
2N/A
2N/A return 0;
2N/A}
2N/A
2N/Astruct lzop_header
2N/A{
2N/A grub_uint8_t magic[LZOP_MAGIC_SIZE];
2N/A grub_uint16_t lzop_version;
2N/A grub_uint16_t lib_version;
2N/A grub_uint16_t lib_version_ext;
2N/A grub_uint8_t method;
2N/A grub_uint8_t level;
2N/A grub_uint32_t flags;
2N/A /* grub_uint32_t filter; */ /* No filters support. Rarely used anyway. */
2N/A grub_uint32_t mode;
2N/A grub_uint32_t mtime_lo;
2N/A grub_uint32_t mtime_hi;
2N/A grub_uint8_t name_len;
2N/A} __attribute__ ((packed));
2N/A
2N/Astatic int
2N/Atest_header (grub_file_t file)
2N/A{
2N/A grub_lzopio_t lzopio = file->data;
2N/A struct lzop_header header;
2N/A grub_uint32_t flags, checksum;
2N/A const gcry_md_spec_t *hcheck;
2N/A grub_uint8_t *context = NULL;
2N/A grub_uint8_t *name = NULL;
2N/A
2N/A if (grub_file_read (lzopio->file, &header, sizeof (header)) != sizeof (header))
2N/A {
2N/A grub_error (GRUB_ERR_BAD_FILE_TYPE, "no lzop magic found");
2N/A return 0;
2N/A }
2N/A
2N/A if (grub_memcmp (header.magic, LZOP_MAGIC, LZOP_MAGIC_SIZE) != 0)
2N/A {
2N/A grub_error (GRUB_ERR_BAD_FILE_TYPE, "no lzop magic found");
2N/A return 0;
2N/A }
2N/A
2N/A if (grub_be_to_cpu16(header.lib_version) < LZOP_NEW_LIB)
2N/A {
2N/A grub_error (GRUB_ERR_BAD_COMPRESSED_DATA,
2N/A "unsupported (too old) LZOP version");
2N/A return 0;
2N/A }
2N/A
2N/A /* Too new version, should upgrade minilzo? */
2N/A if (grub_be_to_cpu16 (header.lib_version_ext) > MINILZO_VERSION)
2N/A {
2N/A grub_error (GRUB_ERR_BAD_COMPRESSED_DATA,
2N/A "unsupported (too new) LZO version");
2N/A return 0;
2N/A }
2N/A
2N/A flags = grub_be_to_cpu32 (header.flags);
2N/A
2N/A if (flags & F_CRC32_D)
2N/A {
2N/A lzopio->has_ucheck = 1;
2N/A lzopio->ucheck_fun = grub_crypto_lookup_md_by_name ("crc32");
2N/A }
2N/A else if (flags & F_ADLER32_D)
2N/A {
2N/A lzopio->has_ucheck = 1;
2N/A lzopio->ucheck_fun = grub_crypto_lookup_md_by_name ("adler32");
2N/A }
2N/A
2N/A if (flags & F_CRC32_C)
2N/A {
2N/A lzopio->has_ccheck = 1;
2N/A lzopio->ccheck_fun = grub_crypto_lookup_md_by_name ("crc32");
2N/A }
2N/A else if (flags & F_ADLER32_C)
2N/A {
2N/A lzopio->has_ccheck = 1;
2N/A lzopio->ccheck_fun = grub_crypto_lookup_md_by_name ("adler32");
2N/A }
2N/A
2N/A if (flags & F_H_CRC32)
2N/A hcheck = grub_crypto_lookup_md_by_name ("crc32");
2N/A else
2N/A hcheck = grub_crypto_lookup_md_by_name ("adler32");
2N/A
2N/A if (hcheck) {
2N/A context = grub_malloc(hcheck->contextsize);
2N/A if (! context)
2N/A return 0;
2N/A
2N/A hcheck->init(context);
2N/A
2N/A /* MAGIC is not included in check calculation. */
2N/A hcheck->write(context, &header.lzop_version, sizeof(header)- LZOP_MAGIC_SIZE);
2N/A }
2N/A
2N/A if (header.name_len != 0)
2N/A {
2N/A name = grub_malloc (header.name_len);
2N/A if (! name)
2N/A {
2N/A grub_free (context);
2N/A return 0;
2N/A }
2N/A
2N/A if (grub_file_read (lzopio->file, name, header.name_len) !=
2N/A header.name_len)
2N/A {
2N/A grub_free(name);
2N/A goto CORRUPTED;
2N/A }
2N/A
2N/A if (hcheck)
2N/A hcheck->write(context, name, header.name_len);
2N/A
2N/A grub_free(name);
2N/A }
2N/A
2N/A if (hcheck)
2N/A hcheck->final(context);
2N/A
2N/A if (grub_file_read (lzopio->file, &checksum, sizeof (checksum)) !=
2N/A sizeof (checksum))
2N/A goto CORRUPTED;
2N/A
2N/A if (hcheck)
2N/A {
2N/A checksum = grub_cpu_to_be32(checksum);
2N/A if (grub_memcmp (&checksum, hcheck->read(context), sizeof(checksum)) != 0)
2N/A goto CORRUPTED;
2N/A }
2N/A
2N/A lzopio->start_block_off = grub_file_tell (lzopio->file);
2N/A
2N/A if (calculate_uncompressed_size (file) < 0)
2N/A goto CORRUPTED;
2N/A
2N/A /* Get back to start block. */
2N/A grub_file_seek (lzopio->file, lzopio->start_block_off);
2N/A
2N/A /* Read first block - grub_lzopio_read() expects valid block. */
2N/A if (read_block_header (lzopio) < 0)
2N/A goto CORRUPTED;
2N/A
2N/A lzopio->saved_off = 0;
2N/A return 1;
2N/A
2N/ACORRUPTED:
2N/A grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "lzop file corrupted");
2N/A
2N/A grub_free(name);
2N/A
2N/A return 0;
2N/A}
2N/A
2N/Astatic grub_file_t
2N/Agrub_lzopio_open (grub_file_t io)
2N/A{
2N/A grub_file_t file;
2N/A grub_lzopio_t lzopio;
2N/A
2N/A file = (grub_file_t) grub_zalloc (sizeof (*file));
2N/A if (!file)
2N/A return 0;
2N/A
2N/A lzopio = grub_zalloc (sizeof (*lzopio));
2N/A if (!lzopio)
2N/A {
2N/A grub_free (file);
2N/A return 0;
2N/A }
2N/A
2N/A lzopio->file = io;
2N/A
2N/A file->device = io->device;
2N/A file->offset = 0;
2N/A file->data = lzopio;
2N/A file->read_hook = 0;
2N/A file->fs = &grub_lzopio_fs;
2N/A file->size = GRUB_FILE_SIZE_UNKNOWN;
2N/A file->not_easily_seekable = 1;
2N/A
2N/A if (grub_file_tell (lzopio->file) != 0)
2N/A grub_file_seek (lzopio->file, 0);
2N/A
2N/A if (!test_header (file))
2N/A {
2N/A grub_errno = GRUB_ERR_NONE;
2N/A grub_file_seek (io, 0);
2N/A grub_free (lzopio);
2N/A grub_free (file);
2N/A
2N/A return io;
2N/A }
2N/A
2N/A return file;
2N/A}
2N/A
2N/Astatic grub_ssize_t
2N/Agrub_lzopio_read (grub_file_t file, char *buf, grub_size_t len)
2N/A{
2N/A grub_lzopio_t lzopio = file->data;
2N/A grub_ssize_t ret = 0;
2N/A grub_off_t off;
2N/A
2N/A /* Backward seek before last read block. */
2N/A if (lzopio->saved_off > grub_file_tell (file))
2N/A {
2N/A grub_file_seek (lzopio->file, lzopio->start_block_off);
2N/A
2N/A if (read_block_header (lzopio) < 0)
2N/A goto CORRUPTED;
2N/A
2N/A lzopio->saved_off = 0;
2N/A }
2N/A
2N/A /* Forward to first block with requested data. */
2N/A while (lzopio->saved_off + lzopio->block.usize <= grub_file_tell (file))
2N/A {
2N/A /* EOF, could be possible files with unknown size. */
2N/A if (lzopio->block.usize == 0)
2N/A return 0;
2N/A
2N/A if (jump_block (lzopio) < 0)
2N/A goto CORRUPTED;
2N/A }
2N/A
2N/A off = grub_file_tell (file) - lzopio->saved_off;
2N/A
2N/A while (len != 0 && lzopio->block.usize != 0)
2N/A {
2N/A grub_size_t to_copy;
2N/A
2N/A /* Block not decompressed yet. */
2N/A if (!lzopio->block.udata && uncompress_block (lzopio) < 0)
2N/A goto CORRUPTED;
2N/A
2N/A /* Copy requested data into buffer. */
2N/A to_copy = lzopio->block.usize - off;
2N/A if (to_copy > len)
2N/A to_copy = len;
2N/A grub_memcpy (buf, lzopio->block.udata + off, to_copy);
2N/A
2N/A len -= to_copy;
2N/A buf += to_copy;
2N/A ret += to_copy;
2N/A off = 0;
2N/A
2N/A /* Read next block if needed. */
2N/A if (len > 0 && read_block_header (lzopio) < 0)
2N/A goto CORRUPTED;
2N/A }
2N/A
2N/A return ret;
2N/A
2N/ACORRUPTED:
2N/A grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "lzop file corrupted");
2N/A return -1;
2N/A}
2N/A
2N/A/* Release everything, including the underlying file object. */
2N/Astatic grub_err_t
2N/Agrub_lzopio_close (grub_file_t file)
2N/A{
2N/A grub_lzopio_t lzopio = file->data;
2N/A
2N/A grub_file_close (lzopio->file);
2N/A grub_free (lzopio->block.cdata);
2N/A grub_free (lzopio->block.udata);
2N/A grub_free (lzopio);
2N/A
2N/A /* Device must not be closed twice. */
2N/A file->device = 0;
2N/A return grub_errno;
2N/A}
2N/A
2N/Astatic struct grub_fs grub_lzopio_fs = {
2N/A .name = "lzopio",
2N/A .dir = 0,
2N/A .open = 0,
2N/A .read = grub_lzopio_read,
2N/A .close = grub_lzopio_close,
2N/A .label = 0,
2N/A .next = 0
2N/A};
2N/A
2N/AGRUB_MOD_INIT (lzopio)
2N/A{
2N/A grub_file_filter_register (GRUB_FILE_FILTER_LZOPIO, grub_lzopio_open);
2N/A}
2N/A
2N/AGRUB_MOD_FINI (lzopio)
2N/A{
2N/A grub_file_filter_unregister (GRUB_FILE_FILTER_LZOPIO);
2N/A}