]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
Fix handling of uncompressed blocks on squashfs and break 4G limit.
authorVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Tue, 25 Oct 2011 16:09:00 +0000 (18:09 +0200)
committerVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Tue, 25 Oct 2011 16:09:00 +0000 (18:09 +0200)
* grub-core/fs/squash4.c (grub_squash_super): Add block_size. Remove
unused flags.
(grub_squash_inode): Add long_file and block_size.
(grub_squash_cache_inode): New struct.
(grub_squash_dirent): Make types into enum.
(SQUASH_TYPE_LONG_REGULAR): New type.
(grub_squash_frag_desc): Add field size.
(SQUASH_BLOCK_FLAGS): New enum.
(grub_squash_data): Use grub_squash_cache_inode.
(grub_fshelp_node): Make ino_chunk 64-bit.
(read_chunk): Minor argument change. All users updated.
(squash_mount): Use correct le_to_cpu.
(grub_squash_open): Handle LONG_REGULAR.
(direct_read): New function.
(grub_squash_read_data): Handle blocks correctly.

ChangeLog
grub-core/fs/squash4.c

index 7eef0f796d0ec24cbea3f1d78aaedf1c354b5e3a..586de218fffd1c9254a80f50ea3f90244b24a639 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,23 @@
+2011-10-25  Vladimir Serbinenko  <phcoder@gmail.com>
+
+       Fix handling of uncompressed blocks on squashfs and break 4G limit.
+
+       * grub-core/fs/squash4.c (grub_squash_super): Add block_size. Remove
+       unused flags.
+       (grub_squash_inode): Add long_file and block_size.
+       (grub_squash_cache_inode): New struct.
+       (grub_squash_dirent): Make types into enum.
+       (SQUASH_TYPE_LONG_REGULAR): New type.
+       (grub_squash_frag_desc): Add field size.
+       (SQUASH_BLOCK_FLAGS): New enum.
+       (grub_squash_data): Use grub_squash_cache_inode.
+       (grub_fshelp_node): Make ino_chunk 64-bit.
+       (read_chunk): Minor argument change. All users updated.
+       (squash_mount): Use correct le_to_cpu.
+       (grub_squash_open): Handle LONG_REGULAR.
+       (direct_read): New function.
+       (grub_squash_read_data): Handle blocks correctly.
+
 2011-10-25  Vladimir Serbinenko  <phcoder@gmail.com>
 
        * grub-core/kern/disk.c (grub_disk_read_small): Fix memory leak.
index 4f126558258418292558a80d44b8398f2d514b02..d45732c16a2d09c249fc2d72ecf935eb5e76ae7c 100644 (file)
@@ -42,7 +42,7 @@ GRUB_MOD_LICENSE ("GPLv3+");
   exttblptr      RAW         superblock
 
   UID/GID table is the array ot uint32_t
-  unk1 contains pointer to unk3 followed by some chunk.
+  unk1 contains pointer to fragment table followed by some chunk.
   unk2 containts one uint64_t
 */
 
@@ -52,13 +52,9 @@ struct grub_squash_super
 #define SQUASH_MAGIC 0x73717368
   grub_uint32_t dummy1;
   grub_uint32_t creation_time;
-  grub_uint32_t dummy2;
+  grub_uint32_t block_size;
   grub_uint64_t dummy3;
-  grub_uint8_t flags;
-#define SQUASH_FLAG_UNCOMPRESSED_INODES 1
-#define SQUASH_FLAG_UNCOMPRESSED_DATA 2
-#define SQUASH_FLAG_UNCOMPRESSED_FRAGMENTS 8
-  grub_uint8_t dummy4[7];
+  grub_uint64_t dummy4;
   grub_uint16_t root_ino_offset;
   grub_uint32_t root_ino_chunk;
   grub_uint16_t dummy5;
@@ -71,7 +67,6 @@ struct grub_squash_super
   grub_uint64_t unk2offset;
 } __attribute__ ((packed));
 
-
 /* Chunk-based */
 struct grub_squash_inode
 {
@@ -87,7 +82,18 @@ struct grub_squash_inode
       grub_uint32_t fragment;
       grub_uint32_t offset;
       grub_uint32_t size;
+      grub_uint32_t block_size[0];
     }  __attribute__ ((packed)) file;
+    struct {
+      grub_uint32_t dummy;
+      grub_uint64_t chunk;
+      grub_uint64_t size;
+      grub_uint32_t dummy2[3];
+      grub_uint32_t fragment;
+      grub_uint32_t offset;
+      grub_uint32_t dummy3;
+      grub_uint32_t block_size[0];
+    }  __attribute__ ((packed)) long_file;
     struct {
       grub_uint32_t dummy1;
       grub_uint32_t chunk;
@@ -104,6 +110,15 @@ struct grub_squash_inode
   }  __attribute__ ((packed));
 } __attribute__ ((packed));
 
+struct grub_squash_cache_inode
+{
+  struct grub_squash_inode ino;
+  grub_disk_addr_t ino_chunk;
+  grub_uint16_t        ino_offset;
+  grub_uint32_t *block_sizes;
+  grub_disk_addr_t *cumulated_block_sizes;
+};
+
 /* Chunk-based.  */
 struct grub_squash_dirent_header
 {
@@ -117,29 +132,46 @@ struct grub_squash_dirent
   grub_uint16_t ino_offset;
   grub_uint16_t dummy;
   grub_uint16_t type;
-#define SQUASH_TYPE_DIR 1
-#define SQUASH_TYPE_REGULAR 2
-#define SQUASH_TYPE_SYMLINK 3
   /* Actually the value is the length of name - 1.  */
   grub_uint16_t namelen;
   char name[0];
 } __attribute__ ((packed));
 
+enum
+  {
+    SQUASH_TYPE_DIR = 1,
+    SQUASH_TYPE_REGULAR = 2,
+    SQUASH_TYPE_SYMLINK = 3,
+    SQUASH_TYPE_LONG_REGULAR = 9,
+  };
+
+
 struct grub_squash_frag_desc
 {
   grub_uint64_t offset;
-  grub_uint64_t dummy;
+  grub_uint32_t size;
+  grub_uint32_t dummy;
 } __attribute__ ((packed));
 
+enum
+  {
+    SQUASH_CHUNK_FLAGS = 0x8000,
+    SQUASH_CHUNK_UNCOMPRESSED = 0x8000
+  };
+
+enum
+  {
+    SQUASH_BLOCK_FLAGS = 0x1000000,
+    SQUASH_BLOCK_UNCOMPRESSED = 0x1000000
+  };
+
 #define SQUASH_CHUNK_SIZE 0x2000
-#define SQUASH_CHUNK_FLAGS 0x8000
-#define SQUASH_CHUNK_UNCOMPRESSED 0x8000
 
 struct grub_squash_data
 {
   grub_disk_t disk;
   struct grub_squash_super sb;
-  struct grub_squash_inode ino;
+  struct grub_squash_cache_inode ino;
   grub_uint64_t fragments;
 };
 
@@ -147,12 +179,12 @@ struct grub_fshelp_node
 {
   struct grub_squash_data *data;
   struct grub_squash_inode ino;
-  grub_uint32_t ino_chunk;
+  grub_disk_addr_t ino_chunk;
   grub_uint16_t        ino_offset;
 };
 
 static grub_err_t
-read_chunk (grub_disk_t disk, void *buf, grub_size_t len,
+read_chunk (struct grub_squash_data *data, void *buf, grub_size_t len,
            grub_uint64_t chunk, grub_off_t offset)
 {
   grub_uint64_t chunk_start;
@@ -164,7 +196,8 @@ read_chunk (grub_disk_t disk, void *buf, grub_size_t len,
       grub_err_t err;
       while (1)
        {
-         err = grub_disk_read (disk, chunk_start >> GRUB_DISK_SECTOR_BITS,
+         err = grub_disk_read (data->disk,
+                               chunk_start >> GRUB_DISK_SECTOR_BITS,
                                chunk_start & (GRUB_DISK_SECTOR_SIZE - 1),
                                sizeof (d), &d);
          if (err)
@@ -182,7 +215,7 @@ read_chunk (grub_disk_t disk, void *buf, grub_size_t len,
       if (grub_le_to_cpu16 (d) & SQUASH_CHUNK_UNCOMPRESSED)
        {
          grub_disk_addr_t a = chunk_start + 2 + offset;
-         err = grub_disk_read (disk, (a >> GRUB_DISK_SECTOR_BITS),
+         err = grub_disk_read (data->disk, (a >> GRUB_DISK_SECTOR_BITS),
                                a & (GRUB_DISK_SECTOR_SIZE - 1),
                                csize, buf);
          if (err)
@@ -197,7 +230,7 @@ read_chunk (grub_disk_t disk, void *buf, grub_size_t len,
          if (!tmp)
            return grub_errno;
          /* FIXME: buffer uncompressed data.  */
-         err = grub_disk_read (disk, (a >> GRUB_DISK_SECTOR_BITS),
+         err = grub_disk_read (data->disk, (a >> GRUB_DISK_SECTOR_BITS),
                                a & (GRUB_DISK_SECTOR_SIZE - 1),
                                bsize, tmp);
          if (err)
@@ -240,9 +273,10 @@ squash_mount (grub_disk_t disk)
       return NULL;
     }
 
-  err = grub_disk_read (disk, grub_le_to_cpu32 (sb.unk1offset)
+  err = grub_disk_read (disk, 
+                       grub_le_to_cpu64 (sb.unk1offset)
                        >> GRUB_DISK_SECTOR_BITS, 
-                       grub_le_to_cpu32 (sb.unk1offset)
+                       grub_le_to_cpu64 (sb.unk1offset)
                        & (GRUB_DISK_SECTOR_SIZE - 1), sizeof (frag), &frag);
   if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
     grub_error (GRUB_ERR_BAD_FS, "not a squash4");
@@ -254,7 +288,7 @@ squash_mount (grub_disk_t disk)
     return NULL;
   data->sb = sb;
   data->disk = disk;
-  data->fragments = frag;
+  data->fragments = grub_le_to_cpu64 (frag);
 
   return data;
 }
@@ -266,7 +300,7 @@ grub_squash_read_symlink (grub_fshelp_node_t node)
   grub_err_t err;
   ret = grub_malloc (grub_le_to_cpu32 (node->ino.symlink.namelen) + 1);
 
-  err = read_chunk (node->data->disk, ret,
+  err = read_chunk (node->data, ret,
                    grub_le_to_cpu32 (node->ino.symlink.namelen),
                    grub_le_to_cpu64 (node->data->sb.inodeoffset)
                    + node->ino_chunk,
@@ -300,7 +334,7 @@ grub_squash_iterate_dir (grub_fshelp_node_t dir,
       struct grub_squash_dirent_header dh;
       grub_err_t err;
 
-      err = read_chunk (dir->data->disk, &dh, sizeof (dh),
+      err = read_chunk (dir->data, &dh, sizeof (dh),
                        grub_le_to_cpu64 (dir->data->sb.diroffset)
                        + grub_le_to_cpu32 (dir->ino.dir.chunk), off);
       if (err)
@@ -315,14 +349,14 @@ grub_squash_iterate_dir (grub_fshelp_node_t dir,
          struct grub_squash_dirent di;
          struct grub_squash_inode ino;
 
-         err = read_chunk (dir->data->disk, &di, sizeof (di),
+         err = read_chunk (dir->data, &di, sizeof (di),
                            grub_le_to_cpu64 (dir->data->sb.diroffset)
                            + grub_le_to_cpu32 (dir->ino.dir.chunk), off);
          if (err)
            return 0;
          off += sizeof (di);
 
-         err = read_chunk (dir->data->disk, &ino, sizeof (ino),
+         err = read_chunk (dir->data, &ino, sizeof (ino),
                            grub_le_to_cpu64 (dir->data->sb.inodeoffset)
                            + grub_le_to_cpu32 (dh.ino_chunk),
                            grub_cpu_to_le16 (di.ino_offset));
@@ -332,7 +366,7 @@ grub_squash_iterate_dir (grub_fshelp_node_t dir,
          buf = grub_malloc (grub_le_to_cpu16 (di.namelen) + 2);
          if (!buf)
            return 0;
-         err = read_chunk (dir->data->disk, buf,
+         err = read_chunk (dir->data, buf,
                            grub_le_to_cpu16 (di.namelen) + 1,
                            grub_le_to_cpu64 (dir->data->sb.diroffset)
                            + grub_le_to_cpu32 (dir->ino.dir.chunk), off);
@@ -370,7 +404,7 @@ make_root_node (struct grub_squash_data *data, struct grub_fshelp_node *root)
   grub_memset (root, 0, sizeof (*root));
   root->data = data;
  
- return read_chunk (data->disk, &root->ino, sizeof (root->ino),
+ return read_chunk (data, &root->ino, sizeof (root->ino),
                    grub_le_to_cpu64 (data->sb.inodeoffset) 
                    + grub_le_to_cpu16 (data->sb.root_ino_chunk),
                    grub_cpu_to_le16 (data->sb.root_ino_offset));
@@ -445,48 +479,166 @@ grub_squash_open (struct grub_file *file, const char *name)
     }
 
   file->data = data;
-  data->ino = fdiro->ino;
-  file->size = grub_le_to_cpu32 (fdiro->ino.file.size);
+  data->ino.ino = fdiro->ino;
+  data->ino.block_sizes = NULL;
+  data->ino.cumulated_block_sizes = NULL;
+  data->ino.ino_chunk = fdiro->ino_chunk;
+  data->ino.ino_offset = fdiro->ino_offset;
+
+  if (fdiro->ino.type
+      == grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR))
+    file->size = grub_le_to_cpu64 (fdiro->ino.long_file.size);
+  else
+    file->size = grub_le_to_cpu32 (fdiro->ino.file.size);
 
   return GRUB_ERR_NONE;
 }
 
+static grub_ssize_t
+direct_read (struct grub_squash_data *data, 
+            struct grub_squash_cache_inode *ino,
+            grub_off_t off, char *buf, grub_size_t len)
+{
+  grub_err_t err;
+  grub_off_t cumulated_uncompressed_size = 0;
+  grub_uint64_t a;
+  grub_size_t i;
+  grub_size_t origlen = len;
+
+  if (ino->ino.type == grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR))
+    a = grub_le_to_cpu64 (ino->ino.long_file.chunk);
+  else
+    a = grub_le_to_cpu32 (ino->ino.file.chunk);
+
+  if (!ino->block_sizes)
+    {
+      grub_off_t total_size;
+      grub_size_t total_blocks;
+      grub_size_t block_offset;
+      if (ino->ino.type
+         == grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR))
+       {
+         total_size = grub_le_to_cpu64 (ino->ino.long_file.size);
+         block_offset = ((char *) &ino->ino.long_file.block_size
+                         - (char *) &ino->ino);
+       }
+      else
+       {
+         total_size = grub_le_to_cpu32 (ino->ino.file.size);
+         block_offset = ((char *) &ino->ino.file.block_size
+                         - (char *) &ino->ino);
+       }
+      total_blocks = grub_divmod64 (total_size
+                                   + grub_le_to_cpu32 (data->sb.block_size) - 1,
+                                   grub_le_to_cpu32 (data->sb.block_size),
+                                   0);
+      ino->block_sizes = grub_malloc (total_blocks
+                                     * sizeof (ino->block_sizes[0]));
+      ino->cumulated_block_sizes = grub_malloc (total_blocks
+                                               * sizeof (ino->cumulated_block_sizes[0]));
+      if (!ino->block_sizes || !ino->cumulated_block_sizes)
+       {
+         grub_free (ino->block_sizes);
+         grub_free (ino->cumulated_block_sizes);
+         ino->block_sizes = 0;
+         ino->cumulated_block_sizes = 0;
+         return -1;
+       }
+      err = read_chunk (data, ino->block_sizes,
+                       total_blocks * sizeof (ino->block_sizes[0]),
+                       grub_le_to_cpu64 (data->sb.inodeoffset)
+                       + ino->ino_chunk,
+                       ino->ino_offset + block_offset);
+      if (err)
+       {
+         grub_free (ino->block_sizes);
+         grub_free (ino->cumulated_block_sizes);
+         ino->block_sizes = 0;
+         ino->cumulated_block_sizes = 0;
+         return -1;
+       }
+      ino->cumulated_block_sizes[0] = 0;
+      for (i = 1; i < total_blocks; i++)
+       ino->cumulated_block_sizes[i] = ino->cumulated_block_sizes[i - 1]
+         + (grub_le_to_cpu32 (ino->block_sizes[i - 1]) & ~SQUASH_BLOCK_FLAGS);
+    }
+
+  if (a == 0)
+    a = sizeof (struct grub_squash_super);
+  i = grub_divmod64 (off, grub_le_to_cpu32 (data->sb.block_size), 0);
+  cumulated_uncompressed_size = grub_le_to_cpu32 (data->sb.block_size)
+    * (grub_disk_addr_t) i;
+  while (cumulated_uncompressed_size < off + len)
+    {
+      grub_size_t boff, read;
+      boff = off - cumulated_uncompressed_size;
+      read = grub_le_to_cpu32 (data->sb.block_size) - boff;
+      if (read > len)
+       read = len;
+      if (!(ino->block_sizes[i] & SQUASH_BLOCK_UNCOMPRESSED))
+       err = grub_zlib_disk_read (data->disk,
+                                  ino->cumulated_block_sizes[i] + a,
+                                  boff, buf, read);
+      else
+       err = grub_disk_read (data->disk, 
+                             (ino->cumulated_block_sizes[i] + a + boff)
+                             >> GRUB_DISK_SECTOR_BITS,
+                             (ino->cumulated_block_sizes[i] + a + boff)
+                             & (GRUB_DISK_SECTOR_SIZE - 1),
+                             read, buf);
+      if (err)
+       return -1;
+      off += read;
+      len -= read;
+      buf += read;
+      cumulated_uncompressed_size += grub_le_to_cpu32 (data->sb.block_size);
+      i++;
+    }
+  return origlen;
+}
+
+
 static grub_ssize_t
 grub_squash_read_data (struct grub_squash_data *data, 
-                      grub_disk_t disk, const struct grub_squash_inode *ino,
+                      struct grub_squash_cache_inode *ino,
                       grub_off_t off, char *buf, grub_size_t len)
 {
   grub_err_t err;
   grub_uint64_t a, b;
+  grub_uint32_t fragment;
   int compressed = 0;
+  struct grub_squash_frag_desc frag;
 
-  if (grub_le_to_cpu16 (ino->file.fragment) == 0xffff)
+  if (ino->ino.type == grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR))
     {
-      if (grub_le_to_cpu32 (ino->file.chunk))
-       a = grub_le_to_cpu32 (ino->file.chunk);
-      else
-       a = sizeof (struct grub_squash_super);
-      compressed = !(data->sb.flags & SQUASH_FLAG_UNCOMPRESSED_DATA);
+      a = grub_le_to_cpu64 (ino->ino.long_file.chunk);
+      fragment = grub_le_to_cpu32 (ino->ino.long_file.fragment);
     }
   else
     {
-      struct grub_squash_frag_desc frag;
-      err = read_chunk (disk, &frag, sizeof (frag),
-                       data->fragments, sizeof (frag)
-                       * grub_le_to_cpu16 (ino->file.fragment));
-      if (err)
-       return -1;
-      a = grub_le_to_cpu64 (frag.offset) + grub_le_to_cpu32 (ino->file.chunk);
-      compressed = !(data->sb.flags & SQUASH_FLAG_UNCOMPRESSED_FRAGMENTS);
+      a = grub_le_to_cpu32 (ino->ino.file.chunk);
+      fragment = grub_le_to_cpu32 (ino->ino.file.fragment);
     }
 
-  b = grub_le_to_cpu32 (data->ino.file.offset) + off;
-
+  if (fragment == 0xffffffff)
+    return direct_read (data, ino, off, buf, len);
+  err = read_chunk (data, &frag, sizeof (frag),
+                   data->fragments, sizeof (frag) * fragment);
+  if (err)
+    return -1;
+  a += grub_le_to_cpu64 (frag.offset);
+  compressed = !(frag.size & SQUASH_BLOCK_UNCOMPRESSED);
+  if (ino->ino.type == grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR))
+    b = grub_le_to_cpu64 (ino->ino.long_file.offset) + off;
+  else
+    b = grub_le_to_cpu32 (ino->ino.file.offset) + off;
+  
   /* FIXME: cache uncompressed chunks.  */
   if (compressed)
-    err = grub_zlib_disk_read (disk, a, b, buf, len);
+    err = grub_zlib_disk_read (data->disk, a, b, buf, len);
   else
-    err = grub_disk_read (disk, (a + b) >> GRUB_DISK_SECTOR_BITS,
+    err = grub_disk_read (data->disk, (a + b) >> GRUB_DISK_SECTOR_BITS,
                          (a + b) & (GRUB_DISK_SECTOR_SIZE - 1), len, buf);
   if (err)
     return -1;
@@ -498,7 +650,7 @@ grub_squash_read (grub_file_t file, char *buf, grub_size_t len)
 {
   struct grub_squash_data *data = file->data;
 
-  return grub_squash_read_data (data, file->device->disk, &data->ino,
+  return grub_squash_read_data (data, &data->ino,
                                file->offset, buf, len);
 }