diff --git a/grub-core/fs/btrfs.c b/grub-core/fs/btrfs.c index a2ee485b4..780327702 100644 --- a/grub-core/fs/btrfs.c +++ b/grub-core/fs/btrfs.c @@ -24,39 +24,491 @@ #include #include #include +#include #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: @@ -65,28 +517,381 @@ grub_btrfs_mount (grub_disk_t disk) } 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) { @@ -113,12 +918,33 @@ 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)