]> git.proxmox.com Git - grub2.git/blobdiff - grub-core/fs/btrfs.c
btrfs: fix get_root key comparison failures due to endianness
[grub2.git] / grub-core / fs / btrfs.c
index 7af735630f65ea6aa4443797f22e8d00de6eec25..f7b6c1520371cb16c22ff9aae60f462d26223758 100644 (file)
@@ -1,7 +1,7 @@
 /* btrfs.c - B-tree file system.  */
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2010  Free Software Foundation, Inc.
+ *  Copyright (C) 2010,2011,2012,2013  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
 #include <grub/types.h>
 #include <grub/lib/crc.h>
 #include <grub/deflate.h>
-#include "minilzo.h"
+#include <minilzo.h>
+#include <grub/i18n.h>
+#include <grub/btrfs.h>
 
 GRUB_MOD_LICENSE ("GPLv3+");
 
 #define GRUB_BTRFS_SIGNATURE "_BHRfS_M"
+
+/* From http://www.oberhumer.com/opensource/lzo/lzofaq.php
+ * LZO will expand incompressible data by a little amount. I still haven't
+ * computed the exact values, but I suggest using these formulas for
+ * a worst-case expansion calculation:
+ *
+ * output_block_size = input_block_size + (input_block_size / 16) + 64 + 3
+ *  */
 #define GRUB_BTRFS_LZO_BLOCK_SIZE 4096
+#define GRUB_BTRFS_LZO_BLOCK_MAX_CSIZE (GRUB_BTRFS_LZO_BLOCK_SIZE + \
+                                    (GRUB_BTRFS_LZO_BLOCK_SIZE / 16) + 64 + 3)
 
 typedef grub_uint8_t grub_btrfs_checksum_t[0x20];
 typedef grub_uint16_t grub_btrfs_uuid_t[8];
@@ -39,8 +51,9 @@ typedef grub_uint16_t grub_btrfs_uuid_t[8];
 struct grub_btrfs_device
 {
   grub_uint64_t device_id;
-  grub_uint8_t dummy[0x62 - 8];
-} __attribute__ ((packed));
+  grub_uint64_t size;
+  grub_uint8_t dummy[0x62 - 0x10];
+} GRUB_PACKED;
 
 struct grub_btrfs_superblock
 {
@@ -58,7 +71,7 @@ struct grub_btrfs_superblock
   char label[0x100];
   grub_uint8_t dummy4[0x100];
   grub_uint8_t bootstrap_mapping[0x800];
-} __attribute__ ((packed));
+} GRUB_PACKED;
 
 struct btrfs_header
 {
@@ -67,7 +80,7 @@ struct btrfs_header
   grub_uint8_t dummy[0x30];
   grub_uint32_t nitems;
   grub_uint8_t level;
-} __attribute__ ((packed));
+} GRUB_PACKED;
 
 struct grub_btrfs_device_desc
 {
@@ -94,19 +107,6 @@ struct grub_btrfs_data
   struct grub_btrfs_extent_data *extent;
 };
 
-struct grub_btrfs_key
-{
-  grub_uint64_t object_id;
-#define GRUB_BTRFS_ITEM_TYPE_INODE_ITEM 0x01
-#define GRUB_BTRFS_ITEM_TYPE_DIR_ITEM 0x54
-#define GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM 0x6c
-#define GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM 0x84
-#define GRUB_BTRFS_ITEM_TYPE_DEVICE 0xd8
-#define GRUB_BTRFS_ITEM_TYPE_CHUNK 0xe4
-  grub_uint8_t type;
-  grub_uint64_t offset;
-} __attribute__ ((packed));
-
 struct grub_btrfs_chunk_item
 {
   grub_uint64_t size;
@@ -122,28 +122,28 @@ struct grub_btrfs_chunk_item
   grub_uint8_t dummy2[0xc];
   grub_uint16_t nstripes;
   grub_uint16_t nsubstripes;
-} __attribute__ ((packed));
+} GRUB_PACKED;
 
 struct grub_btrfs_chunk_stripe
 {
   grub_uint64_t device_id;
   grub_uint64_t offset;
   grub_btrfs_uuid_t device_uuid;
-} __attribute__ ((packed));
+} GRUB_PACKED;
 
 struct grub_btrfs_leaf_node
 {
   struct grub_btrfs_key key;
   grub_uint32_t offset;
   grub_uint32_t size;
-} __attribute__ ((packed));
+} GRUB_PACKED;
 
 struct grub_btrfs_internal_node
 {
   struct grub_btrfs_key key;
   grub_uint64_t addr;
   grub_uint64_t dummy;
-} __attribute__ ((packed));
+} GRUB_PACKED;
 
 struct grub_btrfs_dir_item
 {
@@ -156,7 +156,7 @@ struct grub_btrfs_dir_item
 #define GRUB_BTRFS_DIR_ITEM_TYPE_SYMLINK 7
   grub_uint8_t type;
   char name[0];
-} __attribute__ ((packed));
+} GRUB_PACKED;
 
 struct grub_btrfs_leaf_descriptor
 {
@@ -171,13 +171,6 @@ struct grub_btrfs_leaf_descriptor
   } *data;
 };
 
-struct grub_btrfs_root_item
-{
-  grub_uint8_t dummy[0xb0];
-  grub_uint64_t tree;
-  grub_uint64_t inode;
-};
-
 struct grub_btrfs_time
 {
   grub_int64_t sec;
@@ -190,7 +183,7 @@ struct grub_btrfs_inode
   grub_uint64_t size;
   grub_uint8_t dummy2[0x70];
   struct grub_btrfs_time mtime;
-} __attribute__ ((packed));
+} GRUB_PACKED;
 
 struct grub_btrfs_extent_data
 {
@@ -211,7 +204,7 @@ struct grub_btrfs_extent_data
       grub_uint64_t filled;
     };
   };
-} __attribute__ ((packed));
+} GRUB_PACKED;
 
 #define GRUB_BTRFS_EXTENT_INLINE 0
 #define GRUB_BTRFS_EXTENT_REGULAR 1
@@ -228,7 +221,8 @@ static grub_disk_addr_t superblock_sectors[] = { 64 * 2, 64 * 1024 * 2,
 
 static grub_err_t
 grub_btrfs_read_logical (struct grub_btrfs_data *data,
-                        grub_disk_addr_t addr, void *buf, grub_size_t size);
+                        grub_disk_addr_t addr, void *buf, grub_size_t size,
+                        int recursion_depth);
 
 static grub_err_t
 read_sblock (grub_disk_t disk, struct grub_btrfs_superblock *sb)
@@ -238,6 +232,10 @@ read_sblock (grub_disk_t disk, struct grub_btrfs_superblock *sb)
   for (i = 0; i < ARRAY_SIZE (superblock_sectors); i++)
     {
       struct grub_btrfs_superblock sblock;
+      /* Don't try additional superblocks beyond device size.  */
+      if (i && (grub_le_to_cpu64 (sblock.this_device.size)
+               >> GRUB_DISK_SECTOR_BITS) <= superblock_sectors[i])
+       break;
       err = grub_disk_read (disk, superblock_sectors[i], 0,
                            sizeof (sblock), &sblock);
       if (err == GRUB_ERR_OUT_OF_RANGE)
@@ -263,9 +261,9 @@ read_sblock (grub_disk_t disk, struct grub_btrfs_superblock *sb)
 static int
 key_cmp (const struct grub_btrfs_key *a, const struct grub_btrfs_key *b)
 {
-  if (grub_cpu_to_le64 (a->object_id) < grub_cpu_to_le64 (b->object_id))
+  if (grub_le_to_cpu64 (a->object_id) < grub_le_to_cpu64 (b->object_id))
     return -1;
-  if (grub_cpu_to_le64 (a->object_id) > grub_cpu_to_le64 (b->object_id))
+  if (grub_le_to_cpu64 (a->object_id) > grub_le_to_cpu64 (b->object_id))
     return +1;
 
   if (a->type < b->type)
@@ -273,9 +271,9 @@ key_cmp (const struct grub_btrfs_key *a, const struct grub_btrfs_key *b)
   if (a->type > b->type)
     return +1;
 
-  if (grub_cpu_to_le64 (a->offset) < grub_cpu_to_le64 (b->offset))
+  if (grub_le_to_cpu64 (a->offset) < grub_le_to_cpu64 (b->offset))
     return -1;
-  if (grub_cpu_to_le64 (a->offset) > grub_cpu_to_le64 (b->offset))
+  if (grub_le_to_cpu64 (a->offset) > grub_le_to_cpu64 (b->offset))
     return +1;
   return 0;
 }
@@ -320,8 +318,8 @@ next (struct grub_btrfs_data *data,
   for (; desc->depth > 0; desc->depth--)
     {
       desc->data[desc->depth - 1].iter++;
-      if (desc->data[desc->depth - 1].iter <
-         desc->data[desc->depth - 1].maxiter)
+      if (desc->data[desc->depth - 1].iter
+         desc->data[desc->depth - 1].maxiter)
        break;
     }
   if (desc->depth == 0)
@@ -335,12 +333,12 @@ next (struct grub_btrfs_data *data,
                                     * sizeof (node)
                                     + sizeof (struct btrfs_header)
                                     + desc->data[desc->depth - 1].addr,
-                                    &node, sizeof (node));
+                                    &node, sizeof (node), 0);
       if (err)
        return -err;
 
       err = grub_btrfs_read_logical (data, grub_le_to_cpu64 (node.addr),
-                                    &head, sizeof (head));
+                                    &head, sizeof (head), 0);
       if (err)
        return -err;
 
@@ -351,7 +349,7 @@ next (struct grub_btrfs_data *data,
                                 * sizeof (leaf)
                                 + sizeof (struct btrfs_header)
                                 + desc->data[desc->depth - 1].addr, &leaf,
-                                sizeof (leaf));
+                                sizeof (leaf), 0);
   if (err)
     return -err;
   *outsize = grub_le_to_cpu32 (leaf.size);
@@ -365,11 +363,12 @@ static grub_err_t
 lower_bound (struct grub_btrfs_data *data,
             const struct grub_btrfs_key *key_in,
             struct grub_btrfs_key *key_out,
-            grub_disk_addr_t root,
-            grub_disk_addr_t * outaddr, grub_size_t * outsize,
-            struct grub_btrfs_leaf_descriptor *desc)
+            grub_uint64_t root,
+            grub_disk_addr_t *outaddr, grub_size_t *outsize,
+            struct grub_btrfs_leaf_descriptor *desc,
+            int recursion_depth)
 {
-  grub_disk_addr_t addr = root;
+  grub_disk_addr_t addr = grub_le_to_cpu64 (root);
   int depth = -1;
 
   if (desc)
@@ -381,6 +380,11 @@ lower_bound (struct grub_btrfs_data *data,
        return grub_errno;
     }
 
+  /* > 2 would work as well but be robust and allow a bit more just in case.
+   */
+  if (recursion_depth > 10)
+    return grub_error (GRUB_ERR_BAD_FS, "too deep btrfs virtual nesting");
+
   grub_dprintf ("btrfs",
                "retrieving %" PRIxGRUB_UINT64_T
                " %x %" PRIxGRUB_UINT64_T "\n",
@@ -394,7 +398,8 @@ lower_bound (struct grub_btrfs_data *data,
     reiter:
       depth++;
       /* FIXME: preread few nodes into buffer. */
-      err = grub_btrfs_read_logical (data, addr, &head, sizeof (head));
+      err = grub_btrfs_read_logical (data, addr, &head, sizeof (head),
+                                    recursion_depth + 1);
       if (err)
        return err;
       addr += sizeof (head);
@@ -407,7 +412,8 @@ lower_bound (struct grub_btrfs_data *data,
          for (i = 0; i < grub_le_to_cpu32 (head.nitems); i++)
            {
              err = grub_btrfs_read_logical (data, addr + i * sizeof (node),
-                                            &node, sizeof (node));
+                                            &node, sizeof (node),
+                                            recursion_depth + 1);
              if (err)
                return err;
 
@@ -459,7 +465,8 @@ lower_bound (struct grub_btrfs_data *data,
        for (i = 0; i < grub_le_to_cpu32 (head.nitems); i++)
          {
            err = grub_btrfs_read_logical (data, addr + i * sizeof (leaf),
-                                          &leaf, sizeof (leaf));
+                                          &leaf, sizeof (leaf),
+                                          recursion_depth + 1);
            if (err)
              return err;
 
@@ -507,58 +514,75 @@ lower_bound (struct grub_btrfs_data *data,
     }
 }
 
-static grub_device_t
-find_device (struct grub_btrfs_data *data, grub_uint64_t id, int do_rescan)
+/* Context for find_device.  */
+struct find_device_ctx
 {
-  grub_device_t dev_found = NULL;
-  auto int hook (const char *name);
-  int hook (const char *name)
-  {
-    grub_device_t dev;
-    grub_err_t err;
-    struct grub_btrfs_superblock sb;
-    dev = grub_device_open (name);
-    if (!dev)
+  struct grub_btrfs_data *data;
+  grub_uint64_t id;
+  grub_device_t dev_found;
+};
+
+/* Helper for find_device.  */
+static int
+find_device_iter (const char *name, void *data)
+{
+  struct find_device_ctx *ctx = data;
+  grub_device_t dev;
+  grub_err_t err;
+  struct grub_btrfs_superblock sb;
+
+  dev = grub_device_open (name);
+  if (!dev)
+    return 0;
+  if (!dev->disk)
+    {
+      grub_device_close (dev);
       return 0;
-    if (!dev->disk)
-      {
-       grub_device_close (dev);
-       return 0;
-      }
-    err = read_sblock (dev->disk, &sb);
-    if (err == GRUB_ERR_BAD_FS)
-      {
-       grub_device_close (dev);
-       grub_errno = GRUB_ERR_NONE;
-       return 0;
-      }
-    if (err)
-      {
-       grub_device_close (dev);
-       grub_print_error ();
-       return 0;
-      }
-    if (grub_memcmp (data->sblock.uuid, sb.uuid, sizeof (sb.uuid)) != 0
-       || sb.this_device.device_id != id)
-      {
-       grub_device_close (dev);
-       return 0;
-      }
+    }
+  err = read_sblock (dev->disk, &sb);
+  if (err == GRUB_ERR_BAD_FS)
+    {
+      grub_device_close (dev);
+      grub_errno = GRUB_ERR_NONE;
+      return 0;
+    }
+  if (err)
+    {
+      grub_device_close (dev);
+      grub_print_error ();
+      return 0;
+    }
+  if (grub_memcmp (ctx->data->sblock.uuid, sb.uuid, sizeof (sb.uuid)) != 0
+      || sb.this_device.device_id != ctx->id)
+    {
+      grub_device_close (dev);
+      return 0;
+    }
 
-    dev_found = dev;
-    return 1;
-  }
+  ctx->dev_found = dev;
+  return 1;
+}
 
+static grub_device_t
+find_device (struct grub_btrfs_data *data, grub_uint64_t id, int do_rescan)
+{
+  struct find_device_ctx ctx = {
+    .data = data,
+    .id = id,
+    .dev_found = NULL
+  };
   unsigned i;
 
   for (i = 0; i < data->n_devices_attached; i++)
     if (id == data->devices_attached[i].id)
       return data->devices_attached[i].dev;
   if (do_rescan)
-    grub_device_iterate (hook);
-  if (!dev_found)
+    grub_device_iterate (find_device_iter, &ctx);
+  if (!ctx.dev_found)
     {
-      grub_error (GRUB_ERR_BAD_FS, "couldn't find a member device");
+      grub_error (GRUB_ERR_BAD_FS,
+                 N_("couldn't find a necessary member device "
+                    "of multi-device filesystem"));
       return NULL;
     }
   data->n_devices_attached++;
@@ -572,19 +596,19 @@ find_device (struct grub_btrfs_data *data, grub_uint64_t id, int do_rescan)
                        * sizeof (data->devices_attached[0]));
       if (!data->devices_attached)
        {
-         grub_device_close (dev_found);
+         grub_device_close (ctx.dev_found);
          data->devices_attached = tmp;
          return NULL;
        }
     }
   data->devices_attached[data->n_devices_attached - 1].id = id;
-  data->devices_attached[data->n_devices_attached - 1].dev = dev_found;
-  return dev_found;
+  data->devices_attached[data->n_devices_attached - 1].dev = ctx.dev_found;
+  return ctx.dev_found;
 }
 
 static grub_err_t
 grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr,
-                        void *buf, grub_size_t size)
+                        void *buf, grub_size_t size, int recursion_depth)
 {
   while (size > 0)
     {
@@ -616,20 +640,20 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr,
                        grub_le_to_cpu64 (key->offset),
                        grub_le_to_cpu64 (chunk->size));
          if (grub_le_to_cpu64 (key->offset) <= addr
-             && addr <
-             grub_le_to_cpu64 (key->offset) + grub_le_to_cpu64 (chunk->size))
+             && addr < grub_le_to_cpu64 (key->offset)
+             + grub_le_to_cpu64 (chunk->size))
            goto chunk_found;
          ptr += sizeof (*key) + sizeof (*chunk)
            + sizeof (struct grub_btrfs_chunk_stripe)
            * grub_le_to_cpu16 (chunk->nstripes);
        }
 
-      key_in.object_id = GRUB_BTRFS_OBJECT_ID_CHUNK;
+      key_in.object_id = grub_cpu_to_le64_compile_time (GRUB_BTRFS_OBJECT_ID_CHUNK);
       key_in.type = GRUB_BTRFS_ITEM_TYPE_CHUNK;
-      key_in.offset = addr;
+      key_in.offset = grub_cpu_to_le64 (addr);
       err = lower_bound (data, &key_in, &key_out,
-                        grub_le_to_cpu64 (data->sblock.chunk_tree),
-                        &chaddr, &chsize, NULL);
+                        data->sblock.chunk_tree,
+                        &chaddr, &chsize, NULL, recursion_depth);
       if (err)
        return err;
       key = &key_out;
@@ -643,7 +667,8 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr,
        return grub_errno;
 
       challoc = 1;
-      err = grub_btrfs_read_logical (data, chaddr, chunk, chsize);
+      err = grub_btrfs_read_logical (data, chaddr, chunk, chsize,
+                                    recursion_depth);
       if (err)
        {
          grub_free (chunk);
@@ -724,12 +749,11 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr,
                                      &low);
 
              high = grub_divmod64 (middle,
-                                   grub_le_to_cpu16 (chunk->nsubstripes),
+                                   grub_le_to_cpu16 (chunk->nstripes)
+                                   / grub_le_to_cpu16 (chunk->nsubstripes),
                                    &stripen);
-             stripen *= grub_le_to_cpu16 (chunk->nstripes)
-               / grub_le_to_cpu16 (chunk->nsubstripes);
-             redundancy = grub_le_to_cpu16 (chunk->nstripes)
-               / grub_le_to_cpu16 (chunk->nsubstripes);
+             stripen *= grub_le_to_cpu16 (chunk->nsubstripes);
+             redundancy = grub_le_to_cpu16 (chunk->nsubstripes);
              stripe_offset = low + grub_le_to_cpu64 (chunk->stripe_length)
                * high;
              csize = grub_le_to_cpu64 (chunk->stripe_length) - low;
@@ -744,7 +768,7 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr,
        if (csize == 0)
          return grub_error (GRUB_ERR_BUG,
                             "couldn't find the chunk descriptor");
-       if ((grub_size_t) csize > size)
+       if (csize > (grub_uint64_t) size)
          csize = size;
 
        for (j = 0; j < 2; j++)
@@ -759,7 +783,7 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr,
                   With RAID5-like it will be more difficult.  */
                stripe += stripen + i;
 
-               paddr = stripe->offset + stripe_offset;
+               paddr = grub_le_to_cpu64 (stripe->offset) + stripe_offset;
 
                grub_dprintf ("btrfs", "chunk 0x%" PRIxGRUB_UINT64_T
                              "+0x%" PRIxGRUB_UINT64_T
@@ -870,22 +894,24 @@ grub_btrfs_read_inode (struct grub_btrfs_data *data,
   key_in.type = GRUB_BTRFS_ITEM_TYPE_INODE_ITEM;
   key_in.offset = 0;
 
-  err = lower_bound (data, &key_in, &key_out, tree, &elemaddr, &elemsize, NULL);
+  err = lower_bound (data, &key_in, &key_out, tree, &elemaddr, &elemsize, NULL,
+                    0);
   if (err)
     return err;
   if (num != key_out.object_id
       || key_out.type != GRUB_BTRFS_ITEM_TYPE_INODE_ITEM)
     return grub_error (GRUB_ERR_BAD_FS, "inode not found");
 
-  return grub_btrfs_read_logical (data, elemaddr, inode, sizeof (*inode));
+  return grub_btrfs_read_logical (data, elemaddr, inode, sizeof (*inode), 0);
 }
 
-static int
+static grub_ssize_t
 grub_btrfs_lzo_decompress(char *ibuf, grub_size_t isize, grub_off_t off,
                          char *obuf, grub_size_t osize)
 {
   grub_uint32_t total_size, cblock_size;
-  unsigned char buf[GRUB_BTRFS_LZO_BLOCK_SIZE];
+  grub_size_t ret = 0;
+  char *ibuf0 = ibuf;
 
   total_size = grub_le_to_cpu32 (grub_get_unaligned32 (ibuf));
   ibuf += sizeof (total_size);
@@ -893,62 +919,83 @@ grub_btrfs_lzo_decompress(char *ibuf, grub_size_t isize, grub_off_t off,
   if (isize < total_size)
     return -1;
 
-  while (osize != 0)
+  /* Jump forward to first block with requested data.  */
+  while (off >= GRUB_BTRFS_LZO_BLOCK_SIZE)
+    {
+      /* Don't let following uint32_t cross the page boundary.  */
+      if (((ibuf - ibuf0) & 0xffc) == 0xffc)
+       ibuf = ((ibuf - ibuf0 + 3) & ~3) + ibuf0;
+
+      cblock_size = grub_le_to_cpu32 (grub_get_unaligned32 (ibuf));
+      ibuf += sizeof (cblock_size);
+
+      if (cblock_size > GRUB_BTRFS_LZO_BLOCK_MAX_CSIZE)
+       return -1;
+
+      off -= GRUB_BTRFS_LZO_BLOCK_SIZE;
+      ibuf += cblock_size;
+    }
+
+  while (osize > 0)
     {
       lzo_uint usize = GRUB_BTRFS_LZO_BLOCK_SIZE;
 
+      /* Don't let following uint32_t cross the page boundary.  */
+      if (((ibuf - ibuf0) & 0xffc) == 0xffc)
+       ibuf = ((ibuf - ibuf0 + 3) & ~3) + ibuf0;
+
       cblock_size = grub_le_to_cpu32 (grub_get_unaligned32 (ibuf));
       ibuf += sizeof (cblock_size);
 
-      if (cblock_size > GRUB_BTRFS_LZO_BLOCK_SIZE)
+      if (cblock_size > GRUB_BTRFS_LZO_BLOCK_MAX_CSIZE)
        return -1;
 
-      /* Jump forward to first block with requested data.  */
-      if (off >= GRUB_BTRFS_LZO_BLOCK_SIZE)
+      /* Block partially filled with requested data.  */
+      if (off > 0 || osize < GRUB_BTRFS_LZO_BLOCK_SIZE)
        {
-         off -= GRUB_BTRFS_LZO_BLOCK_SIZE;
-         ibuf += cblock_size;
-         continue;
-       }
+         grub_size_t to_copy = GRUB_BTRFS_LZO_BLOCK_SIZE - off;
+         grub_uint8_t *buf;
+
+         if (to_copy > osize)
+           to_copy = osize;
+
+         buf = grub_malloc (GRUB_BTRFS_LZO_BLOCK_SIZE);
+         if (!buf)
+           return -1;
 
-      /* First block partially filled with requested data. */
-      if (off > 0)
-       {
          if (lzo1x_decompress_safe ((lzo_bytep)ibuf, cblock_size, buf, &usize,
              NULL) != LZO_E_OK)
-           return -1;
+           {
+             grub_free (buf);
+             return -1;
+           }
 
-         grub_memcpy(obuf, buf + off, usize - off);
+         if (to_copy > usize)
+           to_copy = usize;
+         grub_memcpy(obuf, buf + off, to_copy);
 
-         osize -= usize - off;
-         obuf += usize - off;
+         osize -= to_copy;
+         ret += to_copy;
+         obuf += to_copy;
          ibuf += cblock_size;
          off = 0;
+
+         grub_free (buf);
          continue;
        }
 
-      /* 'Main' case, decompress whole block directly to output buffer.  */
-      if (osize >= GRUB_BTRFS_LZO_BLOCK_SIZE)
-       {
-         if (lzo1x_decompress_safe ((lzo_bytep)ibuf, cblock_size,
-             (lzo_bytep)obuf, &usize, NULL) != LZO_E_OK)
-           return -1;
-
-         osize -= usize;
-         obuf += usize;
-         ibuf += cblock_size;
-       }
-      else /* Last possible block partially filled with requested data.  */
-       {
-         if (lzo1x_decompress_safe ((lzo_bytep)ibuf, cblock_size, buf, &usize,
-             NULL) != LZO_E_OK)
-           return -1;
+      /* Decompress whole block directly to output buffer.  */
+      if (lzo1x_decompress_safe ((lzo_bytep)ibuf, cblock_size, (lzo_bytep)obuf,
+         &usize, NULL) != LZO_E_OK)
+       return -1;
 
-         grub_memcpy(obuf, buf, osize);
-         break;
-       }
+      osize -= usize;
+      ret += usize;
+      obuf += usize;
+      ibuf += cblock_size;
     }
-  return 0;
+
+  return ret;
 }
 
 static grub_ssize_t
@@ -974,7 +1021,7 @@ grub_btrfs_extent_read (struct grub_btrfs_data *data,
          key_in.type = GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM;
          key_in.offset = grub_cpu_to_le64 (pos);
          err = lower_bound (data, &key_in, &key_out, tree,
-                            &elemaddr, &elemsize, NULL);
+                            &elemaddr, &elemsize, NULL, 0);
          if (err)
            return -1;
          if (key_out.object_id != ino
@@ -983,6 +1030,12 @@ grub_btrfs_extent_read (struct grub_btrfs_data *data,
              grub_error (GRUB_ERR_BAD_FS, "extent not found");
              return -1;
            }
+         if ((grub_ssize_t) elemsize < ((char *) &data->extent->inl
+                                        - (char *) data->extent))
+           {
+             grub_error (GRUB_ERR_BAD_FS, "extent descriptor is too short");
+             return -1;
+           }
          data->extstart = grub_le_to_cpu64 (key_out.offset);
          data->extsize = elemsize;
          data->extent = grub_malloc (elemsize);
@@ -992,23 +1045,21 @@ grub_btrfs_extent_read (struct grub_btrfs_data *data,
            return grub_errno;
 
          err = grub_btrfs_read_logical (data, elemaddr, data->extent,
-                                        elemsize);
+                                        elemsize, 0);
          if (err)
            return err;
 
          data->extend = data->extstart + grub_le_to_cpu64 (data->extent->size);
          if (data->extent->type == GRUB_BTRFS_EXTENT_REGULAR
-             && (char *) &data->extent + elemsize >=
-             (char *) &data->extent->filled + sizeof (data->extent->filled))
+             && (char *) &data->extent + elemsize
+             >= (char *) &data->extent->filled + sizeof (data->extent->filled))
            data->extend =
              data->extstart + grub_le_to_cpu64 (data->extent->filled);
 
-         grub_dprintf ("btrfs", "extent 0x%" PRIxGRUB_UINT64_T "+0x%"
-                       PRIxGRUB_UINT64_T " (0x%"
-                       PRIxGRUB_UINT64_T ")\n",
+         grub_dprintf ("btrfs", "regular extent 0x%" PRIxGRUB_UINT64_T "+0x%"
+                       PRIxGRUB_UINT64_T "\n",
                        grub_le_to_cpu64 (key_out.offset),
-                       grub_le_to_cpu64 (data->extent->size),
-                       grub_le_to_cpu64 (data->extent->filled));
+                       grub_le_to_cpu64 (data->extent->size));
          if (data->extend <= pos)
            {
              grub_error (GRUB_ERR_BAD_FS, "extent not found");
@@ -1053,14 +1104,20 @@ grub_btrfs_extent_read (struct grub_btrfs_data *data,
                                         - (grub_uint8_t *) data->extent),
                                        extoff, buf, csize)
                  != (grub_ssize_t) csize)
-               return -1;
+               {
+                 if (!grub_errno)
+                   grub_error (GRUB_ERR_BAD_COMPRESSED_DATA,
+                               "premature end of compressed");
+                 return -1;
+               }
            }
          else if (data->extent->compression == GRUB_BTRFS_COMPRESSION_LZO)
            {
              if (grub_btrfs_lzo_decompress(data->extent->inl, data->extsize -
                                           ((grub_uint8_t *) data->extent->inl
                                            - (grub_uint8_t *) data->extent),
-                                          extoff, buf, csize) < 0)
+                                          extoff, buf, csize)
+                 != (grub_ssize_t) csize)
                return -1;
            }
          else
@@ -1075,15 +1132,17 @@ grub_btrfs_extent_read (struct grub_btrfs_data *data,
 
          if (data->extent->compression != GRUB_BTRFS_COMPRESSION_NONE)
            {
-             void *tmp;
+             char *tmp;
              grub_uint64_t zsize;
+             grub_ssize_t ret;
+
              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);
+                                            tmp, zsize, 0);
              if (err)
                {
                  grub_free (tmp);
@@ -1091,40 +1150,32 @@ grub_btrfs_extent_read (struct grub_btrfs_data *data,
                }
 
              if (data->extent->compression == GRUB_BTRFS_COMPRESSION_ZLIB)
-               {
-                 grub_ssize_t ret;
-
-                 ret = grub_zlib_decompress (tmp, zsize, extoff
-                                           + grub_le_to_cpu64 (data->extent->offset),
-                                           buf, csize);
-
-                 grub_free (tmp);
-
-                 if (ret != (grub_ssize_t) csize)
-                     return -1;
-
-                 break;
-               }
+               ret = grub_zlib_decompress (tmp, zsize, extoff
+                                   + grub_le_to_cpu64 (data->extent->offset),
+                                   buf, csize);
              else if (data->extent->compression == GRUB_BTRFS_COMPRESSION_LZO)
-               {
-                 int ret ;
-
-                 ret = grub_btrfs_lzo_decompress (tmp, zsize, extoff
+               ret = grub_btrfs_lzo_decompress (tmp, zsize, extoff
                                    + grub_le_to_cpu64 (data->extent->offset),
                                    buf, csize);
+             else
+               ret = -1;
 
-                 grub_free(tmp);
-
-                 if (ret < 0)
-                   return -1;
+             grub_free (tmp);
 
-                 break;
+             if (ret != (grub_ssize_t) csize)
+               {
+                 if (!grub_errno)
+                   grub_error (GRUB_ERR_BAD_COMPRESSED_DATA,
+                               "premature end of compressed");
+                 return -1;
                }
-             }
+
+             break;
+           }
          err = grub_btrfs_read_logical (data,
                                         grub_le_to_cpu64 (data->extent->laddr)
                                         + grub_le_to_cpu64 (data->extent->offset)
-                                        + extoff, buf, csize);
+                                        + extoff, buf, csize, 0);
          if (err)
            return -1;
          break;
@@ -1140,10 +1191,44 @@ grub_btrfs_extent_read (struct grub_btrfs_data *data,
   return pos - pos0;
 }
 
+static grub_err_t
+get_root (struct grub_btrfs_data *data, struct grub_btrfs_key *key,
+         grub_uint64_t *tree, grub_uint8_t *type)
+{
+  grub_err_t err;
+  grub_disk_addr_t elemaddr;
+  grub_size_t elemsize;
+  struct grub_btrfs_key key_out, key_in;
+  struct grub_btrfs_root_item ri;
+
+  key_in.object_id = grub_cpu_to_le64_compile_time (GRUB_BTRFS_ROOT_VOL_OBJECTID);
+  key_in.offset = 0;
+  key_in.type = GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM;
+  err = lower_bound (data, &key_in, &key_out,
+                    data->sblock.root_tree,
+                    &elemaddr, &elemsize, NULL, 0);
+  if (err)
+    return err;
+  if (key_in.object_id != key_out.object_id
+      || key_in.type != key_out.type
+      || key_in.offset != key_out.offset)
+    return grub_error (GRUB_ERR_BAD_FS, "no root");
+  err = grub_btrfs_read_logical (data, elemaddr, &ri,
+                                sizeof (ri), 0);
+  if (err)
+    return err;
+  key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
+  key->offset = 0;
+  key->object_id = grub_cpu_to_le64_compile_time (GRUB_BTRFS_OBJECT_ID_CHUNK);
+  *tree = ri.tree;
+  *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY;
+  return GRUB_ERR_NONE;
+}
+
 static grub_err_t
 find_path (struct grub_btrfs_data *data,
           const char *path, struct grub_btrfs_key *key,
-          grub_uint64_t * tree, grub_uint8_t * type)
+          grub_uint64_t *tree, grub_uint8_t *type)
 {
   const char *slash = path;
   grub_err_t err;
@@ -1152,55 +1237,82 @@ find_path (struct grub_btrfs_data *data,
   grub_size_t allocated = 0;
   struct grub_btrfs_dir_item *direl = NULL;
   struct grub_btrfs_key key_out;
-  int skip_default;
   const char *ctoken;
   grub_size_t ctokenlen;
   char *path_alloc = NULL;
   char *origpath = NULL;
   unsigned symlinks_max = 32;
 
-  *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY;
-  *tree = data->sblock.root_tree;
-  key->object_id = data->sblock.root_dir_objectid;
-  key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
-  key->offset = 0;
-  skip_default = 1;
+  err = get_root (data, key, tree, type);
+  if (err)
+    return err;
+
   origpath = grub_strdup (path);
   if (!origpath)
     return grub_errno;
 
   while (1)
     {
-      if (!skip_default)
-       {
-         while (path[0] == '/')
-           path++;
-         if (!path[0])
-           break;
-         slash = grub_strchr (path, '/');
-         if (!slash)
-           slash = path + grub_strlen (path);
-         ctoken = path;
-         ctokenlen = slash - path;
-       }
-      else
-       {
-         ctoken = "default";
-         ctokenlen = sizeof ("default") - 1;
-       }
+      while (path[0] == '/')
+       path++;
+      if (!path[0])
+       break;
+      slash = grub_strchr (path, '/');
+      if (!slash)
+       slash = path + grub_strlen (path);
+      ctoken = path;
+      ctokenlen = slash - path;
 
       if (*type != GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY)
        {
          grub_free (path_alloc);
          grub_free (origpath);
-         return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+         return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory"));
+       }
+
+      if (ctokenlen == 1 && ctoken[0] == '.')
+       {
+         path = slash;
+         continue;
+       }
+      if (ctokenlen == 2 && ctoken[0] == '.' && ctoken[1] == '.')
+       {
+         key->type = GRUB_BTRFS_ITEM_TYPE_INODE_REF;
+         key->offset = -1;
+
+         err = lower_bound (data, key, &key_out, *tree, &elemaddr, &elemsize,
+                            NULL, 0);
+         if (err)
+           {
+             grub_free (direl);
+             grub_free (path_alloc);
+             grub_free (origpath);
+             return err;
+           }
+
+         if (key_out.type != key->type
+             || key->object_id != key_out.object_id)
+           {
+             grub_free (direl);
+             grub_free (path_alloc);
+             err = grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath);
+             grub_free (origpath);
+             return err;
+           }
+
+         *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY;
+         key->object_id = key_out.offset;
+
+         path = slash;
+
+         continue;
        }
 
       key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
       key->offset = grub_cpu_to_le64 (~grub_getcrc32c (1, ctoken, ctokenlen));
 
       err = lower_bound (data, key, &key_out, *tree, &elemaddr, &elemsize,
-                        NULL);
+                        NULL, 0);
       if (err)
        {
          grub_free (direl);
@@ -1212,7 +1324,7 @@ find_path (struct grub_btrfs_data *data,
        {
          grub_free (direl);
          grub_free (path_alloc);
-         err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "file `%s' not found", origpath);
+         err = grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath);
          grub_free (origpath);
          return err;
        }
@@ -1231,7 +1343,7 @@ find_path (struct grub_btrfs_data *data,
            }
        }
 
-      err = grub_btrfs_read_logical (data, elemaddr, direl, elemsize);
+      err = grub_btrfs_read_logical (data, elemaddr, direl, elemsize, 0);
       if (err)
        {
          grub_free (direl);
@@ -1256,14 +1368,12 @@ find_path (struct grub_btrfs_data *data,
        {
          grub_free (direl);
          grub_free (path_alloc);
-         err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "file `%s' not found", origpath);
+         err = grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath);
          grub_free (origpath);
          return err;
        }
 
-      if (!skip_default)
-       path = slash;
-      skip_default = 0;
+      path = slash;
       if (cdirel->type == GRUB_BTRFS_DIR_ITEM_TYPE_SYMLINK)
        {
          struct grub_btrfs_inode inode;
@@ -1274,7 +1384,7 @@ find_path (struct grub_btrfs_data *data,
              grub_free (path_alloc);
              grub_free (origpath);
              return grub_error (GRUB_ERR_SYMLINK_LOOP,
-                                "too deep nesting of symlinks");
+                                N_("too deep nesting of symlinks"));
            }
 
          err = grub_btrfs_read_inode (data, &inode,
@@ -1310,16 +1420,12 @@ find_path (struct grub_btrfs_data *data,
          grub_memcpy (tmp + grub_le_to_cpu64 (inode.size), path,
                       grub_strlen (path) + 1);
          grub_free (path_alloc);
-         grub_free (origpath);
          path = path_alloc = tmp;
          if (path[0] == '/')
            {
-             *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY;
-             *tree = data->sblock.root_tree;
-             key->object_id = data->sblock.root_dir_objectid;
-             key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
-             key->offset = 0;
-             skip_default = 1;
+             err = get_root (data, key, tree, type);
+             if (err)
+               return err;
            }
          continue;
        }
@@ -1332,7 +1438,7 @@ find_path (struct grub_btrfs_data *data,
            struct grub_btrfs_root_item ri;
            err = lower_bound (data, &cdirel->key, &key_out,
                               data->sblock.root_tree,
-                              &elemaddr, &elemsize, NULL);
+                              &elemaddr, &elemsize, NULL, 0);
            if (err)
              {
                grub_free (direl);
@@ -1345,11 +1451,12 @@ find_path (struct grub_btrfs_data *data,
              {
                grub_free (direl);
                grub_free (path_alloc);
-               err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "file `%s' not found", origpath);
+               err = grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath);
                grub_free (origpath);
                return err;
              }
-           err = grub_btrfs_read_logical (data, elemaddr, &ri, sizeof (ri));
+           err = grub_btrfs_read_logical (data, elemaddr, &ri,
+                                          sizeof (ri), 0);
            if (err)
              {
                grub_free (direl);
@@ -1359,8 +1466,8 @@ find_path (struct grub_btrfs_data *data,
              }
            key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
            key->offset = 0;
-           key->object_id = GRUB_BTRFS_OBJECT_ID_CHUNK;
-           *tree = grub_le_to_cpu64 (ri.tree);
+           key->object_id = grub_cpu_to_le64_compile_time (GRUB_BTRFS_OBJECT_ID_CHUNK);
+           *tree = ri.tree;
            break;
          }
        case GRUB_BTRFS_ITEM_TYPE_INODE_ITEM:
@@ -1368,7 +1475,7 @@ find_path (struct grub_btrfs_data *data,
            {
              grub_free (direl);
              grub_free (path_alloc);
-             err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "file `%s' not found", origpath);
+             err = grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath);
              grub_free (origpath);
              return err;
            }
@@ -1386,13 +1493,14 @@ find_path (struct grub_btrfs_data *data,
     }
 
   grub_free (direl);
+  grub_free (origpath);
+  grub_free (path_alloc);
   return GRUB_ERR_NONE;
 }
 
 static grub_err_t
 grub_btrfs_dir (grub_device_t device, const char *path,
-               int (*hook) (const char *filename,
-                            const struct grub_dirhook_info * info))
+               grub_fs_dir_hook_t hook, void *hook_data)
 {
   struct grub_btrfs_data *data = grub_btrfs_mount (device);
   struct grub_btrfs_key key_in, key_out;
@@ -1411,22 +1519,29 @@ grub_btrfs_dir (grub_device_t device, const char *path,
 
   err = find_path (data, path, &key_in, &tree, &type);
   if (err)
-    return err;
+    {
+      grub_btrfs_unmount (data);
+      return err;
+    }
   if (type != GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY)
-    return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
+    {
+      grub_btrfs_unmount (data);
+      return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory"));
+    }
 
-  err = lower_bound (data, &key_in, &key_out, tree, &elemaddr, &elemsize, &desc);
+  err = lower_bound (data, &key_in, &key_out, tree,
+                    &elemaddr, &elemsize, &desc, 0);
   if (err)
-    return err;
+    {
+      grub_btrfs_unmount (data);
+      return err;
+    }
   if (key_out.type != GRUB_BTRFS_ITEM_TYPE_DIR_ITEM
       || key_out.object_id != key_in.object_id)
     {
       r = next (data, &desc, &elemaddr, &elemsize, &key_out);
       if (r <= 0)
-       {
-         free_iterator (&desc);
-         return -r;
-       }
+       goto out;
     }
   do
     {
@@ -1444,14 +1559,17 @@ grub_btrfs_dir (grub_device_t device, const char *path,
          direl = grub_malloc (allocated + 1);
          if (!direl)
            {
-             free_iterator (&desc);
-             return grub_errno;
+             r = -grub_errno;
+             break;
            }
        }
 
-      err = grub_btrfs_read_logical (data, elemaddr, direl, elemsize);
+      err = grub_btrfs_read_logical (data, elemaddr, direl, elemsize, 0);
       if (err)
-       return err;
+       {
+         r = -err;
+         break;
+       }
 
       for (cdirel = direl;
           (grub_uint8_t *) cdirel - (grub_uint8_t *) direl
@@ -1470,13 +1588,13 @@ grub_btrfs_dir (grub_device_t device, const char *path,
            grub_errno = GRUB_ERR_NONE;
          else
            {
-             info.mtime = inode.mtime.sec;
+             info.mtime = grub_le_to_cpu64 (inode.mtime.sec);
              info.mtimeset = 1;
            }
          c = cdirel->name[grub_le_to_cpu16 (cdirel->n)];
          cdirel->name[grub_le_to_cpu16 (cdirel->n)] = 0;
          info.dir = (cdirel->type == GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY);
-         if (hook (cdirel->name, &info))
+         if (hook (cdirel->name, &info, hook_data))
            goto out;
          cdirel->name[grub_le_to_cpu16 (cdirel->n)] = c;
        }
@@ -1514,7 +1632,7 @@ grub_btrfs_open (struct grub_file *file, const char *name)
   if (type != GRUB_BTRFS_DIR_ITEM_TYPE_REGULAR)
     {
       grub_btrfs_unmount (data);
-      return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a regular file");
+      return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a regular file"));
     }
 
   data->inode = key_in.object_id;
@@ -1592,6 +1710,38 @@ grub_btrfs_label (grub_device_t device, char **label)
   return grub_errno;
 }
 
+#ifdef GRUB_UTIL
+static grub_err_t
+grub_btrfs_embed (grub_device_t device __attribute__ ((unused)),
+                 unsigned int *nsectors,
+                 unsigned int max_nsectors,
+                 grub_embed_type_t embed_type,
+                 grub_disk_addr_t **sectors)
+{
+  unsigned i;
+
+  if (embed_type != GRUB_EMBED_PCBIOS)
+    return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+                      "BtrFS currently supports only PC-BIOS embedding");
+
+  if (64 * 2 - 1 < *nsectors)
+    return grub_error (GRUB_ERR_OUT_OF_RANGE,
+                      N_("your core.img is unusually large.  "
+                         "It won't fit in the embedding area"));
+
+  *nsectors = 64 * 2 - 1;
+  if (*nsectors > max_nsectors)
+    *nsectors = max_nsectors;
+  *sectors = grub_malloc (*nsectors * sizeof (**sectors));
+  if (!*sectors)
+    return grub_errno;
+  for (i = 0; i < *nsectors; i++)
+    (*sectors)[i] = i + 1;
+
+  return GRUB_ERR_NONE;
+}
+#endif
+
 static struct grub_fs grub_btrfs_fs = {
   .name = "btrfs",
   .dir = grub_btrfs_dir,
@@ -1601,7 +1751,9 @@ static struct grub_fs grub_btrfs_fs = {
   .uuid = grub_btrfs_uuid,
   .label = grub_btrfs_label,
 #ifdef GRUB_UTIL
+  .embed = grub_btrfs_embed,
   .reserved_first_sector = 1,
+  .blocklist_install = 0,
 #endif
 };