]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
BtrFS zlib compression support
authorVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Fri, 3 Dec 2010 20:42:13 +0000 (21:42 +0100)
committerVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Fri, 3 Dec 2010 20:42:13 +0000 (21:42 +0100)
Makefile.util.def
grub-core/fs/btrfs.c
grub-core/io/gzio.c
include/grub/deflate.h [new file with mode: 0644]

index 4d642a2b61b1495f16a550813c2d29d1c3de8911..f3cd1d234776dd96ac493706a39433517f60bcc6 100644 (file)
@@ -95,6 +95,7 @@ library = {
   common = grub-core/script/main.c;
   common = grub-core/script/script.c;
   common = grub-core/script/argv.c;
+  common = grub-core/io/gzio.c;
 };
 
 program = {
index e6bab83aac5416a9b7abf7c6b48f6561819a1ba7..7e31fa3f15cd5a4483ed94ffbb42f0b4c8861392 100644 (file)
@@ -25,6 +25,7 @@
 #include <grub/dl.h>
 #include <grub/types.h>
 #include <grub/lib/crc.h>
+#include <grub/deflate.h>
 
 #define GRUB_BTRFS_SIGNATURE "_BHRfS_M"
 
@@ -84,6 +85,7 @@ struct grub_btrfs_data
   grub_uint64_t extstart;
   grub_uint64_t extino;
   grub_uint64_t exttree;
+  grub_size_t extsize;
   struct grub_btrfs_extent_data *extent;
 };
 
@@ -187,13 +189,20 @@ struct grub_btrfs_extent_data
   union
   {
     char inl[0];
-    grub_uint64_t laddr;
+    struct
+    {
+      grub_uint64_t laddr;
+      grub_uint64_t compressed_size;
+      grub_uint64_t offset;
+    };
   };
 } __attribute__ ((packed));
 
 #define GRUB_BTRFS_EXTENT_INLINE 0
 #define GRUB_BTRFS_EXTENT_REGULAR 1
 
+#define GRUB_BTRFS_COMPRESSION_NONE 0
+#define GRUB_BTRFS_COMPRESSION_ZLIB 1
 
 #define GRUB_BTRFS_OBJECT_ID_CHUNK 0x100
 
@@ -840,6 +849,7 @@ grub_btrfs_extent_read (struct grub_btrfs_data *data,
              return -1;
            }
          data->extstart = grub_le_to_cpu64 (key_out.offset);
+         data->extsize = elemsize;
          data->extent = grub_malloc (elemsize);
          data->extino = ino;
          data->exttree = tree;
@@ -870,14 +880,15 @@ grub_btrfs_extent_read (struct grub_btrfs_data *data,
          return -1;
        }
 
-      if (data->extent->compression)
+      if (data->extent->compression != GRUB_BTRFS_COMPRESSION_NONE
+         && data->extent->compression != GRUB_BTRFS_COMPRESSION_ZLIB)
        {
          grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
-                     "compression not supported");
+                     "compression type 0x%x not supported",
+                     data->extent->compression);
          return -1;
        }
 
-
       if (data->extent->encoding)
        {
          grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
@@ -888,7 +899,17 @@ grub_btrfs_extent_read (struct grub_btrfs_data *data,
       switch (data->extent->type)
        {
        case GRUB_BTRFS_EXTENT_INLINE:
-         grub_memcpy (buf, data->extent->inl + extoff, csize);
+         if (data->extent->compression == GRUB_BTRFS_COMPRESSION_ZLIB)
+           {
+             if (grub_zlib_decompress (data->extent->inl, data->extsize -
+                                       ((grub_uint8_t *) data->extent->inl
+                                        - (grub_uint8_t *) data->extent),
+                                       extoff, buf, csize)
+                 != (grub_ssize_t) csize)
+               return -1;
+           }
+         else
+           grub_memcpy (buf, data->extent->inl + extoff, csize);
          break;
        case GRUB_BTRFS_EXTENT_REGULAR:
          if (!data->extent->laddr)
@@ -896,6 +917,32 @@ grub_btrfs_extent_read (struct grub_btrfs_data *data,
              grub_memset (buf, 0, csize);
              break;
            }
+         if (data->extent->compression == GRUB_BTRFS_COMPRESSION_ZLIB)
+           {
+             char *tmp;
+             grub_uint64_t zsize;
+             zsize = grub_le_to_cpu64 (data->extent->compressed_size);
+             tmp = grub_malloc (zsize);
+             if (!tmp)
+               return -1;
+             err = grub_btrfs_read_logical (data,
+                                            grub_le_to_cpu64 (data->extent->laddr),
+                                            tmp, zsize);
+             if (err)
+               {
+                 grub_free (tmp);
+                 return -1;
+               }
+             if (grub_zlib_decompress (tmp, zsize, extoff
+                                       + grub_le_to_cpu64 (data->extent->offset),
+                                       buf, csize) != (grub_ssize_t) csize)
+               {
+                 grub_free (tmp);
+                 return -1;
+               }
+             grub_free (tmp);
+             break;
+           }
          err = grub_btrfs_read_logical (data,
                                         grub_le_to_cpu64 (data->extent->laddr)
                                         + extoff,
index 43b67c3738594cd638e8377f25b55e4f8ce1597e..248a1750e2ad9be8c1d9e0283bc6a9f3f0053062 100644 (file)
@@ -41,6 +41,7 @@
 #include <grub/fs.h>
 #include <grub/file.h>
 #include <grub/dl.h>
+#include <grub/deflate.h>
 
 /*
  *  Window Size
@@ -58,6 +59,9 @@ 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.  */
@@ -100,7 +104,7 @@ typedef struct grub_gzio *grub_gzio_t;
 static struct grub_fs grub_gzio_fs;
 
 /* Function prototypes */
-static void initialize_tables (grub_file_t file);
+static void initialize_tables (grub_gzio_t);
 
 /* Eat variable-length header fields.  */
 static int
@@ -162,7 +166,7 @@ typedef unsigned short ush;
 typedef unsigned long ulg;
 
 static int
-test_header (grub_file_t file)
+test_gzip_header (grub_file_t file)
 {
   struct {
     grub_uint16_t magic;
@@ -226,7 +230,7 @@ test_header (grub_file_t file)
      But how can we know the real original size?  */
   file->size = grub_le_to_cpu32 (orig_len);
 
-  initialize_tables (file);
+  initialize_tables (gzio);
 
   return 1;
 }
@@ -366,13 +370,18 @@ static ush mask_bits[] =
   0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff
 };
 
-#define NEEDBITS(n) do {while(k<(n)){b|=((ulg)get_byte(file))<<k;k+=8;}} while (0)
+#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_file_t file)
+get_byte (grub_gzio_t gzio)
 {
-  grub_gzio_t gzio = file->data;
+  if (gzio->mem_input)
+    {
+      if (gzio->mem_input_off < gzio->mem_input_size)
+       return gzio->mem_input[gzio->mem_input_off++];
+      return 0;
+    }
 
   if (grub_file_tell (gzio->file) == (grub_off_t) gzio->data_offset
       || gzio->inbuf_d == INBUFSIZ)
@@ -384,11 +393,26 @@ get_byte (grub_file_t file)
   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,
+                   "attempt to seek outside of the file");
+      else
+       gzio->mem_input_off = gzio->data_offset;
+    }
+  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_file_t);
+static int inflate_codes_in_window (grub_gzio_t);
 
 
 /* Given a list of code lengths and a maximum table size, make a set of
@@ -615,7 +639,7 @@ huft_free (struct huft *t)
  */
 
 static int
-inflate_codes_in_window (grub_file_t file)
+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 */
@@ -624,7 +648,6 @@ inflate_codes_in_window (grub_file_t file)
   unsigned ml, md;             /* masks for bl and bd bits */
   register ulg b;              /* bit buffer */
   register unsigned k;         /* number of bits in bit buffer */
-  grub_gzio_t gzio = file->data;
 
   /* make local copies of globals */
   d = gzio->inflate_d;
@@ -752,11 +775,10 @@ inflate_codes_in_window (grub_file_t file)
 /* get header for an inflated type 0 (stored) block. */
 
 static void
-init_stored_block (grub_file_t file)
+init_stored_block (grub_gzio_t gzio)
 {
   register ulg b;              /* bit buffer */
   register unsigned k;         /* number of bits in bit buffer */
-  grub_gzio_t gzio = file->data;
 
   /* make local copies of globals */
   b = gzio->bb;                        /* initialize bit buffer */
@@ -786,11 +808,10 @@ init_stored_block (grub_file_t file)
    Huffman tables. */
 
 static void
-init_fixed_block (grub_file_t file)
+init_fixed_block (grub_gzio_t gzio)
 {
   int i;                       /* temporary variable */
   unsigned l[288];             /* length list for huft_build */
-  grub_gzio_t gzio = file->data;
 
   /* set up literal table */
   for (i = 0; i < 144; i++)
@@ -833,7 +854,7 @@ init_fixed_block (grub_file_t file)
 /* get header for an inflated type 2 (dynamic Huffman codes) block. */
 
 static void
-init_dynamic_block (grub_file_t file)
+init_dynamic_block (grub_gzio_t gzio)
 {
   int i;                       /* temporary variables */
   unsigned j;
@@ -846,7 +867,6 @@ init_dynamic_block (grub_file_t file)
   unsigned ll[286 + 30];       /* literal/length and distance code lengths */
   register ulg b;              /* bit buffer */
   register unsigned k;         /* number of bits in bit buffer */
-  grub_gzio_t gzio = file->data;
 
   /* make local bit buffer */
   b = gzio->bb;
@@ -977,11 +997,10 @@ init_dynamic_block (grub_file_t file)
 
 
 static void
-get_new_block (grub_file_t file)
+get_new_block (grub_gzio_t gzio)
 {
   register ulg b;              /* bit buffer */
   register unsigned k;         /* number of bits in bit buffer */
-  grub_gzio_t gzio = file->data;
 
   /* make local bit buffer */
   b = gzio->bb;
@@ -1004,13 +1023,13 @@ get_new_block (grub_file_t file)
   switch (gzio->block_type)
     {
     case INFLATE_STORED:
-      init_stored_block (file);
+      init_stored_block (gzio);
       break;
     case INFLATE_FIXED:
-      init_fixed_block (file);
+      init_fixed_block (gzio);
       break;
     case INFLATE_DYNAMIC:
-      init_dynamic_block (file);
+      init_dynamic_block (gzio);
       break;
     default:
       break;
@@ -1019,10 +1038,8 @@ get_new_block (grub_file_t file)
 
 
 static void
-inflate_window (grub_file_t file)
+inflate_window (grub_gzio_t gzio)
 {
-  grub_gzio_t gzio = file->data;
-
   /* initialize window */
   gzio->wp = 0;
 
@@ -1037,7 +1054,7 @@ inflate_window (grub_file_t file)
          if (gzio->last_block)
            break;
 
-         get_new_block (file);
+         get_new_block (gzio);
        }
 
       if (gzio->block_type > INFLATE_DYNAMIC)
@@ -1060,7 +1077,7 @@ inflate_window (grub_file_t file)
 
          while (gzio->block_len && w < WSIZE && grub_errno == GRUB_ERR_NONE)
            {
-             gzio->slide[w++] = get_byte (file);
+             gzio->slide[w++] = get_byte (gzio);
              gzio->block_len--;
            }
 
@@ -1073,7 +1090,7 @@ inflate_window (grub_file_t file)
        *  Expand other kind of block.
        */
 
-      if (inflate_codes_in_window (file))
+      if (inflate_codes_in_window (gzio))
        {
          huft_free (gzio->tl);
          huft_free (gzio->td);
@@ -1089,12 +1106,10 @@ inflate_window (grub_file_t file)
 
 
 static void
-initialize_tables (grub_file_t file)
+initialize_tables (grub_gzio_t gzio)
 {
-  grub_gzio_t gzio = file->data;
-
   gzio->saved_offset = 0;
-  grub_file_seek (gzio->file, gzio->data_offset);
+  gzio_seek (gzio, gzio->data_offset);
 
   /* Initialize the bit buffer.  */
   gzio->bk = 0;
@@ -1139,7 +1154,7 @@ grub_gzio_open (grub_file_t io)
   file->fs = &grub_gzio_fs;
   file->not_easly_seekable = 1;
 
-  if (! test_header (file))
+  if (! test_gzip_header (file))
     {
       grub_free (gzio);
       grub_free (file);
@@ -1155,16 +1170,49 @@ grub_gzio_open (grub_file_t io)
   return file;
 }
 
+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) != DEFLATED)
+    {
+      grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "unsupported gzip format");
+      return 0;
+    }
+
+  if ((cmf * 256 + flg) % 31)
+    {
+      grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "unsupported gzip format");
+      return 0;
+    }
+
+  /* Dictionary isn't supported.  */
+  if (flg & 0x20)
+    {
+      grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "unsupported gzip format");
+      return 0;
+    }
+
+  gzio->data_offset = 2;
+  initialize_tables (gzio);
+
+  return 1;
+}
+
 static grub_ssize_t
-grub_gzio_read (grub_file_t file, char *buf, grub_size_t len)
+grub_gzio_read_real (grub_gzio_t gzio, grub_off_t offset,
+                    char *buf, grub_size_t len)
 {
   grub_ssize_t ret = 0;
-  grub_gzio_t gzio = file->data;
-  grub_off_t offset;
 
   /* Do we reset decompression to the beginning of the file?  */
-  if (gzio->saved_offset > file->offset + WSIZE)
-    initialize_tables (file);
+  if (gzio->saved_offset > offset + WSIZE)
+    initialize_tables (gzio);
 
   /*
    *  This loop operates upon uncompressed data only.  The only
@@ -1172,15 +1220,13 @@ grub_gzio_read (grub_file_t file, char *buf, grub_size_t len)
    *  window is within the range of data it needs.
    */
 
-  offset = file->offset;
-
   while (len > 0 && grub_errno == GRUB_ERR_NONE)
     {
       register grub_size_t size;
       register char *srcaddr;
 
       while (offset >= gzio->saved_offset)
-       inflate_window (file);
+       inflate_window (gzio);
 
       srcaddr = (char *) ((offset & (WSIZE - 1)) + gzio->slide);
       size = gzio->saved_offset - offset;
@@ -1201,6 +1247,12 @@ grub_gzio_read (grub_file_t file, char *buf, grub_size_t len)
   return ret;
 }
 
+static grub_ssize_t
+grub_gzio_read (grub_file_t file, char *buf, grub_size_t len)
+{
+  return grub_gzio_read_real (file->data, file->offset, buf, len);
+}
+
 /* Release everything, including the underlying file object.  */
 static grub_err_t
 grub_gzio_close (grub_file_t file)
@@ -1218,6 +1270,33 @@ grub_gzio_close (grub_file_t file)
   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;
+}
+
 \f
 
 static struct grub_fs grub_gzio_fs =
diff --git a/include/grub/deflate.h b/include/grub/deflate.h
new file mode 100644 (file)
index 0000000..6ec4eaa
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ *  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/>.
+ */
+
+#ifndef GRUB_DEFLATE_HEADER
+#define GRUB_DEFLATE_HEADER 1
+
+grub_ssize_t
+grub_zlib_decompress (char *inbuf, grub_size_t insize, grub_off_t off,
+                     char *outbuf, grub_size_t outsize);
+
+#endif