#include <grub/disk.h>
#include <grub/dl.h>
#include <grub/types.h>
+#include <grub/lib/crc.h>
#define BTRFS_SIGNATURE "_BHRfS_M"
+typedef grub_uint8_t btrfs_checksum_t[0x20];
+typedef grub_uint16_t btrfs_uuid_t[8];
+
+struct grub_btrfs_device
+{
+ grub_uint64_t device_id;
+ grub_uint8_t dummy[0x62 - 8];
+} __attribute__ ((packed));
+
struct btrfs_superblock
{
- grub_uint8_t dummy1[32];
- grub_uint16_t uuid[8];
- grub_uint8_t dummy2[16];
+ btrfs_checksum_t checksum;
+ btrfs_uuid_t uuid;
+ grub_uint8_t dummy[0x10];
grub_uint8_t signature[sizeof (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
+{
+ btrfs_checksum_t checksum;
+ btrfs_uuid_t uuid;
+ grub_uint8_t dummy[0x30];
+ grub_uint32_t nitems;
+ grub_uint8_t level;
} __attribute__ ((packed));
struct grub_btrfs_data
{
struct btrfs_superblock sblock;
+ unsigned int sblock_number;
+ grub_uint64_t tree;
+ grub_uint64_t inode;
};
+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_uint8_t dummy2[0x14];
+ grub_uint16_t nstripes;
+ grub_uint16_t dummy3;
+} __attribute__ ((packed));
+
+struct grub_btrfs_chunk_stripe
+{
+ grub_uint64_t device_id;
+ grub_uint64_t offset;
+ 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_dir_item
+{
+ struct grub_btrfs_key key;
+ grub_uint8_t dummy[8];
+ grub_uint16_t m;
+ grub_uint16_t n;
+ 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_inode
+{
+ grub_uint8_t dummy[0x10];
+ grub_uint64_t size;
+} __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];
+ grub_uint64_t laddr;
+ };
+} __attribute__ ((packed));
+
+#define GRUB_BTRFS_EXTENT_INLINE 0
+#define GRUB_BTRFS_EXTENT_REGULAR 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_t disk, grub_disk_addr_t addr,
+ void *buf, grub_size_t size);
+
+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 int
+next (struct grub_btrfs_data *data, grub_disk_t disk,
+ struct grub_btrfs_leaf_descriptor *desc,
+ grub_disk_addr_t *outaddr, grub_size_t *outsize,
+ struct grub_btrfs_key *key_out)
+{
+ int i;
+ grub_err_t err;
+ struct grub_btrfs_leaf_node leaf;
+
+ if (desc->depth == 0)
+ return 0;
+ for (i = desc->depth - 1; i >= 0; i--)
+ {
+ desc->data[i].iter++;
+ if (desc->data[i].iter
+ < desc->data[desc->depth - 1].maxiter)
+ break;
+ desc->depth--;
+ }
+ if (i == -1)
+ return 0;
+ while (!desc->data[desc->depth - 1].leaf)
+ {
+ grub_printf ("No trees\n");
+ return -grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "no trees yet");
+ }
+ err = grub_btrfs_read_logical (data, disk,
+ 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
+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[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 grub_err_t
+lower_bound (struct grub_btrfs_data *data, grub_disk_t disk,
+ 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;
+ struct btrfs_header head;
+ grub_err_t err;
+ unsigned i;
+ struct grub_btrfs_leaf_node leaf, leaf_last;
+ int have_last = 0;
+
+ if (desc)
+ {
+ desc->allocated = 16;
+ desc->depth = 0;
+ desc->data = grub_malloc (sizeof (desc->data[0]) * desc->allocated);
+ if (!desc->data)
+ return grub_errno;
+ }
+
+ while (1)
+ {
+ /* FIXME: preread few leafs into buffer. */
+ err = grub_btrfs_read_logical (data, disk, addr, &head, sizeof (head));
+ if (err)
+ return err;
+ if (head.level)
+ {
+ grub_printf ("No trees\n");
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "trees aren't implemented yet");
+ }
+ addr += sizeof (head);
+ for (i = 0; i < grub_le_to_cpu32 (head.nitems); i++)
+ {
+ err = grub_btrfs_read_logical (data, disk, addr + i * sizeof (leaf),
+ &leaf, sizeof (leaf));
+ if (err)
+ return err;
+
+ grub_dprintf ("btrfs",
+ "%" PRIxGRUB_UINT64_T " %x %" PRIxGRUB_UINT64_T "\n",
+ 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_err_t
+grub_btrfs_read_logical (struct grub_btrfs_data *data,
+ grub_disk_t disk, 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;
+ struct grub_btrfs_chunk_stripe *stripe;
+ grub_size_t csize;
+ grub_err_t err;
+ grub_disk_addr_t paddr;
+ grub_uint64_t stripen;
+ grub_uint32_t stripe_length;
+ grub_uint32_t stripe_offset;
+ struct grub_btrfs_key key_out;
+ int challoc = 0;
+ 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 (*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, disk,
+ &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, disk, chaddr,
+ chunk, chsize);
+ if (err)
+ {
+ grub_free (chunk);
+ return err;
+ }
+
+ if (!(addr < grub_le_to_cpu64 (key->offset)
+ + grub_le_to_cpu64 (chunk->size)))
+ return grub_error (GRUB_ERR_BAD_FS,
+ "couldn't find the chunk descriptor");
+
+ chunk_found:
+ stripe_length = grub_divmod64 (grub_le_to_cpu64 (chunk->size),
+ grub_le_to_cpu16 (chunk->nstripes),
+ NULL);
+ stripen = grub_divmod64 (addr - grub_le_to_cpu64 (key->offset),
+ stripe_length, &stripe_offset);
+ stripe = (struct grub_btrfs_chunk_stripe *) (chunk + 1);
+ stripe += stripen;
+ csize = grub_le_to_cpu64 (key->offset) + grub_le_to_cpu64 (chunk->size)
+ - addr;
+ if (csize > size)
+ csize = size;
+ if (grub_le_to_cpu64 (stripe->device_id) != grub_le_to_cpu64 (data->sblock.this_device.device_id))
+ {
+ if (challoc)
+ grub_free (chunk);
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "multidevice isn't implemented yet");
+ }
+ grub_dprintf ("btrfs", "chunk 0x%" PRIxGRUB_UINT64_T
+ "+0x%" PRIxGRUB_UINT64_T " (%d stripes of %"
+ PRIxGRUB_UINT64_T ") stripe %" PRIxGRUB_UINT64_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_cpu64 (chunk->stripe_length),
+ stripen,
+ stripe->offset);
+ paddr = stripe->offset + stripe_offset;
+
+ grub_dprintf ("btrfs", "reading paddr 0x%" PRIxGRUB_UINT64_T
+ " for laddr 0x%" PRIxGRUB_UINT64_T"\n", paddr,
+ addr);
+ err = grub_disk_read (disk, paddr >> GRUB_DISK_SECTOR_BITS,
+ paddr & (GRUB_DISK_SECTOR_SIZE - 1), csize, buf);
+ 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)
{
struct grub_btrfs_data *data = grub_malloc (sizeof (*data));
+ unsigned i;
+ grub_err_t err = GRUB_ERR_NONE;
+
if (! data)
return NULL;
- if (grub_disk_read (disk, 128, 0, sizeof (data->sblock),
- &data->sblock) != GRUB_ERR_NONE)
- goto fail;
+ for (i = 0; i < ARRAY_SIZE (superblock_sectors); i++)
+ {
+ struct 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 *) data->sblock.signature, BTRFS_SIGNATURE, sizeof (BTRFS_SIGNATURE) - 1))
+ if (grub_memcmp ((char *) sblock.signature, BTRFS_SIGNATURE,
+ sizeof (BTRFS_SIGNATURE) - 1))
+ break;
+ if (i == 0 || grub_le_to_cpu64 (sblock.generation)
+ > grub_le_to_cpu64 (data->sblock.generation))
+ {
+ grub_memcpy (&data->sblock, &sblock, sizeof (sblock));
+ data->sblock_number = i;
+ }
+ }
+
+ if ((err == GRUB_ERR_OUT_OF_RANGE || !err) && i == 0)
{
grub_error (GRUB_ERR_BAD_FS, "not a Btrfs filesystem");
goto fail;
}
+ if (err == GRUB_ERR_OUT_OF_RANGE)
+ grub_errno = err = GRUB_ERR_NONE;
+
+ grub_dprintf ("btrfs", "using superblock %d\n", data->sblock_number);
+
return data;
fail:
}
static grub_err_t
-grub_btrfs_open (struct grub_file *file __attribute__ ((unused)),
- const char *name __attribute__ ((unused)))
+find_path (struct grub_btrfs_data *data,
+ grub_disk_t disk,
+ const char *path, struct grub_btrfs_key *key,
+ grub_uint64_t *tree)
{
- return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "only detection is supported for Btrfs");
+ const char *slash;
+ grub_err_t err;
+ grub_disk_addr_t elemaddr;
+ grub_size_t elemsize;
+ grub_size_t allocated = 0;
+ struct grub_btrfs_dir_item *direl = NULL;
+ struct grub_btrfs_key key_out;
+
+ *tree = data->sblock.root_tree;
+ key->object_id = data->sblock.root_dir_objectid;
+
+ while (1)
+ {
+ key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
+ key->offset = 0;
+ while (path[0] == '/')
+ path++;
+ if (!path[0])
+ break;
+ slash = grub_strchr (path, '/');
+ if (!slash)
+ slash = path + grub_strlen (path);
+ key->offset = grub_cpu_to_le64 (~grub_getcrc32c (1, path, slash - path));
+
+ err = lower_bound (data, disk, key, &key_out, *tree,
+ &elemaddr, &elemsize, NULL);
+ if (err)
+ {
+ grub_free (direl);
+ return err;
+ }
+ if (key_cmp (key, &key_out) != 0)
+ {
+ grub_free (direl);
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+ }
+
+ struct grub_btrfs_dir_item *cdirel;
+ if (elemsize > allocated)
+ {
+ allocated = 2 * elemsize;
+ grub_free (direl);
+ direl = grub_malloc (allocated + 1);
+ if (!direl)
+ return grub_errno;
+ }
+
+ err = grub_btrfs_read_logical (data, disk, elemaddr,
+ direl, elemsize);
+ if (err)
+ {
+ grub_free (direl);
+ return err;
+ }
+
+ for (cdirel = direl;
+ (grub_uint8_t *) cdirel - (grub_uint8_t *) direl
+ < (grub_ssize_t) elemsize;
+ cdirel = (void *) ((grub_uint8_t *) (direl + 1)
+ + grub_le_to_cpu16 (cdirel->n)
+ + grub_le_to_cpu16 (cdirel->m)))
+ {
+ char c;
+ c = cdirel->name[grub_le_to_cpu16 (cdirel->n)];
+ cdirel->name[grub_le_to_cpu16 (cdirel->n)] = 0;
+ if (grub_strncmp (cdirel->name, path, slash - path) == 0)
+ break;
+ cdirel->name[grub_le_to_cpu16 (cdirel->n)] = c;
+ }
+ if ((grub_uint8_t *) cdirel - (grub_uint8_t *) direl
+ >= (grub_ssize_t) elemsize)
+ {
+ grub_free (direl);
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+ }
+
+ path = slash;
+
+ switch (cdirel->key.type)
+ {
+ case GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM:
+ {
+ struct grub_btrfs_root_item ri;
+ err = lower_bound (data, disk, &cdirel->key, &key_out, *tree,
+ &elemaddr, &elemsize, NULL);
+ if (err)
+ return err;
+ if (cdirel->key.object_id != key_out.object_id
+ || cdirel->key.type != key_out.type)
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+ err = grub_btrfs_read_logical (data, disk, elemaddr,
+ &ri, sizeof (ri));
+ if (err)
+ return err;
+ 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);
+ break;
+ }
+ case GRUB_BTRFS_ITEM_TYPE_INODE_ITEM:
+ if (*slash)
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
+ *key = cdirel->key;
+ break;
+ default:
+ return grub_error (GRUB_ERR_BAD_FS, "unrecognised object type 0x%x",
+ cdirel->key.type);
+ }
+ }
+
+ grub_free (direl);
+
+ return GRUB_ERR_NONE;
}
static grub_err_t
grub_btrfs_dir (grub_device_t device,
const char *path __attribute__ ((unused)),
int (*hook) (const char *filename,
- const struct grub_dirhook_info *info)
- __attribute__ ((unused)))
+ const struct grub_dirhook_info *info))
{
struct grub_btrfs_data *data = grub_btrfs_mount (device->disk);
- if (grub_errno)
+ struct grub_btrfs_key key_in, key_out;
+ grub_err_t err;
+ grub_disk_addr_t elemaddr;
+ grub_size_t elemsize;
+ grub_size_t allocated = 0;
+ struct grub_btrfs_dir_item *direl = NULL;
+ struct grub_btrfs_leaf_descriptor desc;
+ int r;
+ grub_uint64_t tree;
+
+ if (!data)
return grub_errno;
+ err = find_path (data, device->disk, path, &key_in, &tree);
+ if (err)
+ return err;
+
+ err = lower_bound (data, device->disk, &key_in, &key_out,
+ tree,
+ &elemaddr, &elemsize, &desc);
+ if (err)
+ return err;
+ if (key_out.type != GRUB_BTRFS_ITEM_TYPE_DIR_ITEM
+ || key_out.object_id != key_in.object_id)
+ {
+ r = next (data, device->disk, &desc, &elemaddr, &elemsize, &key_out);
+ if (r <= 0)
+ {
+ free_iterator (&desc);
+ return -r;
+ }
+ }
+ do
+ {
+ struct grub_dirhook_info info;
+ struct grub_btrfs_dir_item *cdirel;
+ if (key_out.type != GRUB_BTRFS_ITEM_TYPE_DIR_ITEM
+ || key_out.object_id != key_in.object_id)
+ {
+ r = 0;
+ break;
+ }
+ if (elemsize > allocated)
+ {
+ allocated = 2 * elemsize;
+ grub_free (direl);
+ direl = grub_malloc (allocated + 1);
+ if (!direl)
+ {
+ free_iterator (&desc);
+ return grub_errno;
+ }
+ }
+
+ err = grub_btrfs_read_logical (data, device->disk, elemaddr,
+ direl, elemsize);
+ if (err)
+ return err;
+
+ for (cdirel = direl;
+ (grub_uint8_t *) cdirel - (grub_uint8_t *) direl
+ < (grub_ssize_t) elemsize;
+ cdirel = (void *) ((grub_uint8_t *) (direl + 1)
+ + grub_le_to_cpu16 (cdirel->n)
+ + grub_le_to_cpu16 (cdirel->m)))
+ {
+ char c;
+ c = cdirel->name[grub_le_to_cpu16 (cdirel->n)];
+ cdirel->name[grub_le_to_cpu16 (cdirel->n)] = 0;
+ grub_memset (&info, 0, sizeof (info));
+ info.dir = (cdirel->type == 2);
+ if (hook (cdirel->name, &info))
+ goto out;
+ cdirel->name[grub_le_to_cpu16 (cdirel->n)] = c;
+ }
+ r = next (data, device->disk, &desc, &elemaddr, &elemsize, &key_out);
+ }
+ while (r > 0);
+
+ out:
+ grub_free (direl);
+
+ free_iterator (&desc);
grub_free (data);
+ return -r;
+}
+
+static grub_err_t
+grub_btrfs_open (struct grub_file *file, const char *name)
+{
+ struct grub_btrfs_data *data = grub_btrfs_mount (file->device->disk);
+ struct grub_btrfs_key key_in, key_out;
+ grub_err_t err;
+ grub_disk_addr_t elemaddr;
+ grub_size_t elemsize;
+ struct grub_btrfs_inode inode;
+
+ if (!data)
+ return grub_errno;
+
+ err = find_path (data, file->device->disk, name, &key_in, &data->tree);
+ if (err)
+ {
+ grub_free (data);
+ return err;
+ }
+ data->inode = key_in.object_id;
+ key_in.type = GRUB_BTRFS_ITEM_TYPE_INODE_ITEM;
+
+ err = lower_bound (data, file->device->disk, &key_in, &key_out,
+ data->tree,
+ &elemaddr, &elemsize, NULL);
+ if (err)
+ return err;
+ if (data->inode != key_out.object_id
+ || key_out.type != GRUB_BTRFS_ITEM_TYPE_INODE_ITEM)
+ return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a regular file");
+
+ err = grub_btrfs_read_logical (data, file->device->disk, elemaddr,
+ &inode, sizeof (inode));
+ if (err)
+ return err;
+
+ file->data = data;
+ file->size = grub_le_to_cpu64 (inode.size);
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_btrfs_close (grub_file_t file)
+{
+ grub_free (file->data);
+
return GRUB_ERR_NONE;
}
+static grub_ssize_t
+grub_btrfs_read (grub_file_t file, char *buf, grub_size_t len)
+{
+ struct grub_btrfs_data *data = file->data;
+ grub_off_t pos = file->offset;
+ grub_disk_addr_t elemaddr;
+ grub_size_t elemsize;
+ struct grub_btrfs_key key_in, key_out;
+
+ while (len)
+ {
+ grub_size_t csize;
+ struct grub_btrfs_extent_data *extent;
+ grub_err_t err;
+ key_in.object_id = data->inode;
+ key_in.type = GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM;
+ key_in.offset = grub_cpu_to_le64 (pos);
+ err = lower_bound (data, file->device->disk, &key_in, &key_out,
+ data->tree,
+ &elemaddr, &elemsize, NULL);
+ if (err)
+ return -1;
+ if (key_out.object_id != data->inode
+ || key_out.type != GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM)
+ {
+ grub_error (GRUB_ERR_BAD_FS, "extent not found");
+ grub_printf ("no extent\n");
+ return -1;
+ }
+ extent = grub_malloc (elemsize);
+ if (!extent)
+ return grub_errno;
+
+ err = grub_btrfs_read_logical (data, file->device->disk, elemaddr,
+ extent, elemsize);
+ if (err)
+ {
+ grub_free (extent);
+ return err;
+ }
+ if (grub_le_to_cpu64 (extent->size) + grub_le_to_cpu64 (key_out.offset)
+ <= pos)
+ {
+ grub_free (extent);
+ return grub_error (GRUB_ERR_BAD_FS, "extent not found");
+ }
+ grub_dprintf ("btrfs", "extent 0x%" PRIxGRUB_UINT64_T "+0x%"
+ PRIxGRUB_UINT64_T "\n",
+ grub_le_to_cpu64 (key_out.offset),
+ grub_le_to_cpu64 (extent->size));
+ csize = grub_le_to_cpu64 (extent->size)
+ + grub_le_to_cpu64 (key_out.offset) - pos;
+ if (csize > len)
+ csize = len;
+
+ if (extent->encryption)
+ {
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "encryption not supported");
+ return -1;
+ }
+
+ if (extent->compression)
+ {
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "compression not supported");
+ return -1;
+ }
+
+
+ if (extent->encoding)
+ {
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "encoding not supported");
+ return -1;
+ }
+
+ switch (extent->type)
+ {
+ case GRUB_BTRFS_EXTENT_INLINE:
+ grub_memcpy (buf, extent->inl, csize);
+ grub_free (extent);
+ break;
+ case GRUB_BTRFS_EXTENT_REGULAR:
+ if (!extent->laddr)
+ {
+ grub_memset (buf, 0, csize);
+ break;
+ }
+ err = grub_btrfs_read_logical (data, file->device->disk,
+ grub_le_to_cpu64 (extent->laddr),
+ buf, csize);
+ grub_free (extent);
+ if (err)
+ return -1;
+ break;
+ default:
+ grub_free (extent);
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "unsupported extent type 0x%x\n", extent->type);
+ return -1;
+ }
+ buf += csize;
+ pos += csize;
+ len -= csize;
+ }
+ return pos - file->offset;
+}
+
static grub_err_t
grub_btrfs_uuid (grub_device_t device, char **uuid)
{
return grub_errno;
}
+static grub_err_t
+grub_btrfs_label (grub_device_t device, char **label)
+{
+ struct grub_btrfs_data *data;
+
+ *label = NULL;
+
+ data = grub_btrfs_mount (device->disk);
+ if (! data)
+ return grub_errno;
+
+ *label = grub_strndup (data->sblock.label, sizeof (data->sblock.label));
+
+ grub_free (data);
+
+ return grub_errno;
+}
+
static struct grub_fs grub_btrfs_fs =
{
.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,
};
GRUB_MOD_INIT(btrfs)