#include <grub/disk.h>
#include <grub/dl.h>
#include <grub/types.h>
+ #include <grub/lib/crc.h>
+ #include <grub/deflate.h>
- #define BTRFS_SIGNATURE "_BHRfS_M"
+GRUB_MOD_LICENSE ("GPLv3+");
+
+ #define GRUB_BTRFS_SIGNATURE "_BHRfS_M"
- struct btrfs_superblock
+ typedef grub_uint8_t grub_btrfs_checksum_t[0x20];
+ 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));
+
+ struct grub_btrfs_superblock
{
- grub_uint8_t dummy1[32];
- grub_uint16_t uuid[8];
- grub_uint8_t dummy2[16];
- grub_uint8_t signature[sizeof (BTRFS_SIGNATURE) - 1];
+ grub_btrfs_checksum_t checksum;
+ grub_btrfs_uuid_t uuid;
+ grub_uint8_t dummy[0x10];
+ grub_uint8_t signature[sizeof (GRUB_BTRFS_SIGNATURE) - 1];
+ grub_uint64_t generation;
+ grub_uint64_t root_tree;
+ grub_uint64_t chunk_tree;
+ grub_uint8_t dummy2[0x20];
+ grub_uint64_t root_dir_objectid;
+ grub_uint8_t dummy3[0x41];
+ struct grub_btrfs_device this_device;
+ char label[0x100];
+ grub_uint8_t dummy4[0x100];
+ grub_uint8_t bootstrap_mapping[0x800];
} __attribute__ ((packed));
+ struct btrfs_header
+ {
+ grub_btrfs_checksum_t checksum;
+ grub_btrfs_uuid_t uuid;
+ grub_uint8_t dummy[0x30];
+ grub_uint32_t nitems;
+ grub_uint8_t level;
+ } __attribute__ ((packed));
+
+ struct grub_btrfs_device_desc
+ {
+ grub_device_t dev;
+ grub_uint64_t id;
+ };
+
struct grub_btrfs_data
{
- struct btrfs_superblock sblock;
+ struct grub_btrfs_superblock sblock;
+ grub_uint64_t tree;
+ grub_uint64_t inode;
+
+ struct grub_btrfs_device_desc *devices_attached;
+ unsigned n_devices_attached;
+ unsigned n_devices_allocated;
+
+ /* Cached extent data. */
+ grub_uint64_t extstart;
+ grub_uint64_t extend;
+ grub_uint64_t extino;
+ grub_uint64_t exttree;
+ grub_size_t extsize;
+ 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;
+ grub_uint64_t dummy;
+ grub_uint64_t stripe_length;
+ grub_uint64_t type;
+ #define GRUB_BTRFS_CHUNK_TYPE_BITS_DONTCARE 0x07
+ #define GRUB_BTRFS_CHUNK_TYPE_SINGLE 0x00
+ #define GRUB_BTRFS_CHUNK_TYPE_RAID0 0x08
+ #define GRUB_BTRFS_CHUNK_TYPE_RAID1 0x10
+ #define GRUB_BTRFS_CHUNK_TYPE_DUPLICATED 0x20
+ #define GRUB_BTRFS_CHUNK_TYPE_RAID10 0x40
+ grub_uint8_t dummy2[0xc];
+ grub_uint16_t nstripes;
+ grub_uint16_t nsubstripes;
+ } __attribute__ ((packed));
+
+ struct grub_btrfs_chunk_stripe
+ {
+ grub_uint64_t device_id;
+ grub_uint64_t offset;
+ grub_btrfs_uuid_t device_uuid;
+ } __attribute__ ((packed));
+
+ struct grub_btrfs_leaf_node
+ {
+ struct grub_btrfs_key key;
+ grub_uint32_t offset;
+ grub_uint32_t size;
+ } __attribute__ ((packed));
+
+ struct grub_btrfs_internal_node
+ {
+ struct grub_btrfs_key key;
+ grub_uint64_t addr;
+ grub_uint64_t dummy;
+ } __attribute__ ((packed));
+
+ struct grub_btrfs_dir_item
+ {
+ struct grub_btrfs_key key;
+ grub_uint8_t dummy[8];
+ grub_uint16_t m;
+ grub_uint16_t n;
+ #define GRUB_BTRFS_DIR_ITEM_TYPE_REGULAR 1
+ #define GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY 2
+ #define GRUB_BTRFS_DIR_ITEM_TYPE_SYMLINK 7
+ grub_uint8_t type;
+ char name[0];
+ } __attribute__ ((packed));
+
+ struct grub_btrfs_leaf_descriptor
+ {
+ unsigned depth;
+ unsigned allocated;
+ struct {
+ grub_disk_addr_t addr;
+ unsigned iter;
+ unsigned maxiter;
+ int leaf;
+ } *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;
+ grub_uint32_t nanosec;
+ } __attribute__ ((aligned(4)));
+
+ struct grub_btrfs_inode
+ {
+ grub_uint8_t dummy1[0x10];
+ grub_uint64_t size;
+ grub_uint8_t dummy2[0x70];
+ struct grub_btrfs_time mtime;
+ } __attribute__ ((packed));
+
+ struct grub_btrfs_extent_data
+ {
+ grub_uint64_t dummy;
+ grub_uint64_t size;
+ grub_uint8_t compression;
+ grub_uint8_t encryption;
+ grub_uint16_t encoding;
+ grub_uint8_t type;
+ union
+ {
+ char inl[0];
+ struct
+ {
+ grub_uint64_t laddr;
+ grub_uint64_t compressed_size;
+ grub_uint64_t offset;
+ grub_uint64_t filled;
+ };
+ };
+ } __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
+
+ static grub_disk_addr_t superblock_sectors[] = { 64 * 2, 64 * 1024 * 2,
+ 256 * 1048576 * 2,
+ 1048576ULL * 1048576ULL * 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);
+
+ static grub_err_t
+ read_sblock (grub_disk_t disk, struct grub_btrfs_superblock *sb)
+ {
+ unsigned i;
+ grub_err_t err = GRUB_ERR_NONE;
+ for (i = 0; i < ARRAY_SIZE (superblock_sectors); i++)
+ {
+ struct grub_btrfs_superblock sblock;
+ err = grub_disk_read (disk, superblock_sectors[i], 0,
+ sizeof (sblock), &sblock);
+ if (err == GRUB_ERR_OUT_OF_RANGE)
+ break;
+
+ if (grub_memcmp ((char *) sblock.signature, GRUB_BTRFS_SIGNATURE,
+ sizeof (GRUB_BTRFS_SIGNATURE) - 1) != 0)
+ break;
+ if (i == 0 || grub_le_to_cpu64 (sblock.generation)
+ > grub_le_to_cpu64 (sb->generation))
+ grub_memcpy (sb, &sblock, sizeof (sblock));
+ }
+
+ if ((err == GRUB_ERR_OUT_OF_RANGE || !err) && i == 0)
+ return grub_error (GRUB_ERR_BAD_FS, "not a Btrfs filesystem");
+
+ if (err == GRUB_ERR_OUT_OF_RANGE)
+ grub_errno = err = GRUB_ERR_NONE;
+
+ return err;
+ }
+
+ 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))
+ return -1;
+ if (grub_cpu_to_le64 (a->object_id) > grub_cpu_to_le64 (b->object_id))
+ return +1;
+
+ if (a->type < b->type)
+ return -1;
+ if (a->type > b->type)
+ return +1;
+
+ if (grub_cpu_to_le64 (a->offset) < grub_cpu_to_le64 (b->offset))
+ return -1;
+ if (grub_cpu_to_le64 (a->offset) > grub_cpu_to_le64 (b->offset))
+ return +1;
+ return 0;
+ }
+
+ static void
+ free_iterator (struct grub_btrfs_leaf_descriptor *desc)
+ {
+ grub_free (desc->data);
+ }
+
+ static grub_err_t
+ save_ref (struct grub_btrfs_leaf_descriptor *desc,
+ grub_disk_addr_t addr, unsigned i, unsigned m, int l)
+ {
+ desc->depth++;
+ if (desc->allocated < desc->depth)
+ {
+ void *newdata;
+ desc->allocated *= 2;
+ newdata = grub_realloc (desc->data, sizeof (desc->data[0])
+ * desc->allocated);
+ if (!newdata)
+ return grub_errno;
+ desc->data = newdata;
+ }
+ desc->data[desc->depth - 1].addr = addr;
+ desc->data[desc->depth - 1].iter = i;
+ desc->data[desc->depth - 1].maxiter = m;
+ desc->data[desc->depth - 1].leaf = l;
+ return GRUB_ERR_NONE;
+ }
+
+ static int
+ next (struct grub_btrfs_data *data,
+ struct grub_btrfs_leaf_descriptor *desc,
+ grub_disk_addr_t *outaddr, grub_size_t *outsize,
+ struct grub_btrfs_key *key_out)
+ {
+ grub_err_t err;
+ struct grub_btrfs_leaf_node leaf;
+
+ 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)
+ break;
+ }
+ if (desc->depth == 0)
+ return 0;
+ while (!desc->data[desc->depth - 1].leaf)
+ {
+ struct grub_btrfs_internal_node node;
+ struct btrfs_header head;
+
+ err = grub_btrfs_read_logical (data, desc->data[desc->depth - 1].iter
+ * sizeof (node)
+ + sizeof (struct btrfs_header)
+ + desc->data[desc->depth - 1].addr, &node,
+ sizeof (node));
+ if (err)
+ return -err;
+
+ err = grub_btrfs_read_logical (data, grub_le_to_cpu64 (node.addr), &head,
+ sizeof (head));
+ if (err)
+ return -err;
+
+ save_ref (desc, grub_le_to_cpu64 (node.addr), 0,
+ grub_le_to_cpu32 (head.nitems), !head.level);
+ }
+ err = grub_btrfs_read_logical (data, desc->data[desc->depth - 1].iter
+ * sizeof (leaf)
+ + sizeof (struct btrfs_header)
+ + desc->data[desc->depth - 1].addr, &leaf,
+ sizeof (leaf));
+ if (err)
+ return -err;
+ *outsize = grub_le_to_cpu32 (leaf.size);
+ *outaddr = desc->data[desc->depth - 1].addr + sizeof (struct btrfs_header)
+ + grub_le_to_cpu32 (leaf.offset);
+ *key_out = leaf.key;
+ return 1;
+ }
+
+ 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_disk_addr_t addr = root;
+ int depth = -1;
+
+ if (desc)
+ {
+ desc->allocated = 16;
+ desc->depth = 0;
+ desc->data = grub_malloc (sizeof (desc->data[0]) * desc->allocated);
+ if (!desc->data)
+ return grub_errno;
+ }
+
+ grub_dprintf ("btrfs",
+ "retrieving %" PRIxGRUB_UINT64_T
+ " %x %" PRIxGRUB_UINT64_T "\n",
+ key_in->object_id, key_in->type, key_in->offset);
+
+ while (1)
+ {
+ grub_err_t err;
+ struct btrfs_header head;
+
+ reiter:
+ depth++;
+ /* FIXME: preread few nodes into buffer. */
+ err = grub_btrfs_read_logical (data, addr, &head, sizeof (head));
+ if (err)
+ return err;
+ addr += sizeof (head);
+ if (head.level)
+ {
+ unsigned i;
+ struct grub_btrfs_internal_node node, node_last;
+ int have_last = 0;
+ grub_memset (&node_last, 0, sizeof (node_last));
+ for (i = 0; i < grub_le_to_cpu32 (head.nitems); i++)
+ {
+ err = grub_btrfs_read_logical (data, addr + i * sizeof (node),
+ &node, sizeof (node));
+ if (err)
+ return err;
+
+ grub_dprintf ("btrfs",
+ "internal node (depth %d) %" PRIxGRUB_UINT64_T
+ " %x %" PRIxGRUB_UINT64_T "\n", depth,
+ node.key.object_id, node.key.type, node.key.offset);
+
+ if (key_cmp (&node.key, key_in) == 0)
+ {
+ err = GRUB_ERR_NONE;
+ if (desc)
+ err = save_ref (desc, addr - sizeof (head), i,
+ grub_le_to_cpu32 (head.nitems), 0);
+ if (err)
+ return err;
+ addr = grub_le_to_cpu64 (node.addr);
+ goto reiter;
+ }
+ if (key_cmp (&node.key, key_in) > 0)
+ break;
+ node_last = node;
+ have_last = 1;
+ }
+ if (have_last)
+ {
+ err = GRUB_ERR_NONE;
+ if (desc)
+ err = save_ref (desc, addr - sizeof (head), i - 1,
+ grub_le_to_cpu32 (head.nitems), 0);
+ if (err)
+ return err;
+ addr = grub_le_to_cpu64 (node_last.addr);
+ goto reiter;
+ }
+ *outsize = 0;
+ *outaddr = 0;
+ grub_memset (key_out, 0, sizeof (*key_out));
+ if (desc)
+ return save_ref (desc, addr - sizeof (head), -1,
+ grub_le_to_cpu32 (head.nitems), 0);
+ return GRUB_ERR_NONE;
+ }
+ {
+ unsigned i;
+ struct grub_btrfs_leaf_node leaf, leaf_last;
+ int have_last = 0;
+ for (i = 0; i < grub_le_to_cpu32 (head.nitems); i++)
+ {
+ err = grub_btrfs_read_logical (data, addr + i * sizeof (leaf),
+ &leaf, sizeof (leaf));
+ if (err)
+ return err;
+
+ grub_dprintf ("btrfs",
+ "leaf (depth %d) %" PRIxGRUB_UINT64_T
+ " %x %" PRIxGRUB_UINT64_T "\n", depth,
+ leaf.key.object_id, leaf.key.type, leaf.key.offset);
+
+ if (key_cmp (&leaf.key, key_in) == 0)
+ {
+ grub_memcpy (key_out, &leaf.key, sizeof(*key_out));
+ *outsize = grub_le_to_cpu32 (leaf.size);
+ *outaddr = addr + grub_le_to_cpu32 (leaf.offset);
+ if (desc)
+ return save_ref (desc, addr - sizeof (head), i,
+ grub_le_to_cpu32 (head.nitems), 1);
+ return GRUB_ERR_NONE;
+ }
+
+ if (key_cmp (&leaf.key, key_in) > 0)
+ break;
+
+ have_last = 1;
+ leaf_last = leaf;
+ }
+
+ if (have_last)
+ {
+ grub_memcpy (key_out, &leaf_last.key, sizeof(*key_out));
+ *outsize = grub_le_to_cpu32 (leaf_last.size);
+ *outaddr = addr + grub_le_to_cpu32 (leaf_last.offset);
+ if (desc)
+ return save_ref (desc, addr - sizeof (head), i - 1,
+ grub_le_to_cpu32 (head.nitems), 1);
+ return GRUB_ERR_NONE;
+ }
+ *outsize = 0;
+ *outaddr = 0;
+ grub_memset (key_out, 0, sizeof (*key_out));
+ if (desc)
+ return save_ref (desc, addr - sizeof (head), -1,
+ grub_le_to_cpu32 (head.nitems), 1);
+ return GRUB_ERR_NONE;
+ }
+ }
+ }
+
+ static grub_device_t
+ find_device (struct grub_btrfs_data *data, grub_uint64_t id,
+ int do_rescan)
+ {
+ 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)
+ 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;
+ }
+
+ dev_found = dev;
+ return 1;
+ }
+
+ 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_error (GRUB_ERR_BAD_FS, "couldn't find a member device");
+ return NULL;
+ }
+ data->n_devices_attached++;
+ if (data->n_devices_attached > data->n_devices_allocated)
+ {
+ void *tmp;
+ data->n_devices_allocated = 2 * data->n_devices_attached + 1;
+ data->devices_attached
+ = grub_realloc (tmp = data->devices_attached,
+ data->n_devices_allocated
+ * sizeof (data->devices_attached[0]));
+ if (!data->devices_attached)
+ {
+ grub_device_close (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;
+ }
+
+ static grub_err_t
+ grub_btrfs_read_logical (struct grub_btrfs_data *data,
+ grub_disk_addr_t addr,
+ void *buf, grub_size_t size)
+ {
+ while (size > 0)
+ {
+ grub_uint8_t *ptr;
+ struct grub_btrfs_key *key;
+ struct grub_btrfs_chunk_item *chunk;
+ grub_ssize_t csize;
+ grub_err_t err;
+ struct grub_btrfs_key key_out;
+ int challoc = 0;
+ grub_device_t dev;
+ grub_dprintf ("btrfs", "searching for laddr %" PRIxGRUB_UINT64_T "\n",
+ addr);
+ for (ptr = data->sblock.bootstrap_mapping;
+ ptr < data->sblock.bootstrap_mapping
+ + sizeof (data->sblock.bootstrap_mapping)
+ - sizeof (struct grub_btrfs_key);
+ )
+ {
+ key = (struct grub_btrfs_key *) ptr;
+ if (key->type != GRUB_BTRFS_ITEM_TYPE_CHUNK)
+ break;
+ chunk = (struct grub_btrfs_chunk_item *) (key + 1);
+ grub_dprintf ("btrfs", "%" PRIxGRUB_UINT64_T " %" PRIxGRUB_UINT64_T " \n",
+ 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))
+ goto chunk_found;
+ ptr += sizeof (*key) + sizeof (*chunk)
+ + sizeof (struct grub_btrfs_chunk_stripe)
+ * grub_le_to_cpu16 (chunk->nstripes);
+ }
+ struct grub_btrfs_key key_in;
+ grub_size_t chsize;
+ grub_disk_addr_t chaddr;
+ key_in.object_id = GRUB_BTRFS_OBJECT_ID_CHUNK;
+ key_in.type = GRUB_BTRFS_ITEM_TYPE_CHUNK;
+ key_in.offset = addr;
+ err = lower_bound (data, &key_in, &key_out,
+ grub_le_to_cpu64 (data->sblock.chunk_tree),
+ &chaddr, &chsize, NULL);
+ if (err)
+ return err;
+ key = &key_out;
+ if (key->type != GRUB_BTRFS_ITEM_TYPE_CHUNK
+ || !(grub_le_to_cpu64 (key->offset) <= addr))
+ return grub_error (GRUB_ERR_BAD_FS,
+ "couldn't find the chunk descriptor");
+
+ chunk = grub_malloc (chsize);
+ if (!chunk)
+ return grub_errno;
+
+ challoc = 1;
+ err = grub_btrfs_read_logical (data, chaddr, chunk, chsize);
+ if (err)
+ {
+ grub_free (chunk);
+ return err;
+ }
+
+ chunk_found:
+ {
+ grub_uint32_t stripen;
+ grub_uint32_t stripe_offset;
+ grub_uint64_t off = addr - grub_le_to_cpu64 (key->offset);
+ unsigned redundancy = 1;
+ unsigned i, j;
+
+ grub_dprintf ("btrfs", "chunk 0x%" PRIxGRUB_UINT64_T
+ "+0x%" PRIxGRUB_UINT64_T
+ " (%d stripes (%d substripes) of %"
+ PRIxGRUB_UINT64_T ")\n",
+ grub_le_to_cpu64 (key->offset),
+ grub_le_to_cpu64 (chunk->size),
+ grub_le_to_cpu16 (chunk->nstripes),
+ grub_le_to_cpu16 (chunk->nsubstripes),
+ grub_le_to_cpu64 (chunk->stripe_length));
+
+ switch (grub_le_to_cpu64 (chunk->type)
+ & ~GRUB_BTRFS_CHUNK_TYPE_BITS_DONTCARE)
+ {
+ case GRUB_BTRFS_CHUNK_TYPE_SINGLE:
+ {
+ grub_uint32_t stripe_length;
+ stripe_length = grub_divmod64 (grub_le_to_cpu64 (chunk->size),
+ grub_le_to_cpu16 (chunk->nstripes),
+ NULL);
+ stripen = grub_divmod64 (off, stripe_length, &stripe_offset);
+ csize = (stripen + 1) * stripe_length - off;
+ break;
+ }
+ case GRUB_BTRFS_CHUNK_TYPE_DUPLICATED:
+ case GRUB_BTRFS_CHUNK_TYPE_RAID1:
+ {
+ stripen = 0;
+ stripe_offset = off;
+ csize = grub_le_to_cpu64 (chunk->size) - off;
+ redundancy = 2;
+ break;
+ }
+ case GRUB_BTRFS_CHUNK_TYPE_RAID0:
+ {
+ grub_uint64_t middle, high;
+ grub_uint32_t low;
+ middle = grub_divmod64 (off,
+ grub_le_to_cpu64 (chunk->stripe_length),
+ &low);
+
+ high = grub_divmod64 (middle, grub_le_to_cpu16 (chunk->nstripes),
+ &stripen);
+ stripe_offset = low + grub_le_to_cpu64 (chunk->stripe_length)
+ * high;
+ csize = grub_le_to_cpu64 (chunk->stripe_length) - low;
+ break;
+ }
+ case GRUB_BTRFS_CHUNK_TYPE_RAID10:
+ {
+ grub_uint64_t middle, high;
+ grub_uint32_t low;
+ middle = grub_divmod64 (off,
+ grub_le_to_cpu64 (chunk->stripe_length),
+ &low);
+
+ high = grub_divmod64 (middle,
+ 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);
+ stripe_offset = low + grub_le_to_cpu64 (chunk->stripe_length)
+ * high;
+ csize = grub_le_to_cpu64 (chunk->stripe_length) - low;
+ break;
+ }
+ default:
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "unsupported RAID flags %" PRIxGRUB_UINT64_T,
+ grub_le_to_cpu64 (chunk->type));
+ }
+ if (csize <= 0)
+ return grub_error (GRUB_ERR_BAD_FS,
+ "couldn't find the chunk descriptor");
+ if ((grub_size_t) csize > size)
+ csize = size;
+
+ for (j = 0; j < 2; j++)
+ {
+ for (i = 0; i < redundancy; i++)
+ {
+ struct grub_btrfs_chunk_stripe *stripe;
+ grub_disk_addr_t paddr;
+
+ stripe = (struct grub_btrfs_chunk_stripe *) (chunk + 1);
+ /* Right now the redundancy handling is easy.
+ With RAID5-like it will be more difficult. */
+ stripe += stripen + i;
+
+ paddr = stripe->offset + stripe_offset;
+
+ grub_dprintf ("btrfs", "chunk 0x%" PRIxGRUB_UINT64_T
+ "+0x%" PRIxGRUB_UINT64_T " (%d stripes (%d substripes) of %"
+ PRIxGRUB_UINT64_T ") stripe %" PRIxGRUB_UINT32_T
+ " maps to 0x%" PRIxGRUB_UINT64_T "\n",
+ grub_le_to_cpu64 (key->offset),
+ grub_le_to_cpu64 (chunk->size),
+ grub_le_to_cpu16 (chunk->nstripes),
+ grub_le_to_cpu16 (chunk->nsubstripes),
+ grub_le_to_cpu64 (chunk->stripe_length),
+ stripen, stripe->offset);
+ grub_dprintf ("btrfs", "reading paddr 0x%" PRIxGRUB_UINT64_T
+ " for laddr 0x%" PRIxGRUB_UINT64_T"\n", paddr,
+ addr);
+
+ dev = find_device (data, stripe->device_id, j);
+ if (!dev)
+ {
+ err = grub_errno;
+ grub_errno = GRUB_ERR_NONE;
+ continue;
+ }
+
+ err = grub_disk_read (dev->disk, paddr >> GRUB_DISK_SECTOR_BITS,
+ paddr & (GRUB_DISK_SECTOR_SIZE - 1),
+ csize, buf);
+ if (!err)
+ break;
+ grub_errno = GRUB_ERR_NONE;
+ }
+ if (i != redundancy)
+ break;
+ }
+ if (err)
+ return grub_errno = err;
+ }
+ size -= csize;
+ buf = (grub_uint8_t *) buf + csize;
+ addr += csize;
+ if (challoc)
+ grub_free (chunk);
+ }
+ return GRUB_ERR_NONE;
+ }
+
static struct grub_btrfs_data *
- grub_btrfs_mount (grub_disk_t disk)
+ grub_btrfs_mount (grub_device_t dev)
{
- struct grub_btrfs_data *data = grub_malloc (sizeof (*data));
+ struct grub_btrfs_data *data;
+ grub_err_t err;
+
+ if (!dev->disk)
+ {
+ grub_error (GRUB_ERR_BAD_FS, "not BtrFS");
+ return NULL;
+ }
+
+ data = grub_zalloc (sizeof (*data));
if (! data)
return NULL;
.name = "btrfs",
.dir = grub_btrfs_dir,
.open = grub_btrfs_open,
+ .read = grub_btrfs_read,
+ .close = grub_btrfs_close,
.uuid = grub_btrfs_uuid,
+ .label = grub_btrfs_label,
+#ifdef GRUB_UTIL
+ .reserved_first_sector = 1,
+#endif
};
GRUB_MOD_INIT(btrfs)