/* btrfs.c - B-tree file system. */ /* * GRUB -- GRand Unified Bootloader * Copyright (C) 2010 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 * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GRUB is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GRUB. If not, see . */ #include #include #include #include #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 { 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_internal_node { struct grub_btrfs_key key; grub_uint64_t blockn; 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_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 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, 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) { 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, disk, 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, disk, grub_le_to_cpu64 (node.blockn), &head, sizeof (head)); if (err) return -err; save_ref (desc, grub_le_to_cpu64 (node.blockn), 0, grub_le_to_cpu32 (head.nitems), !head.level); } 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 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; 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, disk, 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, disk, 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.blockn); 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.blockn); 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, disk, 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_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; 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 *) 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: grub_free (data); return NULL; } static grub_err_t find_path (struct grub_btrfs_data *data, grub_disk_t disk, const char *path, struct grub_btrfs_key *key, grub_uint64_t *tree, grub_uint8_t *type) { const char *slash = path; 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; int skip_default = 1; const char *ctoken; grub_size_t ctokenlen; *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; 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; } if (*type != GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory"); key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM; key->offset = grub_cpu_to_le64 (~grub_getcrc32c (1, ctoken, ctokenlen)); 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, ctoken, ctokenlen) == 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"); } if (!skip_default) path = slash; skip_default = 0; *type = cdirel->type; if (*type == GRUB_BTRFS_DIR_ITEM_TYPE_SYMLINK) { grub_free (direl); return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "symlinks not supported"); } 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, data->sblock.root_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 && *type == GRUB_BTRFS_DIR_ITEM_TYPE_REGULAR) return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found"); *key = cdirel->key; if (*type == GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY) key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM; 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, int (*hook) (const char *filename, const struct grub_dirhook_info *info)) { struct grub_btrfs_data *data = grub_btrfs_mount (device->disk); 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 = 0; grub_uint64_t tree; grub_uint8_t type; if (!data) return grub_errno; err = find_path (data, device->disk, path, &key_in, &tree, &type); if (err) return err; if (type != GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory"); 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 == GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY); 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; grub_uint8_t type; if (!data) return grub_errno; err = find_path (data, file->device->disk, name, &key_in, &data->tree, &type); if (err) { grub_free (data); return err; } if (type != GRUB_BTRFS_DIR_ITEM_TYPE_REGULAR) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a regular file"); 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"); 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", 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) { struct grub_btrfs_data *data; *uuid = NULL; data = grub_btrfs_mount (device->disk); if (! data) return grub_errno; *uuid = grub_xasprintf ("%04x%04x-%04x-%04x-%04x-%04x%04x%04x", grub_be_to_cpu16 (data->sblock.uuid[0]), grub_be_to_cpu16 (data->sblock.uuid[1]), grub_be_to_cpu16 (data->sblock.uuid[2]), grub_be_to_cpu16 (data->sblock.uuid[3]), grub_be_to_cpu16 (data->sblock.uuid[4]), grub_be_to_cpu16 (data->sblock.uuid[5]), grub_be_to_cpu16 (data->sblock.uuid[6]), grub_be_to_cpu16 (data->sblock.uuid[7])); grub_free (data); 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) { grub_fs_register (&grub_btrfs_fs); } GRUB_MOD_FINI(btrfs) { grub_fs_unregister (&grub_btrfs_fs); }