]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
Add initial support for lzop files decompression.
authorSzymon Janc <szymon@janc.net.pl>
Wed, 17 Aug 2011 17:40:25 +0000 (19:40 +0200)
committerSzymon Janc <szymon@janc.net.pl>
Wed, 17 Aug 2011 17:40:25 +0000 (19:40 +0200)
* grub-core/Makefile.core.def (lzopio): New module.
* grub-core/io/lzopio.c: New file.
* include/grub/file.h (grub_file_filter_id): New compression filter
GRUB_FILE_FILTER_LZOPIO.

ChangeLog.lzo
grub-core/Makefile.core.def
grub-core/io/lzopio.c [new file with mode: 0644]
include/grub/file.h

index c892553893626790d79f136ece90121f2198bb9d..782c6b60d4bb460f331e6380df44066a13c444f3 100644 (file)
@@ -1,3 +1,12 @@
+2011-08-17  Szymon Janc <szymon@janc.net.pl>
+
+       Add initial support for lzop files decompression.
+
+       * grub-core/Makefile.core.def (lzopio): New module.
+       * grub-core/io/lzopio.c: New file.
+       * include/grub/file.h (grub_file_filter_id): New compression filter
+       GRUB_FILE_FILTER_LZOPIO.
+
 2011-08-14  Szymon Janc <szymon@janc.net.pl>
 
        Add support for LZO compression in btrfs.
index 1ff179c6770fda67e60fa5187744a688304c04c0..67959d861a4622729c75a06f236ffef8853f7788 100644 (file)
@@ -1651,6 +1651,14 @@ module = {
   cppflags = '-I$(srcdir)/lib/posix_wrap -I$(srcdir)/lib/xzembed';
 };
 
+module = {
+  name = lzopio;
+  common = io/lzopio.c;
+  common = lib/minilzo/minilzo.c;
+  cflags = '$(CFLAGS_POSIX) -Wno-undef';
+  cppflags = '-I$(srcdir)/lib/posix_wrap -I$(srcdir)/lib/minilzo -DMINILZO_HAVE_CONFIG_H';
+};
+
 module = {
   name = testload;
   common = commands/testload.c;
diff --git a/grub-core/io/lzopio.c b/grub-core/io/lzopio.c
new file mode 100644 (file)
index 0000000..4ccc41a
--- /dev/null
@@ -0,0 +1,527 @@
+/* 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);
+}
index 3adb1706f520bcadc3094cd78e09dd98db4989e9..8fe87e6b4a56d6bf1aa522703bdf2264015c5db5 100644 (file)
@@ -56,6 +56,7 @@ typedef enum grub_file_filter_id
   {
     GRUB_FILE_FILTER_GZIO,
     GRUB_FILE_FILTER_XZIO,
+    GRUB_FILE_FILTER_LZOPIO,
     GRUB_FILE_FILTER_MAX,
     GRUB_FILE_FILTER_COMPRESSION_FIRST = GRUB_FILE_FILTER_GZIO,
     GRUB_FILE_FILTER_COMPRESSION_LAST = GRUB_FILE_FILTER_XZIO,