/* btrfs.c - B-tree file system. */ /* * VAS_EBOOT -- GRand Unified Bootloader * Copyright (C) 2010,2011,2012,2013 Free Software Foundation, Inc. * * VAS_EBOOT 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. * * VAS_EBOOT 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 VAS_EBOOT. If not, see . */ /* * Tell zstd to expose functions that aren't part of the stable API, which * aren't safe to use when linking against a dynamic library. We vendor in a * specific zstd version, so we know what we're getting. We need these unstable * functions to provide our own allocator, which uses VasEBoot_malloc(), to zstd. */ #define ZSTD_STATIC_LINKING_ONLY #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include VAS_EBOOT_MOD_LICENSE ("GPLv3+"); #define VAS_EBOOT_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 VAS_EBOOT_BTRFS_LZO_BLOCK_SIZE 4096 #define VAS_EBOOT_BTRFS_LZO_BLOCK_MAX_CSIZE (VAS_EBOOT_BTRFS_LZO_BLOCK_SIZE + \ (VAS_EBOOT_BTRFS_LZO_BLOCK_SIZE / 16) + 64 + 3) #define ZSTD_BTRFS_MAX_WINDOWLOG 17 #define ZSTD_BTRFS_MAX_INPUT (1 << ZSTD_BTRFS_MAX_WINDOWLOG) typedef VasEBoot_uint8_t VasEBoot_btrfs_checksum_t[0x20]; typedef VasEBoot_uint16_t VasEBoot_btrfs_uuid_t[8]; struct VasEBoot_btrfs_device { VasEBoot_uint64_t device_id; VasEBoot_uint64_t size; VasEBoot_uint8_t dummy[0x62 - 0x10]; } VAS_EBOOT_PACKED; struct VasEBoot_btrfs_superblock { VasEBoot_btrfs_checksum_t checksum; VasEBoot_btrfs_uuid_t uuid; VasEBoot_uint8_t dummy[0x10]; VasEBoot_uint8_t signature[sizeof (VAS_EBOOT_BTRFS_SIGNATURE) - 1]; VasEBoot_uint64_t generation; VasEBoot_uint64_t root_tree; VasEBoot_uint64_t chunk_tree; VasEBoot_uint8_t dummy2[0x20]; VasEBoot_uint64_t root_dir_objectid; VasEBoot_uint8_t dummy3[0x41]; struct VasEBoot_btrfs_device this_device; char label[0x100]; VasEBoot_uint8_t dummy4[0x100]; VasEBoot_uint8_t bootstrap_mapping[0x800]; } VAS_EBOOT_PACKED; struct btrfs_header { VasEBoot_btrfs_checksum_t checksum; VasEBoot_btrfs_uuid_t uuid; VasEBoot_uint64_t bytenr; VasEBoot_uint8_t dummy[0x28]; VasEBoot_uint32_t nitems; VasEBoot_uint8_t level; } VAS_EBOOT_PACKED; struct VasEBoot_btrfs_device_desc { VasEBoot_device_t dev; VasEBoot_uint64_t id; }; struct VasEBoot_btrfs_data { struct VasEBoot_btrfs_superblock sblock; VasEBoot_uint64_t tree; VasEBoot_uint64_t inode; struct VasEBoot_btrfs_device_desc *devices_attached; unsigned n_devices_attached; unsigned n_devices_allocated; /* Cached extent data. */ VasEBoot_uint64_t extstart; VasEBoot_uint64_t extend; VasEBoot_uint64_t extino; VasEBoot_uint64_t exttree; VasEBoot_size_t extsize; struct VasEBoot_btrfs_extent_data *extent; }; struct VasEBoot_btrfs_chunk_item { VasEBoot_uint64_t size; VasEBoot_uint64_t dummy; VasEBoot_uint64_t stripe_length; VasEBoot_uint64_t type; #define VAS_EBOOT_BTRFS_CHUNK_TYPE_BITS_DONTCARE 0x07 #define VAS_EBOOT_BTRFS_CHUNK_TYPE_SINGLE 0x00 #define VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID0 0x08 #define VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID1 0x10 #define VAS_EBOOT_BTRFS_CHUNK_TYPE_DUPLICATED 0x20 #define VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID10 0x40 #define VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID5 0x80 #define VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID6 0x100 #define VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID1C3 0x200 #define VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID1C4 0x400 VasEBoot_uint8_t dummy2[0xc]; VasEBoot_uint16_t nstripes; VasEBoot_uint16_t nsubstripes; } VAS_EBOOT_PACKED; struct VasEBoot_btrfs_chunk_stripe { VasEBoot_uint64_t device_id; VasEBoot_uint64_t offset; VasEBoot_btrfs_uuid_t device_uuid; } VAS_EBOOT_PACKED; struct VasEBoot_btrfs_leaf_node { struct VasEBoot_btrfs_key key; VasEBoot_uint32_t offset; VasEBoot_uint32_t size; } VAS_EBOOT_PACKED; struct VasEBoot_btrfs_internal_node { struct VasEBoot_btrfs_key key; VasEBoot_uint64_t addr; VasEBoot_uint64_t dummy; } VAS_EBOOT_PACKED; struct VasEBoot_btrfs_dir_item { struct VasEBoot_btrfs_key key; VasEBoot_uint8_t dummy[8]; VasEBoot_uint16_t m; VasEBoot_uint16_t n; #define VAS_EBOOT_BTRFS_DIR_ITEM_TYPE_REGULAR 1 #define VAS_EBOOT_BTRFS_DIR_ITEM_TYPE_DIRECTORY 2 #define VAS_EBOOT_BTRFS_DIR_ITEM_TYPE_SYMLINK 7 VasEBoot_uint8_t type; char name[0]; } VAS_EBOOT_PACKED; struct VasEBoot_btrfs_leaf_descriptor { unsigned depth; unsigned allocated; struct { VasEBoot_disk_addr_t addr; unsigned iter; unsigned maxiter; int leaf; } *data; }; struct VasEBoot_btrfs_time { VasEBoot_int64_t sec; VasEBoot_uint32_t nanosec; } VAS_EBOOT_PACKED; struct VasEBoot_btrfs_inode { VasEBoot_uint8_t dummy1[0x10]; VasEBoot_uint64_t size; VasEBoot_uint8_t dummy2[0x70]; struct VasEBoot_btrfs_time mtime; } VAS_EBOOT_PACKED; struct VasEBoot_btrfs_extent_data { VasEBoot_uint64_t dummy; VasEBoot_uint64_t size; VasEBoot_uint8_t compression; VasEBoot_uint8_t encryption; VasEBoot_uint16_t encoding; VasEBoot_uint8_t type; union { char inl[0]; struct { VasEBoot_uint64_t laddr; VasEBoot_uint64_t compressed_size; VasEBoot_uint64_t offset; VasEBoot_uint64_t filled; }; }; } VAS_EBOOT_PACKED; #define VAS_EBOOT_BTRFS_EXTENT_INLINE 0 #define VAS_EBOOT_BTRFS_EXTENT_REGULAR 1 #define VAS_EBOOT_BTRFS_COMPRESSION_NONE 0 #define VAS_EBOOT_BTRFS_COMPRESSION_ZLIB 1 #define VAS_EBOOT_BTRFS_COMPRESSION_LZO 2 #define VAS_EBOOT_BTRFS_COMPRESSION_ZSTD 3 #define VAS_EBOOT_BTRFS_OBJECT_ID_CHUNK 0x100 static VasEBoot_disk_addr_t superblock_sectors[] = { 64 * 2, 64 * 1024 * 2, 256 * 1048576 * 2, 1048576ULL * 1048576ULL * 2 }; static VasEBoot_err_t VasEBoot_btrfs_read_logical (struct VasEBoot_btrfs_data *data, VasEBoot_disk_addr_t addr, void *buf, VasEBoot_size_t size, int recursion_depth); static VasEBoot_err_t read_sblock (VasEBoot_disk_t disk, struct VasEBoot_btrfs_superblock *sb) { struct VasEBoot_btrfs_superblock sblock; unsigned i; VasEBoot_err_t err = VAS_EBOOT_ERR_NONE; for (i = 0; i < ARRAY_SIZE (superblock_sectors); i++) { /* Don't try additional superblocks beyond device size. */ if (i && (VasEBoot_le_to_cpu64 (sblock.this_device.size) >> VAS_EBOOT_DISK_SECTOR_BITS) <= superblock_sectors[i]) break; err = VasEBoot_disk_read (disk, superblock_sectors[i], 0, sizeof (sblock), &sblock); if (err == VAS_EBOOT_ERR_OUT_OF_RANGE) break; if (VasEBoot_memcmp ((char *) sblock.signature, VAS_EBOOT_BTRFS_SIGNATURE, sizeof (VAS_EBOOT_BTRFS_SIGNATURE) - 1) != 0) break; if (i == 0 || VasEBoot_le_to_cpu64 (sblock.generation) > VasEBoot_le_to_cpu64 (sb->generation)) VasEBoot_memcpy (sb, &sblock, sizeof (sblock)); } if ((err == VAS_EBOOT_ERR_OUT_OF_RANGE || !err) && i == 0) return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "not a Btrfs filesystem"); if (err == VAS_EBOOT_ERR_OUT_OF_RANGE) VasEBoot_errno = err = VAS_EBOOT_ERR_NONE; return err; } static int key_cmp (const struct VasEBoot_btrfs_key *a, const struct VasEBoot_btrfs_key *b) { if (VasEBoot_le_to_cpu64 (a->object_id) < VasEBoot_le_to_cpu64 (b->object_id)) return -1; if (VasEBoot_le_to_cpu64 (a->object_id) > VasEBoot_le_to_cpu64 (b->object_id)) return +1; if (a->type < b->type) return -1; if (a->type > b->type) return +1; if (VasEBoot_le_to_cpu64 (a->offset) < VasEBoot_le_to_cpu64 (b->offset)) return -1; if (VasEBoot_le_to_cpu64 (a->offset) > VasEBoot_le_to_cpu64 (b->offset)) return +1; return 0; } static void free_iterator (struct VasEBoot_btrfs_leaf_descriptor *desc) { VasEBoot_free (desc->data); } static VasEBoot_err_t check_btrfs_header (struct VasEBoot_btrfs_data *data, struct btrfs_header *header, VasEBoot_disk_addr_t addr) { if (VasEBoot_le_to_cpu64 (header->bytenr) != addr) { VasEBoot_dprintf ("btrfs", "btrfs_header.bytenr is not equal node addr\n"); return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "header bytenr is not equal node addr"); } if (VasEBoot_memcmp (data->sblock.uuid, header->uuid, sizeof(VasEBoot_btrfs_uuid_t))) { VasEBoot_dprintf ("btrfs", "btrfs_header.uuid doesn't match sblock uuid\n"); return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "header uuid doesn't match sblock uuid"); } return VAS_EBOOT_ERR_NONE; } static VasEBoot_err_t save_ref (struct VasEBoot_btrfs_leaf_descriptor *desc, VasEBoot_disk_addr_t addr, unsigned i, unsigned m, int l) { desc->depth++; if (desc->allocated < desc->depth) { void *newdata; VasEBoot_size_t sz; if (VasEBoot_mul (desc->allocated, 2, &desc->allocated) || VasEBoot_mul (desc->allocated, sizeof (desc->data[0]), &sz)) return VAS_EBOOT_ERR_OUT_OF_RANGE; newdata = VasEBoot_realloc (desc->data, sz); if (!newdata) return VasEBoot_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 VAS_EBOOT_ERR_NONE; } static int next (struct VasEBoot_btrfs_data *data, struct VasEBoot_btrfs_leaf_descriptor *desc, VasEBoot_disk_addr_t * outaddr, VasEBoot_size_t * outsize, struct VasEBoot_btrfs_key *key_out) { VasEBoot_err_t err; struct VasEBoot_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 VasEBoot_btrfs_internal_node node; struct btrfs_header head; err = VasEBoot_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), 0); if (err) return -err; err = VasEBoot_btrfs_read_logical (data, VasEBoot_le_to_cpu64 (node.addr), &head, sizeof (head), 0); if (err) return -err; check_btrfs_header (data, &head, VasEBoot_le_to_cpu64 (node.addr)); save_ref (desc, VasEBoot_le_to_cpu64 (node.addr), 0, VasEBoot_le_to_cpu32 (head.nitems), !head.level); } err = VasEBoot_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), 0); if (err) return -err; *outsize = VasEBoot_le_to_cpu32 (leaf.size); *outaddr = desc->data[desc->depth - 1].addr + sizeof (struct btrfs_header) + VasEBoot_le_to_cpu32 (leaf.offset); *key_out = leaf.key; return 1; } static VasEBoot_err_t lower_bound (struct VasEBoot_btrfs_data *data, const struct VasEBoot_btrfs_key *key_in, struct VasEBoot_btrfs_key *key_out, VasEBoot_uint64_t root, VasEBoot_disk_addr_t *outaddr, VasEBoot_size_t *outsize, struct VasEBoot_btrfs_leaf_descriptor *desc, int recursion_depth) { VasEBoot_disk_addr_t addr = VasEBoot_le_to_cpu64 (root); int depth = -1; if (desc) { desc->allocated = 16; desc->depth = 0; desc->data = VasEBoot_calloc (desc->allocated, sizeof (desc->data[0])); if (!desc->data) return VasEBoot_errno; } /* > 2 would work as well but be robust and allow a bit more just in case. */ if (recursion_depth > 10) return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "too deep btrfs virtual nesting"); VasEBoot_dprintf ("btrfs", "retrieving %" PRIxVAS_EBOOT_UINT64_T " %x %" PRIxVAS_EBOOT_UINT64_T "\n", key_in->object_id, key_in->type, key_in->offset); while (1) { VasEBoot_err_t err; struct btrfs_header head; reiter: depth++; /* FIXME: preread few nodes into buffer. */ err = VasEBoot_btrfs_read_logical (data, addr, &head, sizeof (head), recursion_depth + 1); if (err) return err; check_btrfs_header (data, &head, addr); addr += sizeof (head); if (head.level) { unsigned i; struct VasEBoot_btrfs_internal_node node, node_last; int have_last = 0; VasEBoot_memset (&node_last, 0, sizeof (node_last)); for (i = 0; i < VasEBoot_le_to_cpu32 (head.nitems); i++) { err = VasEBoot_btrfs_read_logical (data, addr + i * sizeof (node), &node, sizeof (node), recursion_depth + 1); if (err) return err; VasEBoot_dprintf ("btrfs", "internal node (depth %d) %" PRIxVAS_EBOOT_UINT64_T " %x %" PRIxVAS_EBOOT_UINT64_T "\n", depth, node.key.object_id, node.key.type, node.key.offset); if (key_cmp (&node.key, key_in) == 0) { err = VAS_EBOOT_ERR_NONE; if (desc) err = save_ref (desc, addr - sizeof (head), i, VasEBoot_le_to_cpu32 (head.nitems), 0); if (err) return err; addr = VasEBoot_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 = VAS_EBOOT_ERR_NONE; if (desc) err = save_ref (desc, addr - sizeof (head), i - 1, VasEBoot_le_to_cpu32 (head.nitems), 0); if (err) return err; addr = VasEBoot_le_to_cpu64 (node_last.addr); goto reiter; } *outsize = 0; *outaddr = 0; VasEBoot_memset (key_out, 0, sizeof (*key_out)); if (desc) return save_ref (desc, addr - sizeof (head), -1, VasEBoot_le_to_cpu32 (head.nitems), 0); return VAS_EBOOT_ERR_NONE; } { unsigned i; struct VasEBoot_btrfs_leaf_node leaf, leaf_last; int have_last = 0; for (i = 0; i < VasEBoot_le_to_cpu32 (head.nitems); i++) { err = VasEBoot_btrfs_read_logical (data, addr + i * sizeof (leaf), &leaf, sizeof (leaf), recursion_depth + 1); if (err) return err; VasEBoot_dprintf ("btrfs", "leaf (depth %d) %" PRIxVAS_EBOOT_UINT64_T " %x %" PRIxVAS_EBOOT_UINT64_T "\n", depth, leaf.key.object_id, leaf.key.type, leaf.key.offset); if (key_cmp (&leaf.key, key_in) == 0) { VasEBoot_memcpy (key_out, &leaf.key, sizeof (*key_out)); *outsize = VasEBoot_le_to_cpu32 (leaf.size); *outaddr = addr + VasEBoot_le_to_cpu32 (leaf.offset); if (desc) return save_ref (desc, addr - sizeof (head), i, VasEBoot_le_to_cpu32 (head.nitems), 1); return VAS_EBOOT_ERR_NONE; } if (key_cmp (&leaf.key, key_in) > 0) break; have_last = 1; leaf_last = leaf; } if (have_last) { VasEBoot_memcpy (key_out, &leaf_last.key, sizeof (*key_out)); *outsize = VasEBoot_le_to_cpu32 (leaf_last.size); *outaddr = addr + VasEBoot_le_to_cpu32 (leaf_last.offset); if (desc) return save_ref (desc, addr - sizeof (head), i - 1, VasEBoot_le_to_cpu32 (head.nitems), 1); return VAS_EBOOT_ERR_NONE; } *outsize = 0; *outaddr = 0; VasEBoot_memset (key_out, 0, sizeof (*key_out)); if (desc) return save_ref (desc, addr - sizeof (head), -1, VasEBoot_le_to_cpu32 (head.nitems), 1); return VAS_EBOOT_ERR_NONE; } } } /* Context for find_device. */ struct find_device_ctx { struct VasEBoot_btrfs_data *data; VasEBoot_uint64_t id; VasEBoot_device_t dev_found; }; /* Helper for find_device. */ static int find_device_iter (const char *name, void *data) { struct find_device_ctx *ctx = data; VasEBoot_device_t dev; VasEBoot_err_t err; struct VasEBoot_btrfs_superblock sb; dev = VasEBoot_device_open (name); if (!dev) return 0; if (!dev->disk) { VasEBoot_device_close (dev); return 0; } err = read_sblock (dev->disk, &sb); if (err == VAS_EBOOT_ERR_BAD_FS) { VasEBoot_device_close (dev); VasEBoot_errno = VAS_EBOOT_ERR_NONE; return 0; } if (err) { VasEBoot_device_close (dev); VasEBoot_print_error (); return 0; } if (VasEBoot_memcmp (ctx->data->sblock.uuid, sb.uuid, sizeof (sb.uuid)) != 0 || sb.this_device.device_id != ctx->id) { VasEBoot_device_close (dev); return 0; } ctx->dev_found = dev; return 1; } static VasEBoot_device_t find_device (struct VasEBoot_btrfs_data *data, VasEBoot_uint64_t id) { 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; VasEBoot_device_iterate (find_device_iter, &ctx); data->n_devices_attached++; if (data->n_devices_attached > data->n_devices_allocated) { void *tmp; VasEBoot_size_t sz; if (VasEBoot_mul (data->n_devices_attached, 2, &data->n_devices_allocated) || VasEBoot_add (data->n_devices_allocated, 1, &data->n_devices_allocated) || VasEBoot_mul (data->n_devices_allocated, sizeof (data->devices_attached[0]), &sz)) goto fail; data->devices_attached = VasEBoot_realloc (tmp = data->devices_attached, sz); if (!data->devices_attached) { data->devices_attached = tmp; fail: if (ctx.dev_found) VasEBoot_device_close (ctx.dev_found); return NULL; } } data->devices_attached[data->n_devices_attached - 1].id = id; data->devices_attached[data->n_devices_attached - 1].dev = ctx.dev_found; return ctx.dev_found; } static VasEBoot_err_t btrfs_read_from_chunk (struct VasEBoot_btrfs_data *data, struct VasEBoot_btrfs_chunk_item *chunk, VasEBoot_uint64_t stripen, VasEBoot_uint64_t stripe_offset, int redundancy, VasEBoot_uint64_t csize, void *buf) { struct VasEBoot_btrfs_chunk_stripe *stripe; VasEBoot_disk_addr_t paddr; VasEBoot_device_t dev; VasEBoot_err_t err; stripe = (struct VasEBoot_btrfs_chunk_stripe *) (chunk + 1); /* Right now the redundancy handling is easy. With RAID5-like it will be more difficult. */ stripe += stripen + redundancy; paddr = VasEBoot_le_to_cpu64 (stripe->offset) + stripe_offset; VasEBoot_dprintf ("btrfs", "stripe %" PRIxVAS_EBOOT_UINT64_T " maps to 0x%" PRIxVAS_EBOOT_UINT64_T "\n" "reading paddr 0x%" PRIxVAS_EBOOT_UINT64_T "\n", stripen, stripe->offset, paddr); dev = find_device (data, stripe->device_id); if (!dev) { VasEBoot_dprintf ("btrfs", "couldn't find a necessary member device " "of multi-device filesystem\n"); VasEBoot_errno = VAS_EBOOT_ERR_NONE; return VAS_EBOOT_ERR_READ_ERROR; } err = VasEBoot_disk_read (dev->disk, paddr >> VAS_EBOOT_DISK_SECTOR_BITS, paddr & (VAS_EBOOT_DISK_SECTOR_SIZE - 1), csize, buf); return err; } struct raid56_buffer { void *buf; int data_is_valid; }; static void rebuild_raid5 (char *dest, struct raid56_buffer *buffers, VasEBoot_uint64_t nstripes, VasEBoot_uint64_t csize) { VasEBoot_uint64_t i; int first; for(i = 0; buffers[i].data_is_valid && i < nstripes; i++); if (i == nstripes) { VasEBoot_dprintf ("btrfs", "called rebuild_raid5(), but all disks are OK\n"); return; } VasEBoot_dprintf ("btrfs", "rebuilding RAID 5 stripe #%" PRIuVAS_EBOOT_UINT64_T "\n", i); for (i = 0, first = 1; i < nstripes; i++) { if (!buffers[i].data_is_valid) continue; if (first) { VasEBoot_memcpy(dest, buffers[i].buf, csize); first = 0; } else VasEBoot_crypto_xor (dest, dest, buffers[i].buf, csize); } } static VasEBoot_err_t raid6_recover_read_buffer (void *data, int disk_nr, VasEBoot_uint64_t addr __attribute__ ((unused)), void *dest, VasEBoot_size_t size) { struct raid56_buffer *buffers = data; if (!buffers[disk_nr].data_is_valid) return VasEBoot_errno = VAS_EBOOT_ERR_READ_ERROR; VasEBoot_memcpy(dest, buffers[disk_nr].buf, size); return VasEBoot_errno = VAS_EBOOT_ERR_NONE; } static void rebuild_raid6 (struct raid56_buffer *buffers, VasEBoot_uint64_t nstripes, VasEBoot_uint64_t csize, VasEBoot_uint64_t parities_pos, void *dest, VasEBoot_uint64_t stripen) { VasEBoot_raid6_recover_gen (buffers, nstripes, stripen, parities_pos, dest, 0, csize, 0, raid6_recover_read_buffer); } static VasEBoot_err_t raid56_read_retry (struct VasEBoot_btrfs_data *data, struct VasEBoot_btrfs_chunk_item *chunk, VasEBoot_uint64_t stripe_offset, VasEBoot_uint64_t stripen, VasEBoot_uint64_t csize, void *buf, VasEBoot_uint64_t parities_pos) { struct raid56_buffer *buffers; VasEBoot_uint64_t nstripes = VasEBoot_le_to_cpu16 (chunk->nstripes); VasEBoot_uint64_t chunk_type = VasEBoot_le_to_cpu64 (chunk->type); VasEBoot_err_t ret = VAS_EBOOT_ERR_OUT_OF_MEMORY; VasEBoot_uint64_t i, failed_devices; buffers = VasEBoot_calloc (nstripes, sizeof (*buffers)); if (!buffers) goto cleanup; for (i = 0; i < nstripes; i++) { buffers[i].buf = VasEBoot_zalloc (csize); if (!buffers[i].buf) goto cleanup; } for (failed_devices = 0, i = 0; i < nstripes; i++) { struct VasEBoot_btrfs_chunk_stripe *stripe; VasEBoot_disk_addr_t paddr; VasEBoot_device_t dev; VasEBoot_err_t err; /* * The struct VasEBoot_btrfs_chunk_stripe array lives * behind struct VasEBoot_btrfs_chunk_item. */ stripe = (struct VasEBoot_btrfs_chunk_stripe *) (chunk + 1) + i; paddr = VasEBoot_le_to_cpu64 (stripe->offset) + stripe_offset; VasEBoot_dprintf ("btrfs", "reading paddr %" PRIxVAS_EBOOT_UINT64_T " from stripe ID %" PRIxVAS_EBOOT_UINT64_T "\n", paddr, stripe->device_id); dev = find_device (data, stripe->device_id); if (!dev) { VasEBoot_dprintf ("btrfs", "stripe %" PRIuVAS_EBOOT_UINT64_T " FAILED (dev ID %" PRIxVAS_EBOOT_UINT64_T ")\n", i, stripe->device_id); failed_devices++; continue; } err = VasEBoot_disk_read (dev->disk, paddr >> VAS_EBOOT_DISK_SECTOR_BITS, paddr & (VAS_EBOOT_DISK_SECTOR_SIZE - 1), csize, buffers[i].buf); if (err == VAS_EBOOT_ERR_NONE) { buffers[i].data_is_valid = 1; VasEBoot_dprintf ("btrfs", "stripe %" PRIuVAS_EBOOT_UINT64_T " OK (dev ID %" PRIxVAS_EBOOT_UINT64_T ")\n", i, stripe->device_id); } else { VasEBoot_dprintf ("btrfs", "stripe %" PRIuVAS_EBOOT_UINT64_T " READ FAILED (dev ID %" PRIxVAS_EBOOT_UINT64_T ")\n", i, stripe->device_id); failed_devices++; } } if (failed_devices > 1 && (chunk_type & VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID5)) { VasEBoot_dprintf ("btrfs", "not enough disks for RAID 5: total %" PRIuVAS_EBOOT_UINT64_T ", missing %" PRIuVAS_EBOOT_UINT64_T "\n", nstripes, failed_devices); ret = VAS_EBOOT_ERR_READ_ERROR; goto cleanup; } else if (failed_devices > 2 && (chunk_type & VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID6)) { VasEBoot_dprintf ("btrfs", "not enough disks for RAID 6: total %" PRIuVAS_EBOOT_UINT64_T ", missing %" PRIuVAS_EBOOT_UINT64_T "\n", nstripes, failed_devices); ret = VAS_EBOOT_ERR_READ_ERROR; goto cleanup; } else VasEBoot_dprintf ("btrfs", "enough disks for RAID 5: total %" PRIuVAS_EBOOT_UINT64_T ", missing %" PRIuVAS_EBOOT_UINT64_T "\n", nstripes, failed_devices); /* We have enough disks. So, rebuild the data. */ if (chunk_type & VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID5) rebuild_raid5 (buf, buffers, nstripes, csize); else rebuild_raid6 (buffers, nstripes, csize, parities_pos, buf, stripen); ret = VAS_EBOOT_ERR_NONE; cleanup: if (buffers) for (i = 0; i < nstripes; i++) VasEBoot_free (buffers[i].buf); VasEBoot_free (buffers); return ret; } static VasEBoot_err_t VasEBoot_btrfs_read_logical (struct VasEBoot_btrfs_data *data, VasEBoot_disk_addr_t addr, void *buf, VasEBoot_size_t size, int recursion_depth) { while (size > 0) { VasEBoot_uint8_t *ptr; struct VasEBoot_btrfs_key *key; struct VasEBoot_btrfs_chunk_item *chunk; VasEBoot_uint64_t csize; VasEBoot_err_t err = 0; struct VasEBoot_btrfs_key key_out; int challoc = 0; struct VasEBoot_btrfs_key key_in; VasEBoot_size_t chsize; VasEBoot_disk_addr_t chaddr; VasEBoot_dprintf ("btrfs", "searching for laddr %" PRIxVAS_EBOOT_UINT64_T "\n", addr); for (ptr = data->sblock.bootstrap_mapping; ptr < data->sblock.bootstrap_mapping + sizeof (data->sblock.bootstrap_mapping) - sizeof (struct VasEBoot_btrfs_key);) { key = (struct VasEBoot_btrfs_key *) ptr; if (key->type != VAS_EBOOT_BTRFS_ITEM_TYPE_CHUNK) break; chunk = (struct VasEBoot_btrfs_chunk_item *) (key + 1); VasEBoot_dprintf ("btrfs", "%" PRIxVAS_EBOOT_UINT64_T " %" PRIxVAS_EBOOT_UINT64_T " \n", VasEBoot_le_to_cpu64 (key->offset), VasEBoot_le_to_cpu64 (chunk->size)); if (VasEBoot_le_to_cpu64 (key->offset) <= addr && addr < VasEBoot_le_to_cpu64 (key->offset) + VasEBoot_le_to_cpu64 (chunk->size)) goto chunk_found; ptr += sizeof (*key) + sizeof (*chunk) + sizeof (struct VasEBoot_btrfs_chunk_stripe) * VasEBoot_le_to_cpu16 (chunk->nstripes); } key_in.object_id = VasEBoot_cpu_to_le64_compile_time (VAS_EBOOT_BTRFS_OBJECT_ID_CHUNK); key_in.type = VAS_EBOOT_BTRFS_ITEM_TYPE_CHUNK; key_in.offset = VasEBoot_cpu_to_le64 (addr); err = lower_bound (data, &key_in, &key_out, data->sblock.chunk_tree, &chaddr, &chsize, NULL, recursion_depth); if (err) return err; key = &key_out; if (key->type != VAS_EBOOT_BTRFS_ITEM_TYPE_CHUNK || !(VasEBoot_le_to_cpu64 (key->offset) <= addr)) return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "couldn't find the chunk descriptor"); if (!chsize) { VasEBoot_dprintf ("btrfs", "zero-size chunk\n"); return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "got an invalid zero-size chunk"); } /* * The space being allocated for a chunk should at least be able to * contain one chunk item. */ if (chsize < sizeof (struct VasEBoot_btrfs_chunk_item)) { VasEBoot_dprintf ("btrfs", "chunk-size too small\n"); return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "got an invalid chunk size"); } chunk = VasEBoot_malloc (chsize); if (!chunk) return VasEBoot_errno; challoc = 1; err = VasEBoot_btrfs_read_logical (data, chaddr, chunk, chsize, recursion_depth); if (err) { VasEBoot_free (chunk); return err; } chunk_found: { VasEBoot_uint64_t stripen; VasEBoot_uint64_t stripe_offset; VasEBoot_uint64_t off = addr - VasEBoot_le_to_cpu64 (key->offset); VasEBoot_uint64_t chunk_stripe_length; VasEBoot_uint16_t nstripes; unsigned redundancy = 1; unsigned i, j; int is_raid56; VasEBoot_uint64_t parities_pos = 0; is_raid56 = !!(VasEBoot_le_to_cpu64 (chunk->type) & (VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID5 | VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID6)); if (VasEBoot_le_to_cpu64 (chunk->size) <= off) { VasEBoot_dprintf ("btrfs", "no chunk\n"); return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "couldn't find the chunk descriptor"); } nstripes = VasEBoot_le_to_cpu16 (chunk->nstripes) ? : 1; chunk_stripe_length = VasEBoot_le_to_cpu64 (chunk->stripe_length) ? : 512; VasEBoot_dprintf ("btrfs", "chunk 0x%" PRIxVAS_EBOOT_UINT64_T "+0x%" PRIxVAS_EBOOT_UINT64_T " (%d stripes (%d substripes) of %" PRIxVAS_EBOOT_UINT64_T ")\n", VasEBoot_le_to_cpu64 (key->offset), VasEBoot_le_to_cpu64 (chunk->size), nstripes, VasEBoot_le_to_cpu16 (chunk->nsubstripes), chunk_stripe_length); switch (VasEBoot_le_to_cpu64 (chunk->type) & ~VAS_EBOOT_BTRFS_CHUNK_TYPE_BITS_DONTCARE) { case VAS_EBOOT_BTRFS_CHUNK_TYPE_SINGLE: { VasEBoot_uint64_t stripe_length; VasEBoot_dprintf ("btrfs", "single\n"); stripe_length = VasEBoot_divmod64 (VasEBoot_le_to_cpu64 (chunk->size), nstripes, NULL); /* For single, there should be exactly 1 stripe. */ if (VasEBoot_le_to_cpu16 (chunk->nstripes) != 1) { VasEBoot_dprintf ("btrfs", "invalid RAID_SINGLE: nstripes != 1 (%u)\n", VasEBoot_le_to_cpu16 (chunk->nstripes)); return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "invalid RAID_SINGLE: nstripes != 1 (%u)", VasEBoot_le_to_cpu16 (chunk->nstripes)); } if (stripe_length == 0) stripe_length = 512; stripen = VasEBoot_divmod64 (off, stripe_length, &stripe_offset); csize = (stripen + 1) * stripe_length - off; break; } case VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID1C4: redundancy++; /* fall through */ case VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID1C3: redundancy++; /* fall through */ case VAS_EBOOT_BTRFS_CHUNK_TYPE_DUPLICATED: case VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID1: { VasEBoot_dprintf ("btrfs", "RAID1 (copies: %d)\n", ++redundancy); stripen = 0; stripe_offset = off; csize = VasEBoot_le_to_cpu64 (chunk->size) - off; /* * Redundancy, and substripes only apply to RAID10, and there * should be exactly 2 sub-stripes. */ if (VasEBoot_le_to_cpu16 (chunk->nstripes) != redundancy) { VasEBoot_dprintf ("btrfs", "invalid RAID1: nstripes != %u (%u)\n", redundancy, VasEBoot_le_to_cpu16 (chunk->nstripes)); return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "invalid RAID1: nstripes != %u (%u)", redundancy, VasEBoot_le_to_cpu16 (chunk->nstripes)); } break; } case VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID0: { VasEBoot_uint64_t middle, high; VasEBoot_uint64_t low; VasEBoot_dprintf ("btrfs", "RAID0\n"); middle = VasEBoot_divmod64 (off, chunk_stripe_length, &low); high = VasEBoot_divmod64 (middle, nstripes, &stripen); stripe_offset = low + chunk_stripe_length * high; csize = chunk_stripe_length - low; break; } case VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID10: { VasEBoot_uint64_t middle, high; VasEBoot_uint64_t low; VasEBoot_uint16_t nsubstripes; nsubstripes = VasEBoot_le_to_cpu16 (chunk->nsubstripes) ? : 1; middle = VasEBoot_divmod64 (off, chunk_stripe_length, &low); high = VasEBoot_divmod64 (middle, nstripes / nsubstripes ? : 1, &stripen); stripen *= nsubstripes; redundancy = nsubstripes; stripe_offset = low + chunk_stripe_length * high; csize = chunk_stripe_length - low; /* * Substripes only apply to RAID10, and there * should be exactly 2 sub-stripes. */ if (VasEBoot_le_to_cpu16 (chunk->nsubstripes) != 2) { VasEBoot_dprintf ("btrfs", "invalid RAID10: nsubstripes != 2 (%u)", VasEBoot_le_to_cpu16 (chunk->nsubstripes)); return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "invalid RAID10: nsubstripes != 2 (%u)", VasEBoot_le_to_cpu16 (chunk->nsubstripes)); } break; } case VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID5: case VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID6: { VasEBoot_uint64_t nparities, stripe_nr, high, low; redundancy = 1; /* no redundancy for now */ if (VasEBoot_le_to_cpu64 (chunk->type) & VAS_EBOOT_BTRFS_CHUNK_TYPE_RAID5) { VasEBoot_dprintf ("btrfs", "RAID5\n"); nparities = 1; } else { VasEBoot_dprintf ("btrfs", "RAID6\n"); nparities = 2; } /* * RAID 6 layout consists of several stripes spread over * the disks, e.g.: * * Disk_0 Disk_1 Disk_2 Disk_3 * A0 B0 P0 Q0 * Q1 A1 B1 P1 * P2 Q2 A2 B2 * * Note: placement of the parities depend on row number. * * Pay attention that the btrfs terminology may differ from * terminology used in other RAID implementations, e.g. LVM, * dm or md. The main difference is that btrfs calls contiguous * block of data on a given disk, e.g. A0, stripe instead of chunk. * * The variables listed below have following meaning: * - stripe_nr is the stripe number excluding the parities * (A0 = 0, B0 = 1, A1 = 2, B1 = 3, etc.), * - high is the row number (0 for A0...Q0, 1 for Q1...P1, etc.), * - stripen is the disk number in a row (0 for A0, Q1, P2, * 1 for B0, A1, Q2, etc.), * - off is the logical address to read, * - chunk_stripe_length is the size of a stripe (typically 64 KiB), * - nstripes is the number of disks in a row, * - low is the offset of the data inside a stripe, * - stripe_offset is the data offset in an array, * - csize is the "potential" data to read; it will be reduced * to size if the latter is smaller, * - nparities is the number of parities (1 for RAID 5, 2 for * RAID 6); used only in RAID 5/6 code. */ stripe_nr = VasEBoot_divmod64 (off, chunk_stripe_length, &low); /* * stripen is computed without the parities * (0 for A0, A1, A2, 1 for B0, B1, B2, etc.). */ if (nparities >= nstripes) return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "invalid RAID5/6: nparities >= nstripes"); high = VasEBoot_divmod64 (stripe_nr, nstripes - nparities, &stripen); /* * The stripes are spread over the disks. Every each row their * positions are shifted by 1 place. So, the real disks number * change. Hence, we have to take into account current row number * modulo nstripes (0 for A0, 1 for A1, 2 for A2, etc.). */ VasEBoot_divmod64 (high + stripen, nstripes, &stripen); /* * parities_pos is equal to ((high - nparities) % nstripes) * (see the diagram above). However, (high - nparities) can * be negative, e.g. when high == 0, leading to an incorrect * results. (high + nstripes - nparities) is always positive and * modulo nstripes is equal to ((high - nparities) % nstripes). */ VasEBoot_divmod64 (high + nstripes - nparities, nstripes, &parities_pos); stripe_offset = chunk_stripe_length * high + low; csize = chunk_stripe_length - low; break; } default: VasEBoot_dprintf ("btrfs", "unsupported RAID\n"); return VasEBoot_error (VAS_EBOOT_ERR_NOT_IMPLEMENTED_YET, "unsupported RAID flags %" PRIxVAS_EBOOT_UINT64_T, VasEBoot_le_to_cpu64 (chunk->type)); } if (csize == 0) return VasEBoot_error (VAS_EBOOT_ERR_BUG, "couldn't find the chunk descriptor"); if (csize > (VasEBoot_uint64_t) size) csize = size; /* * The space for a chunk stripe is limited to the space provide in the super-block's * bootstrap mapping with an initial btrfs key at the start of each chunk. */ VasEBoot_size_t avail_stripes = sizeof (data->sblock.bootstrap_mapping) / (sizeof (struct VasEBoot_btrfs_key) + sizeof (struct VasEBoot_btrfs_chunk_stripe)); for (j = 0; j < 2; j++) { VasEBoot_size_t est_chunk_alloc = 0; VasEBoot_dprintf ("btrfs", "chunk 0x%" PRIxVAS_EBOOT_UINT64_T "+0x%" PRIxVAS_EBOOT_UINT64_T " (%d stripes (%d substripes) of %" PRIxVAS_EBOOT_UINT64_T ")\n", VasEBoot_le_to_cpu64 (key->offset), VasEBoot_le_to_cpu64 (chunk->size), VasEBoot_le_to_cpu16 (chunk->nstripes), VasEBoot_le_to_cpu16 (chunk->nsubstripes), VasEBoot_le_to_cpu64 (chunk->stripe_length)); VasEBoot_dprintf ("btrfs", "reading laddr 0x%" PRIxVAS_EBOOT_UINT64_T "\n", addr); if (VasEBoot_mul (sizeof (struct VasEBoot_btrfs_chunk_stripe), VasEBoot_le_to_cpu16 (chunk->nstripes), &est_chunk_alloc) || VasEBoot_add (est_chunk_alloc, sizeof (struct VasEBoot_btrfs_chunk_item), &est_chunk_alloc) || est_chunk_alloc > chunk->size) { err = VAS_EBOOT_ERR_BAD_FS; break; } if (VasEBoot_le_to_cpu16 (chunk->nstripes) > avail_stripes) { err = VAS_EBOOT_ERR_BAD_FS; break; } if (is_raid56) { err = btrfs_read_from_chunk (data, chunk, stripen, stripe_offset, 0, /* no mirror */ csize, buf); VasEBoot_errno = VAS_EBOOT_ERR_NONE; if (err) err = raid56_read_retry (data, chunk, stripe_offset, stripen, csize, buf, parities_pos); } else for (i = 0; i < redundancy; i++) { err = btrfs_read_from_chunk (data, chunk, stripen, stripe_offset, i, /* redundancy */ csize, buf); if (!err) break; VasEBoot_errno = VAS_EBOOT_ERR_NONE; } if (!err) break; } if (err) return VasEBoot_errno = err; } size -= csize; buf = (VasEBoot_uint8_t *) buf + csize; addr += csize; if (challoc) VasEBoot_free (chunk); } return VAS_EBOOT_ERR_NONE; } static struct VasEBoot_btrfs_data * VasEBoot_btrfs_mount (VasEBoot_device_t dev) { struct VasEBoot_btrfs_data *data; VasEBoot_err_t err; if (!dev->disk) { VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "not BtrFS"); return NULL; } data = VasEBoot_zalloc (sizeof (*data)); if (!data) return NULL; err = read_sblock (dev->disk, &data->sblock); if (err) { VasEBoot_free (data); return NULL; } data->n_devices_allocated = 16; data->devices_attached = VasEBoot_calloc (data->n_devices_allocated, sizeof (data->devices_attached[0])); if (!data->devices_attached) { VasEBoot_free (data); return NULL; } data->n_devices_attached = 1; data->devices_attached[0].dev = dev; data->devices_attached[0].id = data->sblock.this_device.device_id; return data; } static void VasEBoot_btrfs_unmount (struct VasEBoot_btrfs_data *data) { unsigned i; /* The device 0 is closed one layer upper. */ for (i = 1; i < data->n_devices_attached; i++) if (data->devices_attached[i].dev) VasEBoot_device_close (data->devices_attached[i].dev); VasEBoot_free (data->devices_attached); VasEBoot_free (data->extent); VasEBoot_free (data); } static VasEBoot_err_t VasEBoot_btrfs_read_inode (struct VasEBoot_btrfs_data *data, struct VasEBoot_btrfs_inode *inode, VasEBoot_uint64_t num, VasEBoot_uint64_t tree) { struct VasEBoot_btrfs_key key_in, key_out; VasEBoot_disk_addr_t elemaddr; VasEBoot_size_t elemsize; VasEBoot_err_t err; key_in.object_id = num; key_in.type = VAS_EBOOT_BTRFS_ITEM_TYPE_INODE_ITEM; key_in.offset = 0; 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 != VAS_EBOOT_BTRFS_ITEM_TYPE_INODE_ITEM) return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "inode not found"); return VasEBoot_btrfs_read_logical (data, elemaddr, inode, sizeof (*inode), 0); } static void *VasEBoot_zstd_malloc (void *state __attribute__((unused)), size_t size) { return VasEBoot_malloc (size); } static void VasEBoot_zstd_free (void *state __attribute__((unused)), void *address) { return VasEBoot_free (address); } static ZSTD_customMem VasEBoot_zstd_allocator (void) { ZSTD_customMem allocator; allocator.customAlloc = &VasEBoot_zstd_malloc; allocator.customFree = &VasEBoot_zstd_free; allocator.opaque = NULL; return allocator; } static VasEBoot_ssize_t VasEBoot_btrfs_zstd_decompress (char *ibuf, VasEBoot_size_t isize, VasEBoot_off_t off, char *obuf, VasEBoot_size_t osize) { void *allocated = NULL; char *otmpbuf = obuf; VasEBoot_size_t otmpsize = osize; ZSTD_DCtx *dctx = NULL; VasEBoot_size_t zstd_ret; VasEBoot_ssize_t ret = -1; /* * Zstd will fail if it can't fit the entire output in the destination * buffer, so if osize isn't large enough, allocate a temporary buffer. */ if (otmpsize < ZSTD_BTRFS_MAX_INPUT) { allocated = VasEBoot_malloc (ZSTD_BTRFS_MAX_INPUT); if (!allocated) { VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_MEMORY, "failed allocate a zstd buffer"); goto err; } otmpbuf = (char *) allocated; otmpsize = ZSTD_BTRFS_MAX_INPUT; } /* Create the ZSTD_DCtx. */ dctx = ZSTD_createDCtx_advanced (VasEBoot_zstd_allocator ()); if (!dctx) { /* ZSTD_createDCtx_advanced() only fails if it is out of memory. */ VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_MEMORY, "failed to create a zstd context"); goto err; } /* * Get the real input size, there may be junk at the * end of the frame. */ isize = ZSTD_findFrameCompressedSize (ibuf, isize); if (ZSTD_isError (isize)) { VasEBoot_error (VAS_EBOOT_ERR_BAD_COMPRESSED_DATA, "zstd data corrupted"); goto err; } /* Decompress and check for errors. */ zstd_ret = ZSTD_decompressDCtx (dctx, otmpbuf, otmpsize, ibuf, isize); if (ZSTD_isError (zstd_ret)) { VasEBoot_error (VAS_EBOOT_ERR_BAD_COMPRESSED_DATA, "zstd data corrupted"); goto err; } /* * Move the requested data into the obuf. obuf may be equal * to otmpbuf, which is why VasEBoot_memmove() is required. */ VasEBoot_memmove (obuf, otmpbuf + off, osize); ret = osize; err: VasEBoot_free (allocated); ZSTD_freeDCtx (dctx); return ret; } static VasEBoot_ssize_t VasEBoot_btrfs_lzo_decompress(char *ibuf, VasEBoot_size_t isize, VasEBoot_off_t off, char *obuf, VasEBoot_size_t osize) { VasEBoot_uint32_t total_size, cblock_size; VasEBoot_size_t ret = 0; char *ibuf0 = ibuf; total_size = VasEBoot_le_to_cpu32 (VasEBoot_get_unaligned32 (ibuf)); ibuf += sizeof (total_size); if (isize < total_size) return -1; /* Jump forward to first block with requested data. */ while (off >= VAS_EBOOT_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 = VasEBoot_le_to_cpu32 (VasEBoot_get_unaligned32 (ibuf)); ibuf += sizeof (cblock_size); if (cblock_size > VAS_EBOOT_BTRFS_LZO_BLOCK_MAX_CSIZE) return -1; off -= VAS_EBOOT_BTRFS_LZO_BLOCK_SIZE; ibuf += cblock_size; } while (osize > 0) { lzo_uint usize = VAS_EBOOT_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 = VasEBoot_le_to_cpu32 (VasEBoot_get_unaligned32 (ibuf)); ibuf += sizeof (cblock_size); if (cblock_size > VAS_EBOOT_BTRFS_LZO_BLOCK_MAX_CSIZE) return -1; /* Block partially filled with requested data. */ if (off > 0 || osize < VAS_EBOOT_BTRFS_LZO_BLOCK_SIZE) { VasEBoot_size_t to_copy = VAS_EBOOT_BTRFS_LZO_BLOCK_SIZE - off; VasEBoot_uint8_t *buf; if (to_copy > osize) to_copy = osize; buf = VasEBoot_malloc (VAS_EBOOT_BTRFS_LZO_BLOCK_SIZE); if (!buf) return -1; if (lzo1x_decompress_safe ((lzo_bytep)ibuf, cblock_size, buf, &usize, NULL) != LZO_E_OK) { VasEBoot_free (buf); return -1; } if (to_copy > usize) to_copy = usize; VasEBoot_memcpy(obuf, buf + off, to_copy); osize -= to_copy; ret += to_copy; obuf += to_copy; ibuf += cblock_size; off = 0; VasEBoot_free (buf); continue; } /* 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; osize -= usize; ret += usize; obuf += usize; ibuf += cblock_size; } return ret; } static VasEBoot_ssize_t VasEBoot_btrfs_extent_read (struct VasEBoot_btrfs_data *data, VasEBoot_uint64_t ino, VasEBoot_uint64_t tree, VasEBoot_off_t pos0, char *buf, VasEBoot_size_t len) { VasEBoot_off_t pos = pos0; while (len) { VasEBoot_size_t csize; VasEBoot_err_t err; VasEBoot_off_t extoff; struct VasEBoot_btrfs_leaf_descriptor desc; if (!data->extent || data->extstart > pos || data->extino != ino || data->exttree != tree || data->extend <= pos) { struct VasEBoot_btrfs_key key_in, key_out; VasEBoot_disk_addr_t elemaddr; VasEBoot_size_t elemsize; VasEBoot_free (data->extent); key_in.object_id = ino; key_in.type = VAS_EBOOT_BTRFS_ITEM_TYPE_EXTENT_ITEM; key_in.offset = VasEBoot_cpu_to_le64 (pos); err = lower_bound (data, &key_in, &key_out, tree, &elemaddr, &elemsize, &desc, 0); if (err) { VasEBoot_free (desc.data); return -1; } if (key_out.object_id != ino || key_out.type != VAS_EBOOT_BTRFS_ITEM_TYPE_EXTENT_ITEM) { VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "extent not found"); return -1; } if ((VasEBoot_ssize_t) elemsize < ((char *) &data->extent->inl - (char *) data->extent)) { VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "extent descriptor is too short"); return -1; } data->extstart = VasEBoot_le_to_cpu64 (key_out.offset); data->extsize = elemsize; data->extent = VasEBoot_malloc (elemsize); data->extino = ino; data->exttree = tree; if (!data->extent) return VasEBoot_errno; err = VasEBoot_btrfs_read_logical (data, elemaddr, data->extent, elemsize, 0); if (err) return err; data->extend = data->extstart + VasEBoot_le_to_cpu64 (data->extent->size); if (data->extent->type == VAS_EBOOT_BTRFS_EXTENT_REGULAR && (char *) data->extent + elemsize >= (char *) &data->extent->filled + sizeof (data->extent->filled)) data->extend = data->extstart + VasEBoot_le_to_cpu64 (data->extent->filled); VasEBoot_dprintf ("btrfs", "regular extent 0x%" PRIxVAS_EBOOT_UINT64_T "+0x%" PRIxVAS_EBOOT_UINT64_T "\n", VasEBoot_le_to_cpu64 (key_out.offset), VasEBoot_le_to_cpu64 (data->extent->size)); /* * The way of extent item iteration is pretty bad, it completely * requires all extents are contiguous, which is not ensured. * * Features like NO_HOLE and mixed inline/regular extents can cause * gaps between file extent items. * * The correct way is to follow Linux kernel/U-boot to iterate item * by item, without any assumption on the file offset continuity. * * Here we just manually skip to next item and re-do the verification. * * TODO: Rework the whole extent item iteration code, if not the * whole btrfs implementation. */ if (data->extend <= pos) { err = next (data, &desc, &elemaddr, &elemsize, &key_out); if (err < 0) return -1; /* No next item for the inode, we hit the end. */ if (err == 0 || key_out.object_id != ino || key_out.type != VAS_EBOOT_BTRFS_ITEM_TYPE_EXTENT_ITEM) return pos - pos0; csize = VasEBoot_le_to_cpu64 (key_out.offset) - pos; if (csize > len) csize = len; VasEBoot_memset (buf, 0, csize); buf += csize; pos += csize; len -= csize; continue; } } csize = data->extend - pos; extoff = pos - data->extstart; if (csize > len) csize = len; if (data->extent->encryption) { VasEBoot_error (VAS_EBOOT_ERR_NOT_IMPLEMENTED_YET, "encryption not supported"); return -1; } if (data->extent->compression != VAS_EBOOT_BTRFS_COMPRESSION_NONE && data->extent->compression != VAS_EBOOT_BTRFS_COMPRESSION_ZLIB && data->extent->compression != VAS_EBOOT_BTRFS_COMPRESSION_LZO && data->extent->compression != VAS_EBOOT_BTRFS_COMPRESSION_ZSTD) { VasEBoot_error (VAS_EBOOT_ERR_NOT_IMPLEMENTED_YET, "compression type 0x%x not supported", data->extent->compression); return -1; } if (data->extent->encoding) { VasEBoot_error (VAS_EBOOT_ERR_NOT_IMPLEMENTED_YET, "encoding not supported"); return -1; } switch (data->extent->type) { case VAS_EBOOT_BTRFS_EXTENT_INLINE: if (data->extent->compression == VAS_EBOOT_BTRFS_COMPRESSION_ZLIB) { if (VasEBoot_zlib_decompress (data->extent->inl, data->extsize - ((VasEBoot_uint8_t *) data->extent->inl - (VasEBoot_uint8_t *) data->extent), extoff, buf, csize) != (VasEBoot_ssize_t) csize) { if (!VasEBoot_errno) VasEBoot_error (VAS_EBOOT_ERR_BAD_COMPRESSED_DATA, "premature end of compressed"); return -1; } } else if (data->extent->compression == VAS_EBOOT_BTRFS_COMPRESSION_LZO) { if (VasEBoot_btrfs_lzo_decompress(data->extent->inl, data->extsize - ((VasEBoot_uint8_t *) data->extent->inl - (VasEBoot_uint8_t *) data->extent), extoff, buf, csize) != (VasEBoot_ssize_t) csize) return -1; } else if (data->extent->compression == VAS_EBOOT_BTRFS_COMPRESSION_ZSTD) { if (VasEBoot_btrfs_zstd_decompress (data->extent->inl, data->extsize - ((VasEBoot_uint8_t *) data->extent->inl - (VasEBoot_uint8_t *) data->extent), extoff, buf, csize) != (VasEBoot_ssize_t) csize) return -1; } else VasEBoot_memcpy (buf, data->extent->inl + extoff, csize); break; case VAS_EBOOT_BTRFS_EXTENT_REGULAR: if (!data->extent->laddr) { VasEBoot_memset (buf, 0, csize); break; } if (data->extent->compression != VAS_EBOOT_BTRFS_COMPRESSION_NONE) { char *tmp; VasEBoot_uint64_t zsize; VasEBoot_ssize_t ret; zsize = VasEBoot_le_to_cpu64 (data->extent->compressed_size); tmp = VasEBoot_malloc (zsize); if (!tmp) return -1; err = VasEBoot_btrfs_read_logical (data, VasEBoot_le_to_cpu64 (data->extent->laddr), tmp, zsize, 0); if (err) { VasEBoot_free (tmp); return -1; } if (data->extent->compression == VAS_EBOOT_BTRFS_COMPRESSION_ZLIB) ret = VasEBoot_zlib_decompress (tmp, zsize, extoff + VasEBoot_le_to_cpu64 (data->extent->offset), buf, csize); else if (data->extent->compression == VAS_EBOOT_BTRFS_COMPRESSION_LZO) ret = VasEBoot_btrfs_lzo_decompress (tmp, zsize, extoff + VasEBoot_le_to_cpu64 (data->extent->offset), buf, csize); else if (data->extent->compression == VAS_EBOOT_BTRFS_COMPRESSION_ZSTD) ret = VasEBoot_btrfs_zstd_decompress (tmp, zsize, extoff + VasEBoot_le_to_cpu64 (data->extent->offset), buf, csize); else ret = -1; VasEBoot_free (tmp); if (ret != (VasEBoot_ssize_t) csize) { if (!VasEBoot_errno) VasEBoot_error (VAS_EBOOT_ERR_BAD_COMPRESSED_DATA, "premature end of compressed"); return -1; } break; } err = VasEBoot_btrfs_read_logical (data, VasEBoot_le_to_cpu64 (data->extent->laddr) + VasEBoot_le_to_cpu64 (data->extent->offset) + extoff, buf, csize, 0); if (err) return -1; break; default: VasEBoot_error (VAS_EBOOT_ERR_NOT_IMPLEMENTED_YET, "unsupported extent type 0x%x", data->extent->type); return -1; } buf += csize; pos += csize; len -= csize; } return pos - pos0; } static VasEBoot_err_t get_root (struct VasEBoot_btrfs_data *data, struct VasEBoot_btrfs_key *key, VasEBoot_uint64_t *tree, VasEBoot_uint8_t *type) { VasEBoot_err_t err; VasEBoot_disk_addr_t elemaddr; VasEBoot_size_t elemsize; struct VasEBoot_btrfs_key key_out, key_in; struct VasEBoot_btrfs_root_item ri; key_in.object_id = VasEBoot_cpu_to_le64_compile_time (VAS_EBOOT_BTRFS_ROOT_VOL_OBJECTID); key_in.offset = 0; key_in.type = VAS_EBOOT_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 VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "no root"); err = VasEBoot_btrfs_read_logical (data, elemaddr, &ri, sizeof (ri), 0); if (err) return err; key->type = VAS_EBOOT_BTRFS_ITEM_TYPE_DIR_ITEM; key->offset = 0; key->object_id = VasEBoot_cpu_to_le64_compile_time (VAS_EBOOT_BTRFS_OBJECT_ID_CHUNK); *tree = ri.tree; *type = VAS_EBOOT_BTRFS_DIR_ITEM_TYPE_DIRECTORY; return VAS_EBOOT_ERR_NONE; } static VasEBoot_err_t find_path (struct VasEBoot_btrfs_data *data, const char *path, struct VasEBoot_btrfs_key *key, VasEBoot_uint64_t *tree, VasEBoot_uint8_t *type) { const char *slash = path; VasEBoot_err_t err; VasEBoot_disk_addr_t elemaddr; VasEBoot_size_t elemsize; VasEBoot_size_t allocated = 0; struct VasEBoot_btrfs_dir_item *direl = NULL; struct VasEBoot_btrfs_key key_out; const char *ctoken; VasEBoot_size_t ctokenlen; char *path_alloc = NULL; char *origpath = NULL; unsigned symlinks_max = 32; VasEBoot_size_t sz; err = get_root (data, key, tree, type); if (err) return err; origpath = VasEBoot_strdup (path); if (!origpath) return VasEBoot_errno; while (1) { while (path[0] == '/') path++; if (!path[0]) break; slash = VasEBoot_strchr (path, '/'); if (!slash) slash = path + VasEBoot_strlen (path); ctoken = path; ctokenlen = slash - path; if (*type != VAS_EBOOT_BTRFS_DIR_ITEM_TYPE_DIRECTORY) { VasEBoot_free (path_alloc); VasEBoot_free (origpath); return VasEBoot_error (VAS_EBOOT_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 = VAS_EBOOT_BTRFS_ITEM_TYPE_INODE_REF; key->offset = -1; err = lower_bound (data, key, &key_out, *tree, &elemaddr, &elemsize, NULL, 0); if (err) { VasEBoot_free (direl); VasEBoot_free (path_alloc); VasEBoot_free (origpath); return err; } if (key_out.type != key->type || key->object_id != key_out.object_id) { VasEBoot_free (direl); VasEBoot_free (path_alloc); err = VasEBoot_error (VAS_EBOOT_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath); VasEBoot_free (origpath); return err; } *type = VAS_EBOOT_BTRFS_DIR_ITEM_TYPE_DIRECTORY; key->object_id = key_out.offset; path = slash; continue; } key->type = VAS_EBOOT_BTRFS_ITEM_TYPE_DIR_ITEM; key->offset = VasEBoot_cpu_to_le64 (~VasEBoot_getcrc32c (1, ctoken, ctokenlen)); err = lower_bound (data, key, &key_out, *tree, &elemaddr, &elemsize, NULL, 0); if (err) { VasEBoot_free (direl); VasEBoot_free (path_alloc); VasEBoot_free (origpath); return err; } if (key_cmp (key, &key_out) != 0) { VasEBoot_free (direl); VasEBoot_free (path_alloc); err = VasEBoot_error (VAS_EBOOT_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath); VasEBoot_free (origpath); return err; } struct VasEBoot_btrfs_dir_item *cdirel; if (elemsize > allocated) { if (VasEBoot_mul (2, elemsize, &allocated) || VasEBoot_add (allocated, 1, &sz)) { VasEBoot_free (path_alloc); VasEBoot_free (origpath); return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, N_("directory item size overflow")); } VasEBoot_free (direl); direl = VasEBoot_malloc (sz); if (!direl) { VasEBoot_free (path_alloc); VasEBoot_free (origpath); return VasEBoot_errno; } } err = VasEBoot_btrfs_read_logical (data, elemaddr, direl, elemsize, 0); if (err) { VasEBoot_free (direl); VasEBoot_free (path_alloc); VasEBoot_free (origpath); return err; } for (cdirel = direl; (VasEBoot_uint8_t *) cdirel - (VasEBoot_uint8_t *) direl < (VasEBoot_ssize_t) elemsize; cdirel = (void *) ((VasEBoot_uint8_t *) (direl + 1) + VasEBoot_le_to_cpu16 (cdirel->n) + VasEBoot_le_to_cpu16 (cdirel->m))) { if (ctokenlen == VasEBoot_le_to_cpu16 (cdirel->n) && VasEBoot_memcmp (cdirel->name, ctoken, ctokenlen) == 0) break; } if ((VasEBoot_uint8_t *) cdirel - (VasEBoot_uint8_t *) direl >= (VasEBoot_ssize_t) elemsize) { VasEBoot_free (direl); VasEBoot_free (path_alloc); err = VasEBoot_error (VAS_EBOOT_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath); VasEBoot_free (origpath); return err; } path = slash; if (cdirel->type == VAS_EBOOT_BTRFS_DIR_ITEM_TYPE_SYMLINK) { struct VasEBoot_btrfs_inode inode; char *tmp; if (--symlinks_max == 0) { VasEBoot_free (direl); VasEBoot_free (path_alloc); VasEBoot_free (origpath); return VasEBoot_error (VAS_EBOOT_ERR_SYMLINK_LOOP, N_("too deep nesting of symlinks")); } err = VasEBoot_btrfs_read_inode (data, &inode, cdirel->key.object_id, *tree); if (err) { VasEBoot_free (direl); VasEBoot_free (path_alloc); VasEBoot_free (origpath); return err; } if (VasEBoot_add (VasEBoot_le_to_cpu64 (inode.size), VasEBoot_strlen (path), &sz) || VasEBoot_add (sz, 1, &sz)) { VasEBoot_free (direl); VasEBoot_free (path_alloc); VasEBoot_free (origpath); return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, N_("buffer size overflow")); } tmp = VasEBoot_malloc (sz); if (!tmp) { VasEBoot_free (direl); VasEBoot_free (path_alloc); VasEBoot_free (origpath); return VasEBoot_errno; } if (VasEBoot_btrfs_extent_read (data, cdirel->key.object_id, *tree, 0, tmp, VasEBoot_le_to_cpu64 (inode.size)) != (VasEBoot_ssize_t) VasEBoot_le_to_cpu64 (inode.size)) { VasEBoot_free (direl); VasEBoot_free (path_alloc); VasEBoot_free (origpath); VasEBoot_free (tmp); return VasEBoot_errno; } VasEBoot_memcpy (tmp + VasEBoot_le_to_cpu64 (inode.size), path, VasEBoot_strlen (path) + 1); VasEBoot_free (path_alloc); path = path_alloc = tmp; if (path[0] == '/') { err = get_root (data, key, tree, type); if (err) { VasEBoot_free (direl); VasEBoot_free (path_alloc); VasEBoot_free (origpath); return err; } } continue; } *type = cdirel->type; switch (cdirel->key.type) { case VAS_EBOOT_BTRFS_ITEM_TYPE_ROOT_ITEM: { struct VasEBoot_btrfs_root_item ri; err = lower_bound (data, &cdirel->key, &key_out, data->sblock.root_tree, &elemaddr, &elemsize, NULL, 0); if (err) { VasEBoot_free (direl); VasEBoot_free (path_alloc); VasEBoot_free (origpath); return err; } if (cdirel->key.object_id != key_out.object_id || cdirel->key.type != key_out.type) { VasEBoot_free (direl); VasEBoot_free (path_alloc); err = VasEBoot_error (VAS_EBOOT_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath); VasEBoot_free (origpath); return err; } err = VasEBoot_btrfs_read_logical (data, elemaddr, &ri, sizeof (ri), 0); if (err) { VasEBoot_free (direl); VasEBoot_free (path_alloc); VasEBoot_free (origpath); return err; } key->type = VAS_EBOOT_BTRFS_ITEM_TYPE_DIR_ITEM; key->offset = 0; key->object_id = VasEBoot_cpu_to_le64_compile_time (VAS_EBOOT_BTRFS_OBJECT_ID_CHUNK); *tree = ri.tree; break; } case VAS_EBOOT_BTRFS_ITEM_TYPE_INODE_ITEM: if (*slash && *type == VAS_EBOOT_BTRFS_DIR_ITEM_TYPE_REGULAR) { VasEBoot_free (direl); VasEBoot_free (path_alloc); err = VasEBoot_error (VAS_EBOOT_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath); VasEBoot_free (origpath); return err; } *key = cdirel->key; if (*type == VAS_EBOOT_BTRFS_DIR_ITEM_TYPE_DIRECTORY) key->type = VAS_EBOOT_BTRFS_ITEM_TYPE_DIR_ITEM; break; default: VasEBoot_free (path_alloc); VasEBoot_free (origpath); VasEBoot_free (direl); return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "unrecognised object type 0x%x", cdirel->key.type); } } VasEBoot_free (direl); VasEBoot_free (origpath); VasEBoot_free (path_alloc); return VAS_EBOOT_ERR_NONE; } static VasEBoot_err_t VasEBoot_btrfs_dir (VasEBoot_device_t device, const char *path, VasEBoot_fs_dir_hook_t hook, void *hook_data) { struct VasEBoot_btrfs_data *data = VasEBoot_btrfs_mount (device); struct VasEBoot_btrfs_key key_in, key_out; VasEBoot_err_t err; VasEBoot_disk_addr_t elemaddr; VasEBoot_size_t elemsize; VasEBoot_size_t allocated = 0; struct VasEBoot_btrfs_dir_item *direl = NULL; struct VasEBoot_btrfs_leaf_descriptor desc; int r = 0; VasEBoot_uint64_t tree; VasEBoot_uint8_t type; VasEBoot_size_t est_size = 0; VasEBoot_size_t sz; if (!data) return VasEBoot_errno; err = find_path (data, path, &key_in, &tree, &type); if (err) { VasEBoot_btrfs_unmount (data); return err; } if (type != VAS_EBOOT_BTRFS_DIR_ITEM_TYPE_DIRECTORY) { VasEBoot_btrfs_unmount (data); return VasEBoot_error (VAS_EBOOT_ERR_BAD_FILE_TYPE, N_("not a directory")); } err = lower_bound (data, &key_in, &key_out, tree, &elemaddr, &elemsize, &desc, 0); if (err) { VasEBoot_btrfs_unmount (data); VasEBoot_free (desc.data); return err; } if (key_out.type != VAS_EBOOT_BTRFS_ITEM_TYPE_DIR_ITEM || key_out.object_id != key_in.object_id) { r = next (data, &desc, &elemaddr, &elemsize, &key_out); if (r <= 0) goto out; } do { struct VasEBoot_btrfs_dir_item *cdirel; if (key_out.type != VAS_EBOOT_BTRFS_ITEM_TYPE_DIR_ITEM || key_out.object_id != key_in.object_id) { r = 0; break; } if (elemsize > allocated) { if (VasEBoot_mul (2, elemsize, &allocated) || VasEBoot_add (allocated, 1, &sz)) { VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, N_("directory element size overflow")); r = -VasEBoot_errno; break; } VasEBoot_free (direl); direl = VasEBoot_malloc (sz); if (!direl) { r = -VasEBoot_errno; break; } } err = VasEBoot_btrfs_read_logical (data, elemaddr, direl, elemsize, 0); if (err) { r = -err; break; } if (direl == NULL || VasEBoot_add (VasEBoot_le_to_cpu16 (direl->n), VasEBoot_le_to_cpu16 (direl->m), &est_size) || VasEBoot_add (est_size, sizeof (*direl), &est_size) || VasEBoot_sub (est_size, sizeof (direl->name), &est_size) || est_size > allocated) { VasEBoot_errno = VAS_EBOOT_ERR_OUT_OF_RANGE; r = -VasEBoot_errno; goto out; } for (cdirel = direl; (VasEBoot_uint8_t *) cdirel - (VasEBoot_uint8_t *) direl < (VasEBoot_ssize_t) elemsize; cdirel = (void *) ((VasEBoot_uint8_t *) (direl + 1) + VasEBoot_le_to_cpu16 (cdirel->n) + VasEBoot_le_to_cpu16 (cdirel->m))) { char c; struct VasEBoot_btrfs_inode inode; struct VasEBoot_dirhook_info info; if (cdirel == NULL || VasEBoot_add (VasEBoot_le_to_cpu16 (cdirel->n), VasEBoot_le_to_cpu16 (cdirel->m), &est_size) || VasEBoot_add (est_size, sizeof (*cdirel), &est_size) || VasEBoot_sub (est_size, sizeof (cdirel->name), &est_size) || est_size > allocated) { VasEBoot_errno = VAS_EBOOT_ERR_OUT_OF_RANGE; r = -VasEBoot_errno; goto out; } err = VasEBoot_btrfs_read_inode (data, &inode, cdirel->key.object_id, tree); VasEBoot_memset (&info, 0, sizeof (info)); if (err) VasEBoot_errno = VAS_EBOOT_ERR_NONE; else { info.mtime = VasEBoot_le_to_cpu64 (inode.mtime.sec); info.mtimeset = 1; } c = cdirel->name[VasEBoot_le_to_cpu16 (cdirel->n)]; cdirel->name[VasEBoot_le_to_cpu16 (cdirel->n)] = 0; info.dir = (cdirel->type == VAS_EBOOT_BTRFS_DIR_ITEM_TYPE_DIRECTORY); if (hook (cdirel->name, &info, hook_data)) goto out; cdirel->name[VasEBoot_le_to_cpu16 (cdirel->n)] = c; } r = next (data, &desc, &elemaddr, &elemsize, &key_out); } while (r > 0); out: VasEBoot_free (direl); free_iterator (&desc); VasEBoot_btrfs_unmount (data); return -r; } static VasEBoot_err_t VasEBoot_btrfs_open (struct VasEBoot_file *file, const char *name) { struct VasEBoot_btrfs_data *data = VasEBoot_btrfs_mount (file->device); VasEBoot_err_t err; struct VasEBoot_btrfs_inode inode; VasEBoot_uint8_t type; struct VasEBoot_btrfs_key key_in; if (!data) return VasEBoot_errno; err = find_path (data, name, &key_in, &data->tree, &type); if (err) { VasEBoot_btrfs_unmount (data); return err; } if (type != VAS_EBOOT_BTRFS_DIR_ITEM_TYPE_REGULAR) { VasEBoot_btrfs_unmount (data); return VasEBoot_error (VAS_EBOOT_ERR_BAD_FILE_TYPE, N_("not a regular file")); } data->inode = key_in.object_id; err = VasEBoot_btrfs_read_inode (data, &inode, data->inode, data->tree); if (err) { VasEBoot_btrfs_unmount (data); return err; } file->data = data; file->size = VasEBoot_le_to_cpu64 (inode.size); return err; } static VasEBoot_err_t VasEBoot_btrfs_close (VasEBoot_file_t file) { VasEBoot_btrfs_unmount (file->data); return VAS_EBOOT_ERR_NONE; } static VasEBoot_ssize_t VasEBoot_btrfs_read (VasEBoot_file_t file, char *buf, VasEBoot_size_t len) { struct VasEBoot_btrfs_data *data = file->data; return VasEBoot_btrfs_extent_read (data, data->inode, data->tree, file->offset, buf, len); } static VasEBoot_err_t VasEBoot_btrfs_uuid (VasEBoot_device_t device, char **uuid) { struct VasEBoot_btrfs_data *data; *uuid = NULL; data = VasEBoot_btrfs_mount (device); if (!data) return VasEBoot_errno; *uuid = VasEBoot_xasprintf ("%04x%04x-%04x-%04x-%04x-%04x%04x%04x", VasEBoot_be_to_cpu16 (data->sblock.uuid[0]), VasEBoot_be_to_cpu16 (data->sblock.uuid[1]), VasEBoot_be_to_cpu16 (data->sblock.uuid[2]), VasEBoot_be_to_cpu16 (data->sblock.uuid[3]), VasEBoot_be_to_cpu16 (data->sblock.uuid[4]), VasEBoot_be_to_cpu16 (data->sblock.uuid[5]), VasEBoot_be_to_cpu16 (data->sblock.uuid[6]), VasEBoot_be_to_cpu16 (data->sblock.uuid[7])); VasEBoot_btrfs_unmount (data); return VasEBoot_errno; } static VasEBoot_err_t VasEBoot_btrfs_label (VasEBoot_device_t device, char **label) { struct VasEBoot_btrfs_data *data; *label = NULL; data = VasEBoot_btrfs_mount (device); if (!data) return VasEBoot_errno; *label = VasEBoot_strndup (data->sblock.label, sizeof (data->sblock.label)); VasEBoot_btrfs_unmount (data); return VasEBoot_errno; } #ifdef VAS_EBOOT_UTIL struct embed_region { unsigned int start; unsigned int secs; }; /* * https://btrfs.readthedocs.io/en/latest/btrfs-man5.html#man-btrfs5-bootloader-support * or invoke "man 5 btrfs" and visit the "BOOTLOADER SUPPORT" subsection. * The first 1 MiB on each device is unused with the exception of primary * superblock that is on the offset 64 KiB and spans 4 KiB. * * Note: If this table is modified, also update * util/VasEBoot-editenv.c::fs_envblk_spec, which describes the file-system * specific layout of reserved raw blocks used as environment blocks so that * both stay consistent. */ static const struct { struct embed_region available; struct embed_region used[9]; } btrfs_head = { .available = {0, VAS_EBOOT_DISK_KiB_TO_SECTORS (1024)}, /* The first 1 MiB. */ .used = { {0, 1}, /* boot.S. */ {VAS_EBOOT_DISK_KiB_TO_SECTORS (64) - 1, 1}, /* Overflow guard. */ {VAS_EBOOT_DISK_KiB_TO_SECTORS (64), VAS_EBOOT_DISK_KiB_TO_SECTORS (4)}, /* 4 KiB superblock. */ {VAS_EBOOT_DISK_KiB_TO_SECTORS (68), 1}, /* Overflow guard. */ {(VAS_EBOOT_ENV_BTRFS_OFFSET >> VAS_EBOOT_DISK_SECTOR_BITS) - 1, 1}, /* Overflow guard. */ {(VAS_EBOOT_ENV_BTRFS_OFFSET >> VAS_EBOOT_DISK_SECTOR_BITS), 1}, /* Environment Block. */ {(VAS_EBOOT_ENV_BTRFS_OFFSET >> VAS_EBOOT_DISK_SECTOR_BITS) + 1, 1}, /* Overflow guard. */ {VAS_EBOOT_DISK_KiB_TO_SECTORS (1024) - 1, 1}, /* Overflow guard. */ {0, 0} /* Array terminator. */ } }; static VasEBoot_err_t VasEBoot_btrfs_embed (VasEBoot_device_t device __attribute__ ((unused)), unsigned int *nsectors, unsigned int max_nsectors, VasEBoot_embed_type_t embed_type, VasEBoot_disk_addr_t **sectors) { unsigned int i, j, n = 0; const struct embed_region *u; VasEBoot_disk_addr_t *map; if (embed_type != VAS_EBOOT_EMBED_PCBIOS) return VasEBoot_error (VAS_EBOOT_ERR_NOT_IMPLEMENTED_YET, "BtrFS currently supports only PC-BIOS embedding"); map = VasEBoot_calloc (btrfs_head.available.secs, sizeof (*map)); if (map == NULL) return VasEBoot_errno; /* * Populating the map array so that it can be used to index if a disk * address is available to embed: * - 0: available, * - 1: unavailable. */ for (u = btrfs_head.used; u->secs; ++u) { unsigned int end = u->start + u->secs; if (end > btrfs_head.available.secs) end = btrfs_head.available.secs; for (i = u->start; i < end; ++i) map[i] = 1; } /* Adding up n until it matches total size of available embedding area. */ for (i = 0; i < btrfs_head.available.secs; ++i) if (map[i] == 0) n++; if (n < *nsectors) { VasEBoot_free (map); return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, N_("your core.img is unusually large. " "It won't fit in the embedding area")); } if (n > max_nsectors) n = max_nsectors; /* * Populating the array so that it can used to index disk block address for * an image file's offset to be embedded on disk (the unit is in sectors): * - i: The disk block address relative to btrfs_head.available.start, * - j: The offset in image file. */ for (i = 0, j = 0; i < btrfs_head.available.secs && j < n; ++i) if (map[i] == 0) map[j++] = btrfs_head.available.start + i; *nsectors = n; *sectors = map; return VAS_EBOOT_ERR_NONE; } #endif static struct VasEBoot_fs VasEBoot_btrfs_fs = { .name = "btrfs", .fs_dir = VasEBoot_btrfs_dir, .fs_open = VasEBoot_btrfs_open, .fs_read = VasEBoot_btrfs_read, .fs_close = VasEBoot_btrfs_close, .fs_uuid = VasEBoot_btrfs_uuid, .fs_label = VasEBoot_btrfs_label, #ifdef VAS_EBOOT_UTIL .fs_embed = VasEBoot_btrfs_embed, .reserved_first_sector = 1, .blocklist_install = 0, #endif }; VAS_EBOOT_MOD_INIT (btrfs) { VasEBoot_btrfs_fs.mod = mod; VasEBoot_fs_register (&VasEBoot_btrfs_fs); } VAS_EBOOT_MOD_FINI (btrfs) { VasEBoot_fs_unregister (&VasEBoot_btrfs_fs); }