2N/A/* btrfs.c - B-tree file system. */
2N/A/*
2N/A * GRUB -- GRand Unified Bootloader
2N/A * Copyright (C) 2010 Free Software Foundation, Inc.
2N/A *
2N/A * GRUB is free software: you can redistribute it and/or modify
2N/A * it under the terms of the GNU General Public License as published by
2N/A * the Free Software Foundation, either version 3 of the License, or
2N/A * (at your option) any later version.
2N/A *
2N/A * GRUB is distributed in the hope that it will be useful,
2N/A * but WITHOUT ANY WARRANTY; without even the implied warranty of
2N/A * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2N/A * GNU General Public License for more details.
2N/A *
2N/A * You should have received a copy of the GNU General Public License
2N/A * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
2N/A */
2N/A
2N/A#include <grub/err.h>
2N/A#include <grub/file.h>
2N/A#include <grub/mm.h>
2N/A#include <grub/misc.h>
2N/A#include <grub/disk.h>
2N/A#include <grub/dl.h>
2N/A#include <grub/types.h>
2N/A#include <grub/lib/crc.h>
2N/A#include <grub/deflate.h>
2N/A#include <minilzo.h>
2N/A
2N/AGRUB_MOD_LICENSE ("GPLv3+");
2N/A
2N/A#define GRUB_BTRFS_SIGNATURE "_BHRfS_M"
2N/A
2N/A/* From http://www.oberhumer.com/opensource/lzo/lzofaq.php
2N/A * LZO will expand incompressible data by a little amount. I still haven't
2N/A * computed the exact values, but I suggest using these formulas for
2N/A * a worst-case expansion calculation:
2N/A *
2N/A * output_block_size = input_block_size + (input_block_size / 16) + 64 + 3
2N/A * */
2N/A#define GRUB_BTRFS_LZO_BLOCK_SIZE 4096
2N/A#define GRUB_BTRFS_LZO_BLOCK_MAX_CSIZE (GRUB_BTRFS_LZO_BLOCK_SIZE + \
2N/A (GRUB_BTRFS_LZO_BLOCK_SIZE / 16) + 64 + 3)
2N/A
2N/Atypedef grub_uint8_t grub_btrfs_checksum_t[0x20];
2N/Atypedef grub_uint16_t grub_btrfs_uuid_t[8];
2N/A
2N/Astruct grub_btrfs_device
2N/A{
2N/A grub_uint64_t device_id;
2N/A grub_uint8_t dummy[0x62 - 8];
2N/A} __attribute__ ((packed));
2N/A
2N/Astruct grub_btrfs_superblock
2N/A{
2N/A grub_btrfs_checksum_t checksum;
2N/A grub_btrfs_uuid_t uuid;
2N/A grub_uint8_t dummy[0x10];
2N/A grub_uint8_t signature[sizeof (GRUB_BTRFS_SIGNATURE) - 1];
2N/A grub_uint64_t generation;
2N/A grub_uint64_t root_tree;
2N/A grub_uint64_t chunk_tree;
2N/A grub_uint8_t dummy2[0x20];
2N/A grub_uint64_t root_dir_objectid;
2N/A grub_uint8_t dummy3[0x41];
2N/A struct grub_btrfs_device this_device;
2N/A char label[0x100];
2N/A grub_uint8_t dummy4[0x100];
2N/A grub_uint8_t bootstrap_mapping[0x800];
2N/A} __attribute__ ((packed));
2N/A
2N/Astruct btrfs_header
2N/A{
2N/A grub_btrfs_checksum_t checksum;
2N/A grub_btrfs_uuid_t uuid;
2N/A grub_uint8_t dummy[0x30];
2N/A grub_uint32_t nitems;
2N/A grub_uint8_t level;
2N/A} __attribute__ ((packed));
2N/A
2N/Astruct grub_btrfs_device_desc
2N/A{
2N/A grub_device_t dev;
2N/A grub_uint64_t id;
2N/A};
2N/A
2N/Astruct grub_btrfs_data
2N/A{
2N/A struct grub_btrfs_superblock sblock;
2N/A grub_uint64_t tree;
2N/A grub_uint64_t inode;
2N/A
2N/A struct grub_btrfs_device_desc *devices_attached;
2N/A unsigned n_devices_attached;
2N/A unsigned n_devices_allocated;
2N/A
2N/A /* Cached extent data. */
2N/A grub_uint64_t extstart;
2N/A grub_uint64_t extend;
2N/A grub_uint64_t extino;
2N/A grub_uint64_t exttree;
2N/A grub_size_t extsize;
2N/A struct grub_btrfs_extent_data *extent;
2N/A};
2N/A
2N/Astruct grub_btrfs_key
2N/A{
2N/A grub_uint64_t object_id;
2N/A#define GRUB_BTRFS_ITEM_TYPE_INODE_ITEM 0x01
2N/A#define GRUB_BTRFS_ITEM_TYPE_DIR_ITEM 0x54
2N/A#define GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM 0x6c
2N/A#define GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM 0x84
2N/A#define GRUB_BTRFS_ITEM_TYPE_DEVICE 0xd8
2N/A#define GRUB_BTRFS_ITEM_TYPE_CHUNK 0xe4
2N/A grub_uint8_t type;
2N/A grub_uint64_t offset;
2N/A} __attribute__ ((packed));
2N/A
2N/Astruct grub_btrfs_chunk_item
2N/A{
2N/A grub_uint64_t size;
2N/A grub_uint64_t dummy;
2N/A grub_uint64_t stripe_length;
2N/A grub_uint64_t type;
2N/A#define GRUB_BTRFS_CHUNK_TYPE_BITS_DONTCARE 0x07
2N/A#define GRUB_BTRFS_CHUNK_TYPE_SINGLE 0x00
2N/A#define GRUB_BTRFS_CHUNK_TYPE_RAID0 0x08
2N/A#define GRUB_BTRFS_CHUNK_TYPE_RAID1 0x10
2N/A#define GRUB_BTRFS_CHUNK_TYPE_DUPLICATED 0x20
2N/A#define GRUB_BTRFS_CHUNK_TYPE_RAID10 0x40
2N/A grub_uint8_t dummy2[0xc];
2N/A grub_uint16_t nstripes;
2N/A grub_uint16_t nsubstripes;
2N/A} __attribute__ ((packed));
2N/A
2N/Astruct grub_btrfs_chunk_stripe
2N/A{
2N/A grub_uint64_t device_id;
2N/A grub_uint64_t offset;
2N/A grub_btrfs_uuid_t device_uuid;
2N/A} __attribute__ ((packed));
2N/A
2N/Astruct grub_btrfs_leaf_node
2N/A{
2N/A struct grub_btrfs_key key;
2N/A grub_uint32_t offset;
2N/A grub_uint32_t size;
2N/A} __attribute__ ((packed));
2N/A
2N/Astruct grub_btrfs_internal_node
2N/A{
2N/A struct grub_btrfs_key key;
2N/A grub_uint64_t addr;
2N/A grub_uint64_t dummy;
2N/A} __attribute__ ((packed));
2N/A
2N/Astruct grub_btrfs_dir_item
2N/A{
2N/A struct grub_btrfs_key key;
2N/A grub_uint8_t dummy[8];
2N/A grub_uint16_t m;
2N/A grub_uint16_t n;
2N/A#define GRUB_BTRFS_DIR_ITEM_TYPE_REGULAR 1
2N/A#define GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY 2
2N/A#define GRUB_BTRFS_DIR_ITEM_TYPE_SYMLINK 7
2N/A grub_uint8_t type;
2N/A char name[0];
2N/A} __attribute__ ((packed));
2N/A
2N/Astruct grub_btrfs_leaf_descriptor
2N/A{
2N/A unsigned depth;
2N/A unsigned allocated;
2N/A struct
2N/A {
2N/A grub_disk_addr_t addr;
2N/A unsigned iter;
2N/A unsigned maxiter;
2N/A int leaf;
2N/A } *data;
2N/A};
2N/A
2N/Astruct grub_btrfs_root_item
2N/A{
2N/A grub_uint8_t dummy[0xb0];
2N/A grub_uint64_t tree;
2N/A grub_uint64_t inode;
2N/A};
2N/A
2N/Astruct grub_btrfs_time
2N/A{
2N/A grub_int64_t sec;
2N/A grub_uint32_t nanosec;
2N/A} __attribute__ ((aligned (4)));
2N/A
2N/Astruct grub_btrfs_inode
2N/A{
2N/A grub_uint8_t dummy1[0x10];
2N/A grub_uint64_t size;
2N/A grub_uint8_t dummy2[0x70];
2N/A struct grub_btrfs_time mtime;
2N/A} __attribute__ ((packed));
2N/A
2N/Astruct grub_btrfs_extent_data
2N/A{
2N/A grub_uint64_t dummy;
2N/A grub_uint64_t size;
2N/A grub_uint8_t compression;
2N/A grub_uint8_t encryption;
2N/A grub_uint16_t encoding;
2N/A grub_uint8_t type;
2N/A union
2N/A {
2N/A char inl[0];
2N/A struct
2N/A {
2N/A grub_uint64_t laddr;
2N/A grub_uint64_t compressed_size;
2N/A grub_uint64_t offset;
2N/A grub_uint64_t filled;
2N/A };
2N/A };
2N/A} __attribute__ ((packed));
2N/A
2N/A#define GRUB_BTRFS_EXTENT_INLINE 0
2N/A#define GRUB_BTRFS_EXTENT_REGULAR 1
2N/A
2N/A#define GRUB_BTRFS_COMPRESSION_NONE 0
2N/A#define GRUB_BTRFS_COMPRESSION_ZLIB 1
2N/A#define GRUB_BTRFS_COMPRESSION_LZO 2
2N/A
2N/A#define GRUB_BTRFS_OBJECT_ID_CHUNK 0x100
2N/A
2N/Astatic grub_disk_addr_t superblock_sectors[] = { 64 * 2, 64 * 1024 * 2,
2N/A 256 * 1048576 * 2, 1048576ULL * 1048576ULL * 2
2N/A};
2N/A
2N/Astatic grub_err_t
2N/Agrub_btrfs_read_logical (struct grub_btrfs_data *data,
2N/A grub_disk_addr_t addr, void *buf, grub_size_t size);
2N/A
2N/Astatic grub_err_t
2N/Aread_sblock (grub_disk_t disk, struct grub_btrfs_superblock *sb)
2N/A{
2N/A unsigned i;
2N/A grub_err_t err = GRUB_ERR_NONE;
2N/A for (i = 0; i < ARRAY_SIZE (superblock_sectors); i++)
2N/A {
2N/A struct grub_btrfs_superblock sblock;
2N/A err = grub_disk_read (disk, superblock_sectors[i], 0,
2N/A sizeof (sblock), &sblock);
2N/A if (err == GRUB_ERR_OUT_OF_RANGE)
2N/A break;
2N/A
2N/A if (grub_memcmp ((char *) sblock.signature, GRUB_BTRFS_SIGNATURE,
2N/A sizeof (GRUB_BTRFS_SIGNATURE) - 1) != 0)
2N/A break;
2N/A if (i == 0 || grub_le_to_cpu64 (sblock.generation)
2N/A > grub_le_to_cpu64 (sb->generation))
2N/A grub_memcpy (sb, &sblock, sizeof (sblock));
2N/A }
2N/A
2N/A if ((err == GRUB_ERR_OUT_OF_RANGE || !err) && i == 0)
2N/A return grub_error (GRUB_ERR_BAD_FS, "not a Btrfs filesystem");
2N/A
2N/A if (err == GRUB_ERR_OUT_OF_RANGE)
2N/A grub_errno = err = GRUB_ERR_NONE;
2N/A
2N/A return err;
2N/A}
2N/A
2N/Astatic int
2N/Akey_cmp (const struct grub_btrfs_key *a, const struct grub_btrfs_key *b)
2N/A{
2N/A if (grub_cpu_to_le64 (a->object_id) < grub_cpu_to_le64 (b->object_id))
2N/A return -1;
2N/A if (grub_cpu_to_le64 (a->object_id) > grub_cpu_to_le64 (b->object_id))
2N/A return +1;
2N/A
2N/A if (a->type < b->type)
2N/A return -1;
2N/A if (a->type > b->type)
2N/A return +1;
2N/A
2N/A if (grub_cpu_to_le64 (a->offset) < grub_cpu_to_le64 (b->offset))
2N/A return -1;
2N/A if (grub_cpu_to_le64 (a->offset) > grub_cpu_to_le64 (b->offset))
2N/A return +1;
2N/A return 0;
2N/A}
2N/A
2N/Astatic void
2N/Afree_iterator (struct grub_btrfs_leaf_descriptor *desc)
2N/A{
2N/A grub_free (desc->data);
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Asave_ref (struct grub_btrfs_leaf_descriptor *desc,
2N/A grub_disk_addr_t addr, unsigned i, unsigned m, int l)
2N/A{
2N/A desc->depth++;
2N/A if (desc->allocated < desc->depth)
2N/A {
2N/A void *newdata;
2N/A desc->allocated *= 2;
2N/A newdata = grub_realloc (desc->data, sizeof (desc->data[0])
2N/A * desc->allocated);
2N/A if (!newdata)
2N/A return grub_errno;
2N/A desc->data = newdata;
2N/A }
2N/A desc->data[desc->depth - 1].addr = addr;
2N/A desc->data[desc->depth - 1].iter = i;
2N/A desc->data[desc->depth - 1].maxiter = m;
2N/A desc->data[desc->depth - 1].leaf = l;
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A
2N/Astatic int
2N/Anext (struct grub_btrfs_data *data,
2N/A struct grub_btrfs_leaf_descriptor *desc,
2N/A grub_disk_addr_t * outaddr, grub_size_t * outsize,
2N/A struct grub_btrfs_key *key_out)
2N/A{
2N/A grub_err_t err;
2N/A struct grub_btrfs_leaf_node leaf;
2N/A
2N/A for (; desc->depth > 0; desc->depth--)
2N/A {
2N/A desc->data[desc->depth - 1].iter++;
2N/A if (desc->data[desc->depth - 1].iter
2N/A < desc->data[desc->depth - 1].maxiter)
2N/A break;
2N/A }
2N/A if (desc->depth == 0)
2N/A return 0;
2N/A while (!desc->data[desc->depth - 1].leaf)
2N/A {
2N/A struct grub_btrfs_internal_node node;
2N/A struct btrfs_header head;
2N/A
2N/A err = grub_btrfs_read_logical (data, desc->data[desc->depth - 1].iter
2N/A * sizeof (node)
2N/A + sizeof (struct btrfs_header)
2N/A + desc->data[desc->depth - 1].addr,
2N/A &node, sizeof (node));
2N/A if (err)
2N/A return -err;
2N/A
2N/A err = grub_btrfs_read_logical (data, grub_le_to_cpu64 (node.addr),
2N/A &head, sizeof (head));
2N/A if (err)
2N/A return -err;
2N/A
2N/A save_ref (desc, grub_le_to_cpu64 (node.addr), 0,
2N/A grub_le_to_cpu32 (head.nitems), !head.level);
2N/A }
2N/A err = grub_btrfs_read_logical (data, desc->data[desc->depth - 1].iter
2N/A * sizeof (leaf)
2N/A + sizeof (struct btrfs_header)
2N/A + desc->data[desc->depth - 1].addr, &leaf,
2N/A sizeof (leaf));
2N/A if (err)
2N/A return -err;
2N/A *outsize = grub_le_to_cpu32 (leaf.size);
2N/A *outaddr = desc->data[desc->depth - 1].addr + sizeof (struct btrfs_header)
2N/A + grub_le_to_cpu32 (leaf.offset);
2N/A *key_out = leaf.key;
2N/A return 1;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Alower_bound (struct grub_btrfs_data *data,
2N/A const struct grub_btrfs_key *key_in,
2N/A struct grub_btrfs_key *key_out,
2N/A grub_disk_addr_t root,
2N/A grub_disk_addr_t *outaddr, grub_size_t *outsize,
2N/A struct grub_btrfs_leaf_descriptor *desc)
2N/A{
2N/A grub_disk_addr_t addr = root;
2N/A int depth = -1;
2N/A
2N/A if (desc)
2N/A {
2N/A desc->allocated = 16;
2N/A desc->depth = 0;
2N/A desc->data = grub_malloc (sizeof (desc->data[0]) * desc->allocated);
2N/A if (!desc->data)
2N/A return grub_errno;
2N/A }
2N/A
2N/A grub_dprintf ("btrfs",
2N/A "retrieving %" PRIxGRUB_UINT64_T
2N/A " %x %" PRIxGRUB_UINT64_T "\n",
2N/A key_in->object_id, key_in->type, key_in->offset);
2N/A
2N/A while (1)
2N/A {
2N/A grub_err_t err;
2N/A struct btrfs_header head;
2N/A
2N/A reiter:
2N/A depth++;
2N/A /* FIXME: preread few nodes into buffer. */
2N/A err = grub_btrfs_read_logical (data, addr, &head, sizeof (head));
2N/A if (err)
2N/A return err;
2N/A addr += sizeof (head);
2N/A if (head.level)
2N/A {
2N/A unsigned i;
2N/A struct grub_btrfs_internal_node node, node_last;
2N/A int have_last = 0;
2N/A grub_memset (&node_last, 0, sizeof (node_last));
2N/A for (i = 0; i < grub_le_to_cpu32 (head.nitems); i++)
2N/A {
2N/A err = grub_btrfs_read_logical (data, addr + i * sizeof (node),
2N/A &node, sizeof (node));
2N/A if (err)
2N/A return err;
2N/A
2N/A grub_dprintf ("btrfs",
2N/A "internal node (depth %d) %" PRIxGRUB_UINT64_T
2N/A " %x %" PRIxGRUB_UINT64_T "\n", depth,
2N/A node.key.object_id, node.key.type,
2N/A node.key.offset);
2N/A
2N/A if (key_cmp (&node.key, key_in) == 0)
2N/A {
2N/A err = GRUB_ERR_NONE;
2N/A if (desc)
2N/A err = save_ref (desc, addr - sizeof (head), i,
2N/A grub_le_to_cpu32 (head.nitems), 0);
2N/A if (err)
2N/A return err;
2N/A addr = grub_le_to_cpu64 (node.addr);
2N/A goto reiter;
2N/A }
2N/A if (key_cmp (&node.key, key_in) > 0)
2N/A break;
2N/A node_last = node;
2N/A have_last = 1;
2N/A }
2N/A if (have_last)
2N/A {
2N/A err = GRUB_ERR_NONE;
2N/A if (desc)
2N/A err = save_ref (desc, addr - sizeof (head), i - 1,
2N/A grub_le_to_cpu32 (head.nitems), 0);
2N/A if (err)
2N/A return err;
2N/A addr = grub_le_to_cpu64 (node_last.addr);
2N/A goto reiter;
2N/A }
2N/A *outsize = 0;
2N/A *outaddr = 0;
2N/A grub_memset (key_out, 0, sizeof (*key_out));
2N/A if (desc)
2N/A return save_ref (desc, addr - sizeof (head), -1,
2N/A grub_le_to_cpu32 (head.nitems), 0);
2N/A return GRUB_ERR_NONE;
2N/A }
2N/A {
2N/A unsigned i;
2N/A struct grub_btrfs_leaf_node leaf, leaf_last;
2N/A int have_last = 0;
2N/A for (i = 0; i < grub_le_to_cpu32 (head.nitems); i++)
2N/A {
2N/A err = grub_btrfs_read_logical (data, addr + i * sizeof (leaf),
2N/A &leaf, sizeof (leaf));
2N/A if (err)
2N/A return err;
2N/A
2N/A grub_dprintf ("btrfs",
2N/A "leaf (depth %d) %" PRIxGRUB_UINT64_T
2N/A " %x %" PRIxGRUB_UINT64_T "\n", depth,
2N/A leaf.key.object_id, leaf.key.type, leaf.key.offset);
2N/A
2N/A if (key_cmp (&leaf.key, key_in) == 0)
2N/A {
2N/A grub_memcpy (key_out, &leaf.key, sizeof (*key_out));
2N/A *outsize = grub_le_to_cpu32 (leaf.size);
2N/A *outaddr = addr + grub_le_to_cpu32 (leaf.offset);
2N/A if (desc)
2N/A return save_ref (desc, addr - sizeof (head), i,
2N/A grub_le_to_cpu32 (head.nitems), 1);
2N/A return GRUB_ERR_NONE;
2N/A }
2N/A
2N/A if (key_cmp (&leaf.key, key_in) > 0)
2N/A break;
2N/A
2N/A have_last = 1;
2N/A leaf_last = leaf;
2N/A }
2N/A
2N/A if (have_last)
2N/A {
2N/A grub_memcpy (key_out, &leaf_last.key, sizeof (*key_out));
2N/A *outsize = grub_le_to_cpu32 (leaf_last.size);
2N/A *outaddr = addr + grub_le_to_cpu32 (leaf_last.offset);
2N/A if (desc)
2N/A return save_ref (desc, addr - sizeof (head), i - 1,
2N/A grub_le_to_cpu32 (head.nitems), 1);
2N/A return GRUB_ERR_NONE;
2N/A }
2N/A *outsize = 0;
2N/A *outaddr = 0;
2N/A grub_memset (key_out, 0, sizeof (*key_out));
2N/A if (desc)
2N/A return save_ref (desc, addr - sizeof (head), -1,
2N/A grub_le_to_cpu32 (head.nitems), 1);
2N/A return GRUB_ERR_NONE;
2N/A }
2N/A }
2N/A}
2N/A
2N/Astatic grub_device_t
2N/Afind_device (struct grub_btrfs_data *data, grub_uint64_t id, int do_rescan)
2N/A{
2N/A grub_device_t dev_found = NULL;
2N/A auto int hook (const char *name);
2N/A int hook (const char *name)
2N/A {
2N/A grub_device_t dev;
2N/A grub_err_t err;
2N/A struct grub_btrfs_superblock sb;
2N/A dev = grub_device_open (name);
2N/A if (!dev)
2N/A return 0;
2N/A if (!dev->disk)
2N/A {
2N/A grub_device_close (dev);
2N/A return 0;
2N/A }
2N/A err = read_sblock (dev->disk, &sb);
2N/A if (err == GRUB_ERR_BAD_FS)
2N/A {
2N/A grub_device_close (dev);
2N/A grub_errno = GRUB_ERR_NONE;
2N/A return 0;
2N/A }
2N/A if (err)
2N/A {
2N/A grub_device_close (dev);
2N/A grub_print_error ();
2N/A return 0;
2N/A }
2N/A if (grub_memcmp (data->sblock.uuid, sb.uuid, sizeof (sb.uuid)) != 0
2N/A || sb.this_device.device_id != id)
2N/A {
2N/A grub_device_close (dev);
2N/A return 0;
2N/A }
2N/A
2N/A dev_found = dev;
2N/A return 1;
2N/A }
2N/A
2N/A unsigned i;
2N/A
2N/A for (i = 0; i < data->n_devices_attached; i++)
2N/A if (id == data->devices_attached[i].id)
2N/A return data->devices_attached[i].dev;
2N/A if (do_rescan)
2N/A grub_device_iterate (hook);
2N/A if (!dev_found)
2N/A {
2N/A grub_error (GRUB_ERR_BAD_FS, "couldn't find a member device");
2N/A return NULL;
2N/A }
2N/A data->n_devices_attached++;
2N/A if (data->n_devices_attached > data->n_devices_allocated)
2N/A {
2N/A void *tmp;
2N/A data->n_devices_allocated = 2 * data->n_devices_attached + 1;
2N/A data->devices_attached
2N/A = grub_realloc (tmp = data->devices_attached,
2N/A data->n_devices_allocated
2N/A * sizeof (data->devices_attached[0]));
2N/A if (!data->devices_attached)
2N/A {
2N/A grub_device_close (dev_found);
2N/A data->devices_attached = tmp;
2N/A return NULL;
2N/A }
2N/A }
2N/A data->devices_attached[data->n_devices_attached - 1].id = id;
2N/A data->devices_attached[data->n_devices_attached - 1].dev = dev_found;
2N/A return dev_found;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr,
2N/A void *buf, grub_size_t size)
2N/A{
2N/A while (size > 0)
2N/A {
2N/A grub_uint8_t *ptr;
2N/A struct grub_btrfs_key *key;
2N/A struct grub_btrfs_chunk_item *chunk;
2N/A grub_uint64_t csize;
2N/A grub_err_t err = 0;
2N/A struct grub_btrfs_key key_out;
2N/A int challoc = 0;
2N/A grub_device_t dev;
2N/A struct grub_btrfs_key key_in;
2N/A grub_size_t chsize;
2N/A grub_disk_addr_t chaddr;
2N/A
2N/A grub_dprintf ("btrfs", "searching for laddr %" PRIxGRUB_UINT64_T "\n",
2N/A addr);
2N/A for (ptr = data->sblock.bootstrap_mapping;
2N/A ptr < data->sblock.bootstrap_mapping
2N/A + sizeof (data->sblock.bootstrap_mapping)
2N/A - sizeof (struct grub_btrfs_key);)
2N/A {
2N/A key = (struct grub_btrfs_key *) ptr;
2N/A if (key->type != GRUB_BTRFS_ITEM_TYPE_CHUNK)
2N/A break;
2N/A chunk = (struct grub_btrfs_chunk_item *) (key + 1);
2N/A grub_dprintf ("btrfs",
2N/A "%" PRIxGRUB_UINT64_T " %" PRIxGRUB_UINT64_T " \n",
2N/A grub_le_to_cpu64 (key->offset),
2N/A grub_le_to_cpu64 (chunk->size));
2N/A if (grub_le_to_cpu64 (key->offset) <= addr
2N/A && addr < grub_le_to_cpu64 (key->offset)
2N/A + grub_le_to_cpu64 (chunk->size))
2N/A goto chunk_found;
2N/A ptr += sizeof (*key) + sizeof (*chunk)
2N/A + sizeof (struct grub_btrfs_chunk_stripe)
2N/A * grub_le_to_cpu16 (chunk->nstripes);
2N/A }
2N/A
2N/A key_in.object_id = GRUB_BTRFS_OBJECT_ID_CHUNK;
2N/A key_in.type = GRUB_BTRFS_ITEM_TYPE_CHUNK;
2N/A key_in.offset = addr;
2N/A err = lower_bound (data, &key_in, &key_out,
2N/A grub_le_to_cpu64 (data->sblock.chunk_tree),
2N/A &chaddr, &chsize, NULL);
2N/A if (err)
2N/A return err;
2N/A key = &key_out;
2N/A if (key->type != GRUB_BTRFS_ITEM_TYPE_CHUNK
2N/A || !(grub_le_to_cpu64 (key->offset) <= addr))
2N/A return grub_error (GRUB_ERR_BAD_FS,
2N/A "couldn't find the chunk descriptor");
2N/A
2N/A chunk = grub_malloc (chsize);
2N/A if (!chunk)
2N/A return grub_errno;
2N/A
2N/A challoc = 1;
2N/A err = grub_btrfs_read_logical (data, chaddr, chunk, chsize);
2N/A if (err)
2N/A {
2N/A grub_free (chunk);
2N/A return err;
2N/A }
2N/A
2N/A chunk_found:
2N/A {
2N/A grub_uint64_t stripen;
2N/A grub_uint64_t stripe_offset;
2N/A grub_uint64_t off = addr - grub_le_to_cpu64 (key->offset);
2N/A unsigned redundancy = 1;
2N/A unsigned i, j;
2N/A
2N/A if (grub_le_to_cpu64 (chunk->size) <= off)
2N/A {
2N/A grub_dprintf ("btrfs", "no chunk\n");
2N/A return grub_error (GRUB_ERR_BAD_FS,
2N/A "couldn't find the chunk descriptor");
2N/A }
2N/A
2N/A grub_dprintf ("btrfs", "chunk 0x%" PRIxGRUB_UINT64_T
2N/A "+0x%" PRIxGRUB_UINT64_T
2N/A " (%d stripes (%d substripes) of %"
2N/A PRIxGRUB_UINT64_T ")\n",
2N/A grub_le_to_cpu64 (key->offset),
2N/A grub_le_to_cpu64 (chunk->size),
2N/A grub_le_to_cpu16 (chunk->nstripes),
2N/A grub_le_to_cpu16 (chunk->nsubstripes),
2N/A grub_le_to_cpu64 (chunk->stripe_length));
2N/A
2N/A switch (grub_le_to_cpu64 (chunk->type)
2N/A & ~GRUB_BTRFS_CHUNK_TYPE_BITS_DONTCARE)
2N/A {
2N/A case GRUB_BTRFS_CHUNK_TYPE_SINGLE:
2N/A {
2N/A grub_uint64_t stripe_length;
2N/A grub_dprintf ("btrfs", "single\n");
2N/A stripe_length = grub_divmod64 (grub_le_to_cpu64 (chunk->size),
2N/A grub_le_to_cpu16 (chunk->nstripes),
2N/A NULL);
2N/A stripen = grub_divmod64 (off, stripe_length, &stripe_offset);
2N/A csize = (stripen + 1) * stripe_length - off;
2N/A break;
2N/A }
2N/A case GRUB_BTRFS_CHUNK_TYPE_DUPLICATED:
2N/A case GRUB_BTRFS_CHUNK_TYPE_RAID1:
2N/A {
2N/A grub_dprintf ("btrfs", "RAID1\n");
2N/A stripen = 0;
2N/A stripe_offset = off;
2N/A csize = grub_le_to_cpu64 (chunk->size) - off;
2N/A redundancy = 2;
2N/A break;
2N/A }
2N/A case GRUB_BTRFS_CHUNK_TYPE_RAID0:
2N/A {
2N/A grub_uint64_t middle, high;
2N/A grub_uint64_t low;
2N/A grub_dprintf ("btrfs", "RAID0\n");
2N/A middle = grub_divmod64 (off,
2N/A grub_le_to_cpu64 (chunk->stripe_length),
2N/A &low);
2N/A
2N/A high = grub_divmod64 (middle, grub_le_to_cpu16 (chunk->nstripes),
2N/A &stripen);
2N/A stripe_offset =
2N/A low + grub_le_to_cpu64 (chunk->stripe_length) * high;
2N/A csize = grub_le_to_cpu64 (chunk->stripe_length) - low;
2N/A break;
2N/A }
2N/A case GRUB_BTRFS_CHUNK_TYPE_RAID10:
2N/A {
2N/A grub_uint64_t middle, high;
2N/A grub_uint64_t low;
2N/A middle = grub_divmod64 (off,
2N/A grub_le_to_cpu64 (chunk->stripe_length),
2N/A &low);
2N/A
2N/A high = grub_divmod64 (middle,
2N/A grub_le_to_cpu16 (chunk->nstripes)
2N/A / grub_le_to_cpu16 (chunk->nsubstripes),
2N/A &stripen);
2N/A stripen *= grub_le_to_cpu16 (chunk->nsubstripes);
2N/A redundancy = grub_le_to_cpu16 (chunk->nsubstripes);
2N/A stripe_offset = low + grub_le_to_cpu64 (chunk->stripe_length)
2N/A * high;
2N/A csize = grub_le_to_cpu64 (chunk->stripe_length) - low;
2N/A break;
2N/A }
2N/A default:
2N/A grub_dprintf ("btrfs", "unsupported RAID\n");
2N/A return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
2N/A "unsupported RAID flags %" PRIxGRUB_UINT64_T,
2N/A grub_le_to_cpu64 (chunk->type));
2N/A }
2N/A if (csize == 0)
2N/A return grub_error (GRUB_ERR_BUG,
2N/A "couldn't find the chunk descriptor");
2N/A if ((grub_size_t) csize > size)
2N/A csize = size;
2N/A
2N/A for (j = 0; j < 2; j++)
2N/A {
2N/A for (i = 0; i < redundancy; i++)
2N/A {
2N/A struct grub_btrfs_chunk_stripe *stripe;
2N/A grub_disk_addr_t paddr;
2N/A
2N/A stripe = (struct grub_btrfs_chunk_stripe *) (chunk + 1);
2N/A /* Right now the redundancy handling is easy.
2N/A With RAID5-like it will be more difficult. */
2N/A stripe += stripen + i;
2N/A
2N/A paddr = stripe->offset + stripe_offset;
2N/A
2N/A grub_dprintf ("btrfs", "chunk 0x%" PRIxGRUB_UINT64_T
2N/A "+0x%" PRIxGRUB_UINT64_T
2N/A " (%d stripes (%d substripes) of %"
2N/A PRIxGRUB_UINT64_T ") stripe %" PRIxGRUB_UINT64_T
2N/A " maps to 0x%" PRIxGRUB_UINT64_T "\n",
2N/A grub_le_to_cpu64 (key->offset),
2N/A grub_le_to_cpu64 (chunk->size),
2N/A grub_le_to_cpu16 (chunk->nstripes),
2N/A grub_le_to_cpu16 (chunk->nsubstripes),
2N/A grub_le_to_cpu64 (chunk->stripe_length),
2N/A stripen, stripe->offset);
2N/A grub_dprintf ("btrfs", "reading paddr 0x%" PRIxGRUB_UINT64_T
2N/A " for laddr 0x%" PRIxGRUB_UINT64_T "\n", paddr,
2N/A addr);
2N/A
2N/A dev = find_device (data, stripe->device_id, j);
2N/A if (!dev)
2N/A {
2N/A err = grub_errno;
2N/A grub_errno = GRUB_ERR_NONE;
2N/A continue;
2N/A }
2N/A
2N/A err = grub_disk_read (dev->disk, paddr >> GRUB_DISK_SECTOR_BITS,
2N/A paddr & (GRUB_DISK_SECTOR_SIZE - 1),
2N/A csize, buf);
2N/A if (!err)
2N/A break;
2N/A grub_errno = GRUB_ERR_NONE;
2N/A }
2N/A if (i != redundancy)
2N/A break;
2N/A }
2N/A if (err)
2N/A return grub_errno = err;
2N/A }
2N/A size -= csize;
2N/A buf = (grub_uint8_t *) buf + csize;
2N/A addr += csize;
2N/A if (challoc)
2N/A grub_free (chunk);
2N/A }
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A
2N/Astatic struct grub_btrfs_data *
2N/Agrub_btrfs_mount (grub_device_t dev)
2N/A{
2N/A struct grub_btrfs_data *data;
2N/A grub_err_t err;
2N/A
2N/A if (!dev->disk)
2N/A {
2N/A grub_error (GRUB_ERR_BAD_FS, "not BtrFS");
2N/A return NULL;
2N/A }
2N/A
2N/A data = grub_zalloc (sizeof (*data));
2N/A if (!data)
2N/A return NULL;
2N/A
2N/A err = read_sblock (dev->disk, &data->sblock);
2N/A if (err)
2N/A {
2N/A grub_free (data);
2N/A return NULL;
2N/A }
2N/A
2N/A data->n_devices_allocated = 16;
2N/A data->devices_attached = grub_malloc (sizeof (data->devices_attached[0])
2N/A * data->n_devices_allocated);
2N/A if (!data->devices_attached)
2N/A {
2N/A grub_free (data);
2N/A return NULL;
2N/A }
2N/A data->n_devices_attached = 1;
2N/A data->devices_attached[0].dev = dev;
2N/A data->devices_attached[0].id = data->sblock.this_device.device_id;
2N/A
2N/A return data;
2N/A}
2N/A
2N/Astatic void
2N/Agrub_btrfs_unmount (struct grub_btrfs_data *data)
2N/A{
2N/A unsigned i;
2N/A /* The device 0 is closed one layer upper. */
2N/A for (i = 1; i < data->n_devices_attached; i++)
2N/A grub_device_close (data->devices_attached[i].dev);
2N/A grub_free (data->devices_attached);
2N/A grub_free (data->extent);
2N/A grub_free (data);
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_btrfs_read_inode (struct grub_btrfs_data *data,
2N/A struct grub_btrfs_inode *inode, grub_uint64_t num,
2N/A grub_uint64_t tree)
2N/A{
2N/A struct grub_btrfs_key key_in, key_out;
2N/A grub_disk_addr_t elemaddr;
2N/A grub_size_t elemsize;
2N/A grub_err_t err;
2N/A
2N/A key_in.object_id = num;
2N/A key_in.type = GRUB_BTRFS_ITEM_TYPE_INODE_ITEM;
2N/A key_in.offset = 0;
2N/A
2N/A err = lower_bound (data, &key_in, &key_out, tree, &elemaddr, &elemsize, NULL);
2N/A if (err)
2N/A return err;
2N/A if (num != key_out.object_id
2N/A || key_out.type != GRUB_BTRFS_ITEM_TYPE_INODE_ITEM)
2N/A return grub_error (GRUB_ERR_BAD_FS, "inode not found");
2N/A
2N/A return grub_btrfs_read_logical (data, elemaddr, inode, sizeof (*inode));
2N/A}
2N/A
2N/Astatic grub_ssize_t
2N/Agrub_btrfs_lzo_decompress(char *ibuf, grub_size_t isize, grub_off_t off,
2N/A char *obuf, grub_size_t osize)
2N/A{
2N/A grub_uint32_t total_size, cblock_size, ret = 0;
2N/A unsigned char buf[GRUB_BTRFS_LZO_BLOCK_SIZE];
2N/A
2N/A total_size = grub_le_to_cpu32 (grub_get_unaligned32 (ibuf));
2N/A ibuf += sizeof (total_size);
2N/A
2N/A if (isize < total_size)
2N/A return -1;
2N/A
2N/A /* Jump forward to first block with requested data. */
2N/A while (off >= GRUB_BTRFS_LZO_BLOCK_SIZE)
2N/A {
2N/A cblock_size = grub_le_to_cpu32 (grub_get_unaligned32 (ibuf));
2N/A ibuf += sizeof (cblock_size);
2N/A
2N/A if (cblock_size > GRUB_BTRFS_LZO_BLOCK_MAX_CSIZE)
2N/A return -1;
2N/A
2N/A off -= GRUB_BTRFS_LZO_BLOCK_SIZE;
2N/A ibuf += cblock_size;
2N/A }
2N/A
2N/A while (osize > 0)
2N/A {
2N/A lzo_uint usize = GRUB_BTRFS_LZO_BLOCK_SIZE;
2N/A
2N/A cblock_size = grub_le_to_cpu32 (grub_get_unaligned32 (ibuf));
2N/A ibuf += sizeof (cblock_size);
2N/A
2N/A if (cblock_size > GRUB_BTRFS_LZO_BLOCK_MAX_CSIZE)
2N/A return -1;
2N/A
2N/A /* Block partially filled with requested data. */
2N/A if (off > 0 || osize < GRUB_BTRFS_LZO_BLOCK_SIZE)
2N/A {
2N/A grub_size_t to_copy = GRUB_BTRFS_LZO_BLOCK_SIZE - off;
2N/A
2N/A if (to_copy > osize)
2N/A to_copy = osize;
2N/A
2N/A if (lzo1x_decompress_safe ((lzo_bytep)ibuf, cblock_size, buf, &usize,
2N/A NULL) != LZO_E_OK)
2N/A return -1;
2N/A
2N/A if (to_copy > usize)
2N/A to_copy = usize;
2N/A grub_memcpy(obuf, buf + off, to_copy);
2N/A
2N/A osize -= to_copy;
2N/A ret += to_copy;
2N/A obuf += to_copy;
2N/A ibuf += cblock_size;
2N/A off = 0;
2N/A continue;
2N/A }
2N/A
2N/A /* Decompress whole block directly to output buffer. */
2N/A if (lzo1x_decompress_safe ((lzo_bytep)ibuf, cblock_size, (lzo_bytep)obuf,
2N/A &usize, NULL) != LZO_E_OK)
2N/A return -1;
2N/A
2N/A osize -= usize;
2N/A ret += usize;
2N/A obuf += usize;
2N/A ibuf += cblock_size;
2N/A }
2N/A
2N/A return ret;
2N/A}
2N/A
2N/Astatic grub_ssize_t
2N/Agrub_btrfs_extent_read (struct grub_btrfs_data *data,
2N/A grub_uint64_t ino, grub_uint64_t tree,
2N/A grub_off_t pos0, char *buf, grub_size_t len)
2N/A{
2N/A grub_off_t pos = pos0;
2N/A while (len)
2N/A {
2N/A grub_size_t csize;
2N/A grub_err_t err;
2N/A grub_off_t extoff;
2N/A if (!data->extent || data->extstart > pos || data->extino != ino
2N/A || data->exttree != tree || data->extend <= pos)
2N/A {
2N/A struct grub_btrfs_key key_in, key_out;
2N/A grub_disk_addr_t elemaddr;
2N/A grub_size_t elemsize;
2N/A
2N/A grub_free (data->extent);
2N/A key_in.object_id = ino;
2N/A key_in.type = GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM;
2N/A key_in.offset = grub_cpu_to_le64 (pos);
2N/A err = lower_bound (data, &key_in, &key_out, tree,
2N/A &elemaddr, &elemsize, NULL);
2N/A if (err)
2N/A return -1;
2N/A if (key_out.object_id != ino
2N/A || key_out.type != GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM)
2N/A {
2N/A grub_error (GRUB_ERR_BAD_FS, "extent not found");
2N/A return -1;
2N/A }
2N/A if ((grub_ssize_t) elemsize < ((char *) &data->extent->inl
2N/A - (char *) data->extent))
2N/A {
2N/A grub_error (GRUB_ERR_BAD_FS, "extent descriptor is too short");
2N/A return -1;
2N/A }
2N/A data->extstart = grub_le_to_cpu64 (key_out.offset);
2N/A data->extsize = elemsize;
2N/A data->extent = grub_malloc (elemsize);
2N/A data->extino = ino;
2N/A data->exttree = tree;
2N/A if (!data->extent)
2N/A return grub_errno;
2N/A
2N/A err = grub_btrfs_read_logical (data, elemaddr, data->extent,
2N/A elemsize);
2N/A if (err)
2N/A return err;
2N/A
2N/A data->extend = data->extstart + grub_le_to_cpu64 (data->extent->size);
2N/A if (data->extent->type == GRUB_BTRFS_EXTENT_REGULAR
2N/A && (char *) &data->extent + elemsize
2N/A >= (char *) &data->extent->filled + sizeof (data->extent->filled))
2N/A data->extend =
2N/A data->extstart + grub_le_to_cpu64 (data->extent->filled);
2N/A
2N/A grub_dprintf ("btrfs", "regular extent 0x%" PRIxGRUB_UINT64_T "+0x%"
2N/A PRIxGRUB_UINT64_T "\n",
2N/A grub_le_to_cpu64 (key_out.offset),
2N/A grub_le_to_cpu64 (data->extent->size));
2N/A if (data->extend <= pos)
2N/A {
2N/A grub_error (GRUB_ERR_BAD_FS, "extent not found");
2N/A return -1;
2N/A }
2N/A }
2N/A csize = data->extend - pos;
2N/A extoff = pos - data->extstart;
2N/A if (csize > len)
2N/A csize = len;
2N/A
2N/A if (data->extent->encryption)
2N/A {
2N/A grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
2N/A "encryption not supported");
2N/A return -1;
2N/A }
2N/A
2N/A if (data->extent->compression != GRUB_BTRFS_COMPRESSION_NONE
2N/A && data->extent->compression != GRUB_BTRFS_COMPRESSION_ZLIB
2N/A && data->extent->compression != GRUB_BTRFS_COMPRESSION_LZO)
2N/A {
2N/A grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
2N/A "compression type 0x%x not supported",
2N/A data->extent->compression);
2N/A return -1;
2N/A }
2N/A
2N/A if (data->extent->encoding)
2N/A {
2N/A grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "encoding not supported");
2N/A return -1;
2N/A }
2N/A
2N/A switch (data->extent->type)
2N/A {
2N/A case GRUB_BTRFS_EXTENT_INLINE:
2N/A if (data->extent->compression == GRUB_BTRFS_COMPRESSION_ZLIB)
2N/A {
2N/A if (grub_zlib_decompress (data->extent->inl, data->extsize -
2N/A ((grub_uint8_t *) data->extent->inl
2N/A - (grub_uint8_t *) data->extent),
2N/A extoff, buf, csize)
2N/A != (grub_ssize_t) csize)
2N/A return -1;
2N/A }
2N/A else if (data->extent->compression == GRUB_BTRFS_COMPRESSION_LZO)
2N/A {
2N/A if (grub_btrfs_lzo_decompress(data->extent->inl, data->extsize -
2N/A ((grub_uint8_t *) data->extent->inl
2N/A - (grub_uint8_t *) data->extent),
2N/A extoff, buf, csize)
2N/A != (grub_ssize_t) csize)
2N/A return -1;
2N/A }
2N/A else
2N/A grub_memcpy (buf, data->extent->inl + extoff, csize);
2N/A break;
2N/A case GRUB_BTRFS_EXTENT_REGULAR:
2N/A if (!data->extent->laddr)
2N/A {
2N/A grub_memset (buf, 0, csize);
2N/A break;
2N/A }
2N/A
2N/A if (data->extent->compression != GRUB_BTRFS_COMPRESSION_NONE)
2N/A {
2N/A char *tmp;
2N/A grub_uint64_t zsize;
2N/A grub_ssize_t ret;
2N/A
2N/A zsize = grub_le_to_cpu64 (data->extent->compressed_size);
2N/A tmp = grub_malloc (zsize);
2N/A if (!tmp)
2N/A return -1;
2N/A err = grub_btrfs_read_logical (data,
2N/A grub_le_to_cpu64 (data->extent->laddr),
2N/A tmp, zsize);
2N/A if (err)
2N/A {
2N/A grub_free (tmp);
2N/A return -1;
2N/A }
2N/A
2N/A if (data->extent->compression == GRUB_BTRFS_COMPRESSION_ZLIB)
2N/A ret = grub_zlib_decompress (tmp, zsize, extoff
2N/A + grub_le_to_cpu64 (data->extent->offset),
2N/A buf, csize);
2N/A else if (data->extent->compression == GRUB_BTRFS_COMPRESSION_LZO)
2N/A ret = grub_btrfs_lzo_decompress (tmp, zsize, extoff
2N/A + grub_le_to_cpu64 (data->extent->offset),
2N/A buf, csize);
2N/A else
2N/A ret = -1;
2N/A
2N/A grub_free (tmp);
2N/A
2N/A if (ret != (grub_ssize_t) csize)
2N/A return -1;
2N/A
2N/A break;
2N/A }
2N/A err = grub_btrfs_read_logical (data,
2N/A grub_le_to_cpu64 (data->extent->laddr)
2N/A + grub_le_to_cpu64 (data->extent->offset)
2N/A + extoff, buf, csize);
2N/A if (err)
2N/A return -1;
2N/A break;
2N/A default:
2N/A grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
2N/A "unsupported extent type 0x%x", data->extent->type);
2N/A return -1;
2N/A }
2N/A buf += csize;
2N/A pos += csize;
2N/A len -= csize;
2N/A }
2N/A return pos - pos0;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Afind_path (struct grub_btrfs_data *data,
2N/A const char *path, struct grub_btrfs_key *key,
2N/A grub_uint64_t *tree, grub_uint8_t *type)
2N/A{
2N/A const char *slash = path;
2N/A grub_err_t err;
2N/A grub_disk_addr_t elemaddr;
2N/A grub_size_t elemsize;
2N/A grub_size_t allocated = 0;
2N/A struct grub_btrfs_dir_item *direl = NULL;
2N/A struct grub_btrfs_key key_out;
2N/A int skip_default;
2N/A const char *ctoken;
2N/A grub_size_t ctokenlen;
2N/A char *path_alloc = NULL;
2N/A char *origpath = NULL;
2N/A unsigned symlinks_max = 32;
2N/A
2N/A *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY;
2N/A *tree = data->sblock.root_tree;
2N/A key->object_id = data->sblock.root_dir_objectid;
2N/A key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
2N/A key->offset = 0;
2N/A skip_default = 1;
2N/A origpath = grub_strdup (path);
2N/A if (!origpath)
2N/A return grub_errno;
2N/A
2N/A while (1)
2N/A {
2N/A if (!skip_default)
2N/A {
2N/A while (path[0] == '/')
2N/A path++;
2N/A if (!path[0])
2N/A break;
2N/A slash = grub_strchr (path, '/');
2N/A if (!slash)
2N/A slash = path + grub_strlen (path);
2N/A ctoken = path;
2N/A ctokenlen = slash - path;
2N/A }
2N/A else
2N/A {
2N/A ctoken = "default";
2N/A ctokenlen = sizeof ("default") - 1;
2N/A }
2N/A
2N/A if (*type != GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY)
2N/A {
2N/A grub_free (path_alloc);
2N/A grub_free (origpath);
2N/A return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
2N/A }
2N/A
2N/A key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
2N/A key->offset = grub_cpu_to_le64 (~grub_getcrc32c (1, ctoken, ctokenlen));
2N/A
2N/A err = lower_bound (data, key, &key_out, *tree, &elemaddr, &elemsize,
2N/A NULL);
2N/A if (err)
2N/A {
2N/A grub_free (direl);
2N/A grub_free (path_alloc);
2N/A grub_free (origpath);
2N/A return err;
2N/A }
2N/A if (key_cmp (key, &key_out) != 0)
2N/A {
2N/A grub_free (direl);
2N/A grub_free (path_alloc);
2N/A err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "file `%s' not found", origpath);
2N/A grub_free (origpath);
2N/A return err;
2N/A }
2N/A
2N/A struct grub_btrfs_dir_item *cdirel;
2N/A if (elemsize > allocated)
2N/A {
2N/A allocated = 2 * elemsize;
2N/A grub_free (direl);
2N/A direl = grub_malloc (allocated + 1);
2N/A if (!direl)
2N/A {
2N/A grub_free (path_alloc);
2N/A grub_free (origpath);
2N/A return grub_errno;
2N/A }
2N/A }
2N/A
2N/A err = grub_btrfs_read_logical (data, elemaddr, direl, elemsize);
2N/A if (err)
2N/A {
2N/A grub_free (direl);
2N/A grub_free (path_alloc);
2N/A grub_free (origpath);
2N/A return err;
2N/A }
2N/A
2N/A for (cdirel = direl;
2N/A (grub_uint8_t *) cdirel - (grub_uint8_t *) direl
2N/A < (grub_ssize_t) elemsize;
2N/A cdirel = (void *) ((grub_uint8_t *) (direl + 1)
2N/A + grub_le_to_cpu16 (cdirel->n)
2N/A + grub_le_to_cpu16 (cdirel->m)))
2N/A {
2N/A if (ctokenlen == grub_le_to_cpu16 (cdirel->n)
2N/A && grub_memcmp (cdirel->name, ctoken, ctokenlen) == 0)
2N/A break;
2N/A }
2N/A if ((grub_uint8_t *) cdirel - (grub_uint8_t *) direl
2N/A >= (grub_ssize_t) elemsize)
2N/A {
2N/A grub_free (direl);
2N/A grub_free (path_alloc);
2N/A err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "file `%s' not found", origpath);
2N/A grub_free (origpath);
2N/A return err;
2N/A }
2N/A
2N/A if (!skip_default)
2N/A path = slash;
2N/A skip_default = 0;
2N/A if (cdirel->type == GRUB_BTRFS_DIR_ITEM_TYPE_SYMLINK)
2N/A {
2N/A struct grub_btrfs_inode inode;
2N/A char *tmp;
2N/A if (--symlinks_max == 0)
2N/A {
2N/A grub_free (direl);
2N/A grub_free (path_alloc);
2N/A grub_free (origpath);
2N/A return grub_error (GRUB_ERR_SYMLINK_LOOP,
2N/A "too deep nesting of symlinks");
2N/A }
2N/A
2N/A err = grub_btrfs_read_inode (data, &inode,
2N/A cdirel->key.object_id, *tree);
2N/A if (err)
2N/A {
2N/A grub_free (direl);
2N/A grub_free (path_alloc);
2N/A grub_free (origpath);
2N/A return err;
2N/A }
2N/A tmp = grub_malloc (grub_le_to_cpu64 (inode.size)
2N/A + grub_strlen (path) + 1);
2N/A if (!tmp)
2N/A {
2N/A grub_free (direl);
2N/A grub_free (path_alloc);
2N/A grub_free (origpath);
2N/A return grub_errno;
2N/A }
2N/A
2N/A if (grub_btrfs_extent_read (data, cdirel->key.object_id,
2N/A *tree, 0, tmp,
2N/A grub_le_to_cpu64 (inode.size))
2N/A != (grub_ssize_t) grub_le_to_cpu64 (inode.size))
2N/A {
2N/A grub_free (direl);
2N/A grub_free (path_alloc);
2N/A grub_free (origpath);
2N/A grub_free (tmp);
2N/A return grub_errno;
2N/A }
2N/A grub_memcpy (tmp + grub_le_to_cpu64 (inode.size), path,
2N/A grub_strlen (path) + 1);
2N/A grub_free (path_alloc);
2N/A path = path_alloc = tmp;
2N/A if (path[0] == '/')
2N/A {
2N/A *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY;
2N/A *tree = data->sblock.root_tree;
2N/A key->object_id = data->sblock.root_dir_objectid;
2N/A key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
2N/A key->offset = 0;
2N/A skip_default = 1;
2N/A }
2N/A continue;
2N/A }
2N/A *type = cdirel->type;
2N/A
2N/A switch (cdirel->key.type)
2N/A {
2N/A case GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM:
2N/A {
2N/A struct grub_btrfs_root_item ri;
2N/A err = lower_bound (data, &cdirel->key, &key_out,
2N/A data->sblock.root_tree,
2N/A &elemaddr, &elemsize, NULL);
2N/A if (err)
2N/A {
2N/A grub_free (direl);
2N/A grub_free (path_alloc);
2N/A grub_free (origpath);
2N/A return err;
2N/A }
2N/A if (cdirel->key.object_id != key_out.object_id
2N/A || cdirel->key.type != key_out.type)
2N/A {
2N/A grub_free (direl);
2N/A grub_free (path_alloc);
2N/A err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "file `%s' not found", origpath);
2N/A grub_free (origpath);
2N/A return err;
2N/A }
2N/A err = grub_btrfs_read_logical (data, elemaddr, &ri, sizeof (ri));
2N/A if (err)
2N/A {
2N/A grub_free (direl);
2N/A grub_free (path_alloc);
2N/A grub_free (origpath);
2N/A return err;
2N/A }
2N/A key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
2N/A key->offset = 0;
2N/A key->object_id = GRUB_BTRFS_OBJECT_ID_CHUNK;
2N/A *tree = grub_le_to_cpu64 (ri.tree);
2N/A break;
2N/A }
2N/A case GRUB_BTRFS_ITEM_TYPE_INODE_ITEM:
2N/A if (*slash && *type == GRUB_BTRFS_DIR_ITEM_TYPE_REGULAR)
2N/A {
2N/A grub_free (direl);
2N/A grub_free (path_alloc);
2N/A err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "file `%s' not found", origpath);
2N/A grub_free (origpath);
2N/A return err;
2N/A }
2N/A *key = cdirel->key;
2N/A if (*type == GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY)
2N/A key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
2N/A break;
2N/A default:
2N/A grub_free (path_alloc);
2N/A grub_free (origpath);
2N/A grub_free (direl);
2N/A return grub_error (GRUB_ERR_BAD_FS, "unrecognised object type 0x%x",
2N/A cdirel->key.type);
2N/A }
2N/A }
2N/A
2N/A grub_free (direl);
2N/A grub_free (origpath);
2N/A grub_free (path_alloc);
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_btrfs_dir (grub_device_t device, const char *path,
2N/A int (*hook) (const char *filename,
2N/A const struct grub_dirhook_info *info))
2N/A{
2N/A struct grub_btrfs_data *data = grub_btrfs_mount (device);
2N/A struct grub_btrfs_key key_in, key_out;
2N/A grub_err_t err;
2N/A grub_disk_addr_t elemaddr;
2N/A grub_size_t elemsize;
2N/A grub_size_t allocated = 0;
2N/A struct grub_btrfs_dir_item *direl = NULL;
2N/A struct grub_btrfs_leaf_descriptor desc;
2N/A int r = 0;
2N/A grub_uint64_t tree;
2N/A grub_uint8_t type;
2N/A
2N/A if (!data)
2N/A return grub_errno;
2N/A
2N/A err = find_path (data, path, &key_in, &tree, &type);
2N/A if (err)
2N/A {
2N/A grub_btrfs_unmount (data);
2N/A return err;
2N/A }
2N/A if (type != GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY)
2N/A {
2N/A grub_btrfs_unmount (data);
2N/A return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory");
2N/A }
2N/A
2N/A err = lower_bound (data, &key_in, &key_out, tree, &elemaddr, &elemsize, &desc);
2N/A if (err)
2N/A {
2N/A grub_btrfs_unmount (data);
2N/A return err;
2N/A }
2N/A if (key_out.type != GRUB_BTRFS_ITEM_TYPE_DIR_ITEM
2N/A || key_out.object_id != key_in.object_id)
2N/A {
2N/A r = next (data, &desc, &elemaddr, &elemsize, &key_out);
2N/A if (r <= 0)
2N/A goto out;
2N/A }
2N/A do
2N/A {
2N/A struct grub_btrfs_dir_item *cdirel;
2N/A if (key_out.type != GRUB_BTRFS_ITEM_TYPE_DIR_ITEM
2N/A || key_out.object_id != key_in.object_id)
2N/A {
2N/A r = 0;
2N/A break;
2N/A }
2N/A if (elemsize > allocated)
2N/A {
2N/A allocated = 2 * elemsize;
2N/A grub_free (direl);
2N/A direl = grub_malloc (allocated + 1);
2N/A if (!direl)
2N/A {
2N/A r = -grub_errno;
2N/A break;
2N/A }
2N/A }
2N/A
2N/A err = grub_btrfs_read_logical (data, elemaddr, direl, elemsize);
2N/A if (err)
2N/A {
2N/A r = -err;
2N/A break;
2N/A }
2N/A
2N/A for (cdirel = direl;
2N/A (grub_uint8_t *) cdirel - (grub_uint8_t *) direl
2N/A < (grub_ssize_t) elemsize;
2N/A cdirel = (void *) ((grub_uint8_t *) (direl + 1)
2N/A + grub_le_to_cpu16 (cdirel->n)
2N/A + grub_le_to_cpu16 (cdirel->m)))
2N/A {
2N/A char c;
2N/A struct grub_btrfs_inode inode;
2N/A struct grub_dirhook_info info;
2N/A err = grub_btrfs_read_inode (data, &inode, cdirel->key.object_id,
2N/A tree);
2N/A grub_memset (&info, 0, sizeof (info));
2N/A if (err)
2N/A grub_errno = GRUB_ERR_NONE;
2N/A else
2N/A {
2N/A info.mtime = inode.mtime.sec;
2N/A info.mtimeset = 1;
2N/A }
2N/A c = cdirel->name[grub_le_to_cpu16 (cdirel->n)];
2N/A cdirel->name[grub_le_to_cpu16 (cdirel->n)] = 0;
2N/A info.dir = (cdirel->type == GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY);
2N/A if (hook (cdirel->name, &info))
2N/A goto out;
2N/A cdirel->name[grub_le_to_cpu16 (cdirel->n)] = c;
2N/A }
2N/A r = next (data, &desc, &elemaddr, &elemsize, &key_out);
2N/A }
2N/A while (r > 0);
2N/A
2N/Aout:
2N/A grub_free (direl);
2N/A
2N/A free_iterator (&desc);
2N/A grub_btrfs_unmount (data);
2N/A
2N/A return -r;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_btrfs_open (struct grub_file *file, const char *name)
2N/A{
2N/A struct grub_btrfs_data *data = grub_btrfs_mount (file->device);
2N/A grub_err_t err;
2N/A struct grub_btrfs_inode inode;
2N/A grub_uint8_t type;
2N/A struct grub_btrfs_key key_in;
2N/A
2N/A if (!data)
2N/A return grub_errno;
2N/A
2N/A err = find_path (data, name, &key_in, &data->tree, &type);
2N/A if (err)
2N/A {
2N/A grub_btrfs_unmount (data);
2N/A return err;
2N/A }
2N/A if (type != GRUB_BTRFS_DIR_ITEM_TYPE_REGULAR)
2N/A {
2N/A grub_btrfs_unmount (data);
2N/A return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a regular file");
2N/A }
2N/A
2N/A data->inode = key_in.object_id;
2N/A err = grub_btrfs_read_inode (data, &inode, data->inode, data->tree);
2N/A if (err)
2N/A {
2N/A grub_btrfs_unmount (data);
2N/A return err;
2N/A }
2N/A
2N/A file->data = data;
2N/A file->size = grub_le_to_cpu64 (inode.size);
2N/A
2N/A return err;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_btrfs_close (grub_file_t file)
2N/A{
2N/A grub_btrfs_unmount (file->data);
2N/A
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A
2N/Astatic grub_ssize_t
2N/Agrub_btrfs_read (grub_file_t file, char *buf, grub_size_t len)
2N/A{
2N/A struct grub_btrfs_data *data = file->data;
2N/A
2N/A return grub_btrfs_extent_read (data, data->inode,
2N/A data->tree, file->offset, buf, len);
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_btrfs_uuid (grub_device_t device, char **uuid)
2N/A{
2N/A struct grub_btrfs_data *data;
2N/A
2N/A *uuid = NULL;
2N/A
2N/A data = grub_btrfs_mount (device);
2N/A if (!data)
2N/A return grub_errno;
2N/A
2N/A *uuid = grub_xasprintf ("%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
2N/A grub_be_to_cpu16 (data->sblock.uuid[0]),
2N/A grub_be_to_cpu16 (data->sblock.uuid[1]),
2N/A grub_be_to_cpu16 (data->sblock.uuid[2]),
2N/A grub_be_to_cpu16 (data->sblock.uuid[3]),
2N/A grub_be_to_cpu16 (data->sblock.uuid[4]),
2N/A grub_be_to_cpu16 (data->sblock.uuid[5]),
2N/A grub_be_to_cpu16 (data->sblock.uuid[6]),
2N/A grub_be_to_cpu16 (data->sblock.uuid[7]));
2N/A
2N/A grub_btrfs_unmount (data);
2N/A
2N/A return grub_errno;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_btrfs_label (grub_device_t device, char **label)
2N/A{
2N/A struct grub_btrfs_data *data;
2N/A
2N/A *label = NULL;
2N/A
2N/A data = grub_btrfs_mount (device);
2N/A if (!data)
2N/A return grub_errno;
2N/A
2N/A *label = grub_strndup (data->sblock.label, sizeof (data->sblock.label));
2N/A
2N/A grub_btrfs_unmount (data);
2N/A
2N/A return grub_errno;
2N/A}
2N/A
2N/A#ifdef GRUB_UTIL
2N/Astatic grub_err_t
2N/Agrub_btrfs_embed (grub_device_t device __attribute__ ((unused)),
2N/A unsigned int *nsectors,
2N/A grub_embed_type_t embed_type,
2N/A grub_disk_addr_t **sectors)
2N/A{
2N/A unsigned i;
2N/A
2N/A if (embed_type != GRUB_EMBED_PCBIOS)
2N/A return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
2N/A "BtrFS currently supports only PC-BIOS embedding");
2N/A
2N/A if (64 * 2 - 1 < *nsectors)
2N/A return grub_error (GRUB_ERR_OUT_OF_RANGE,
2N/A "Your core.img is unusually large. "
2N/A "It won't fit in the embedding area.");
2N/A
2N/A *nsectors = 64 * 2 - 1;
2N/A *sectors = grub_malloc (*nsectors * sizeof (**sectors));
2N/A if (!*sectors)
2N/A return grub_errno;
2N/A for (i = 0; i < *nsectors; i++)
2N/A (*sectors)[i] = i + 1;
2N/A
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A#endif
2N/A
2N/Astatic struct grub_fs grub_btrfs_fs = {
2N/A .name = "btrfs",
2N/A .dir = grub_btrfs_dir,
2N/A .open = grub_btrfs_open,
2N/A .read = grub_btrfs_read,
2N/A .close = grub_btrfs_close,
2N/A .uuid = grub_btrfs_uuid,
2N/A .label = grub_btrfs_label,
2N/A#ifdef GRUB_UTIL
2N/A .embed = grub_btrfs_embed,
2N/A .reserved_first_sector = 1,
2N/A#endif
2N/A};
2N/A
2N/AGRUB_MOD_INIT (btrfs)
2N/A{
2N/A grub_fs_register (&grub_btrfs_fs);
2N/A}
2N/A
2N/AGRUB_MOD_FINI (btrfs)
2N/A{
2N/A grub_fs_unregister (&grub_btrfs_fs);
2N/A}