vaseboot/VasEBoot-core/fs/btrfs.c

1776 lines
48 KiB
C

/* btrfs.c - B-tree file system. */
/*
* VasEBoot -- GRand Unified Bootloader
* Copyright (C) 2010,2011,2012,2013 Free Software Foundation, Inc.
*
* VasEBoot 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.
*
* VasEBoot 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 VasEBoot. If not, see <http://www.gnu.org/licenses/>.
*/
#include <VasEBoot/err.h>
#include <VasEBoot/file.h>
#include <VasEBoot/mm.h>
#include <VasEBoot/misc.h>
#include <VasEBoot/disk.h>
#include <VasEBoot/dl.h>
#include <VasEBoot/types.h>
#include <VasEBoot/lib/crc.h>
#include <VasEBoot/deflate.h>
#include <minilzo.h>
#include <VasEBoot/i18n.h>
#include <VasEBoot/btrfs.h>
VasEBoot_MOD_LICENSE ("GPLv3+");
#define VasEBoot_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 VasEBoot_BTRFS_LZO_BLOCK_SIZE 4096
#define VasEBoot_BTRFS_LZO_BLOCK_MAX_CSIZE (VasEBoot_BTRFS_LZO_BLOCK_SIZE + \
(VasEBoot_BTRFS_LZO_BLOCK_SIZE / 16) + 64 + 3)
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];
} VasEBoot_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 (VasEBoot_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];
} VasEBoot_PACKED;
struct btrfs_header
{
VasEBoot_btrfs_checksum_t checksum;
VasEBoot_btrfs_uuid_t uuid;
VasEBoot_uint8_t dummy[0x30];
VasEBoot_uint32_t nitems;
VasEBoot_uint8_t level;
} VasEBoot_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 VasEBoot_BTRFS_CHUNK_TYPE_BITS_DONTCARE 0x07
#define VasEBoot_BTRFS_CHUNK_TYPE_SINGLE 0x00
#define VasEBoot_BTRFS_CHUNK_TYPE_RAID0 0x08
#define VasEBoot_BTRFS_CHUNK_TYPE_RAID1 0x10
#define VasEBoot_BTRFS_CHUNK_TYPE_DUPLICATED 0x20
#define VasEBoot_BTRFS_CHUNK_TYPE_RAID10 0x40
VasEBoot_uint8_t dummy2[0xc];
VasEBoot_uint16_t nstripes;
VasEBoot_uint16_t nsubstripes;
} VasEBoot_PACKED;
struct VasEBoot_btrfs_chunk_stripe
{
VasEBoot_uint64_t device_id;
VasEBoot_uint64_t offset;
VasEBoot_btrfs_uuid_t device_uuid;
} VasEBoot_PACKED;
struct VasEBoot_btrfs_leaf_node
{
struct VasEBoot_btrfs_key key;
VasEBoot_uint32_t offset;
VasEBoot_uint32_t size;
} VasEBoot_PACKED;
struct VasEBoot_btrfs_internal_node
{
struct VasEBoot_btrfs_key key;
VasEBoot_uint64_t addr;
VasEBoot_uint64_t dummy;
} VasEBoot_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 VasEBoot_BTRFS_DIR_ITEM_TYPE_REGULAR 1
#define VasEBoot_BTRFS_DIR_ITEM_TYPE_DIRECTORY 2
#define VasEBoot_BTRFS_DIR_ITEM_TYPE_SYMLINK 7
VasEBoot_uint8_t type;
char name[0];
} VasEBoot_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;
} VasEBoot_PACKED;
struct VasEBoot_btrfs_inode
{
VasEBoot_uint8_t dummy1[0x10];
VasEBoot_uint64_t size;
VasEBoot_uint8_t dummy2[0x70];
struct VasEBoot_btrfs_time mtime;
} VasEBoot_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;
};
};
} VasEBoot_PACKED;
#define VasEBoot_BTRFS_EXTENT_INLINE 0
#define VasEBoot_BTRFS_EXTENT_REGULAR 1
#define VasEBoot_BTRFS_COMPRESSION_NONE 0
#define VasEBoot_BTRFS_COMPRESSION_ZLIB 1
#define VasEBoot_BTRFS_COMPRESSION_LZO 2
#define VasEBoot_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 = VasEBoot_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)
>> VasEBoot_DISK_SECTOR_BITS) <= superblock_sectors[i])
break;
err = VasEBoot_disk_read (disk, superblock_sectors[i], 0,
sizeof (sblock), &sblock);
if (err == VasEBoot_ERR_OUT_OF_RANGE)
break;
if (VasEBoot_memcmp ((char *) sblock.signature, VasEBoot_BTRFS_SIGNATURE,
sizeof (VasEBoot_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 == VasEBoot_ERR_OUT_OF_RANGE || !err) && i == 0)
return VasEBoot_error (VasEBoot_ERR_BAD_FS, "not a Btrfs filesystem");
if (err == VasEBoot_ERR_OUT_OF_RANGE)
VasEBoot_errno = err = VasEBoot_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
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;
desc->allocated *= 2;
newdata = VasEBoot_realloc (desc->data, sizeof (desc->data[0])
* desc->allocated);
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 VasEBoot_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;
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_malloc (sizeof (desc->data[0]) * desc->allocated);
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 (VasEBoot_ERR_BAD_FS, "too deep btrfs virtual nesting");
VasEBoot_dprintf ("btrfs",
"retrieving %" PRIxVasEBoot_UINT64_T
" %x %" PRIxVasEBoot_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;
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) %" PRIxVasEBoot_UINT64_T
" %x %" PRIxVasEBoot_UINT64_T "\n", depth,
node.key.object_id, node.key.type,
node.key.offset);
if (key_cmp (&node.key, key_in) == 0)
{
err = VasEBoot_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 = VasEBoot_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 VasEBoot_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) %" PRIxVasEBoot_UINT64_T
" %x %" PRIxVasEBoot_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 VasEBoot_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 VasEBoot_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 VasEBoot_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 == VasEBoot_ERR_BAD_FS)
{
VasEBoot_device_close (dev);
VasEBoot_errno = VasEBoot_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, int do_rescan)
{
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;
if (do_rescan)
VasEBoot_device_iterate (find_device_iter, &ctx);
if (!ctx.dev_found)
{
VasEBoot_error (VasEBoot_ERR_BAD_FS,
N_("couldn't find a necessary member device "
"of multi-device filesystem"));
return NULL;
}
data->n_devices_attached++;
if (data->n_devices_attached > data->n_devices_allocated)
{
void *tmp;
data->n_devices_allocated = 2 * data->n_devices_attached + 1;
data->devices_attached
= VasEBoot_realloc (tmp = data->devices_attached,
data->n_devices_allocated
* sizeof (data->devices_attached[0]));
if (!data->devices_attached)
{
VasEBoot_device_close (ctx.dev_found);
data->devices_attached = tmp;
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
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;
VasEBoot_device_t dev;
struct VasEBoot_btrfs_key key_in;
VasEBoot_size_t chsize;
VasEBoot_disk_addr_t chaddr;
VasEBoot_dprintf ("btrfs", "searching for laddr %" PRIxVasEBoot_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 != VasEBoot_BTRFS_ITEM_TYPE_CHUNK)
break;
chunk = (struct VasEBoot_btrfs_chunk_item *) (key + 1);
VasEBoot_dprintf ("btrfs",
"%" PRIxVasEBoot_UINT64_T " %" PRIxVasEBoot_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 (VasEBoot_BTRFS_OBJECT_ID_CHUNK);
key_in.type = VasEBoot_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 != VasEBoot_BTRFS_ITEM_TYPE_CHUNK
|| !(VasEBoot_le_to_cpu64 (key->offset) <= addr))
return VasEBoot_error (VasEBoot_ERR_BAD_FS,
"couldn't find the chunk descriptor");
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;
if (VasEBoot_le_to_cpu64 (chunk->size) <= off)
{
VasEBoot_dprintf ("btrfs", "no chunk\n");
return VasEBoot_error (VasEBoot_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%" PRIxVasEBoot_UINT64_T
"+0x%" PRIxVasEBoot_UINT64_T
" (%d stripes (%d substripes) of %"
PRIxVasEBoot_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)
& ~VasEBoot_BTRFS_CHUNK_TYPE_BITS_DONTCARE)
{
case VasEBoot_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);
if (stripe_length == 0)
stripe_length = 512;
stripen = VasEBoot_divmod64 (off, stripe_length, &stripe_offset);
csize = (stripen + 1) * stripe_length - off;
break;
}
case VasEBoot_BTRFS_CHUNK_TYPE_DUPLICATED:
case VasEBoot_BTRFS_CHUNK_TYPE_RAID1:
{
VasEBoot_dprintf ("btrfs", "RAID1\n");
stripen = 0;
stripe_offset = off;
csize = VasEBoot_le_to_cpu64 (chunk->size) - off;
redundancy = 2;
break;
}
case VasEBoot_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 VasEBoot_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;
break;
}
default:
VasEBoot_dprintf ("btrfs", "unsupported RAID\n");
return VasEBoot_error (VasEBoot_ERR_NOT_IMPLEMENTED_YET,
"unsupported RAID flags %" PRIxVasEBoot_UINT64_T,
VasEBoot_le_to_cpu64 (chunk->type));
}
if (csize == 0)
return VasEBoot_error (VasEBoot_ERR_BUG,
"couldn't find the chunk descriptor");
if (csize > (VasEBoot_uint64_t) size)
csize = size;
for (j = 0; j < 2; j++)
{
for (i = 0; i < redundancy; i++)
{
struct VasEBoot_btrfs_chunk_stripe *stripe;
VasEBoot_disk_addr_t paddr;
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 + i;
paddr = VasEBoot_le_to_cpu64 (stripe->offset) + stripe_offset;
VasEBoot_dprintf ("btrfs", "chunk 0x%" PRIxVasEBoot_UINT64_T
"+0x%" PRIxVasEBoot_UINT64_T
" (%d stripes (%d substripes) of %"
PRIxVasEBoot_UINT64_T ") stripe %" PRIxVasEBoot_UINT64_T
" maps to 0x%" PRIxVasEBoot_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),
stripen, stripe->offset);
VasEBoot_dprintf ("btrfs", "reading paddr 0x%" PRIxVasEBoot_UINT64_T
" for laddr 0x%" PRIxVasEBoot_UINT64_T "\n", paddr,
addr);
dev = find_device (data, stripe->device_id, j);
if (!dev)
{
err = VasEBoot_errno;
VasEBoot_errno = VasEBoot_ERR_NONE;
continue;
}
err = VasEBoot_disk_read (dev->disk, paddr >> VasEBoot_DISK_SECTOR_BITS,
paddr & (VasEBoot_DISK_SECTOR_SIZE - 1),
csize, buf);
if (!err)
break;
VasEBoot_errno = VasEBoot_ERR_NONE;
}
if (i != redundancy)
break;
}
if (err)
return VasEBoot_errno = err;
}
size -= csize;
buf = (VasEBoot_uint8_t *) buf + csize;
addr += csize;
if (challoc)
VasEBoot_free (chunk);
}
return VasEBoot_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 (VasEBoot_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_malloc (sizeof (data->devices_attached[0])
* data->n_devices_allocated);
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++)
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 = VasEBoot_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 != VasEBoot_BTRFS_ITEM_TYPE_INODE_ITEM)
return VasEBoot_error (VasEBoot_ERR_BAD_FS, "inode not found");
return VasEBoot_btrfs_read_logical (data, elemaddr, inode, sizeof (*inode), 0);
}
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 >= VasEBoot_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 > VasEBoot_BTRFS_LZO_BLOCK_MAX_CSIZE)
return -1;
off -= VasEBoot_BTRFS_LZO_BLOCK_SIZE;
ibuf += cblock_size;
}
while (osize > 0)
{
lzo_uint usize = VasEBoot_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 > VasEBoot_BTRFS_LZO_BLOCK_MAX_CSIZE)
return -1;
/* Block partially filled with requested data. */
if (off > 0 || osize < VasEBoot_BTRFS_LZO_BLOCK_SIZE)
{
VasEBoot_size_t to_copy = VasEBoot_BTRFS_LZO_BLOCK_SIZE - off;
VasEBoot_uint8_t *buf;
if (to_copy > osize)
to_copy = osize;
buf = VasEBoot_malloc (VasEBoot_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;
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 = VasEBoot_BTRFS_ITEM_TYPE_EXTENT_ITEM;
key_in.offset = VasEBoot_cpu_to_le64 (pos);
err = lower_bound (data, &key_in, &key_out, tree,
&elemaddr, &elemsize, NULL, 0);
if (err)
return -1;
if (key_out.object_id != ino
|| key_out.type != VasEBoot_BTRFS_ITEM_TYPE_EXTENT_ITEM)
{
VasEBoot_error (VasEBoot_ERR_BAD_FS, "extent not found");
return -1;
}
if ((VasEBoot_ssize_t) elemsize < ((char *) &data->extent->inl
- (char *) data->extent))
{
VasEBoot_error (VasEBoot_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 == VasEBoot_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%" PRIxVasEBoot_UINT64_T "+0x%"
PRIxVasEBoot_UINT64_T "\n",
VasEBoot_le_to_cpu64 (key_out.offset),
VasEBoot_le_to_cpu64 (data->extent->size));
if (data->extend <= pos)
{
VasEBoot_error (VasEBoot_ERR_BAD_FS, "extent not found");
return -1;
}
}
csize = data->extend - pos;
extoff = pos - data->extstart;
if (csize > len)
csize = len;
if (data->extent->encryption)
{
VasEBoot_error (VasEBoot_ERR_NOT_IMPLEMENTED_YET,
"encryption not supported");
return -1;
}
if (data->extent->compression != VasEBoot_BTRFS_COMPRESSION_NONE
&& data->extent->compression != VasEBoot_BTRFS_COMPRESSION_ZLIB
&& data->extent->compression != VasEBoot_BTRFS_COMPRESSION_LZO)
{
VasEBoot_error (VasEBoot_ERR_NOT_IMPLEMENTED_YET,
"compression type 0x%x not supported",
data->extent->compression);
return -1;
}
if (data->extent->encoding)
{
VasEBoot_error (VasEBoot_ERR_NOT_IMPLEMENTED_YET, "encoding not supported");
return -1;
}
switch (data->extent->type)
{
case VasEBoot_BTRFS_EXTENT_INLINE:
if (data->extent->compression == VasEBoot_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 (VasEBoot_ERR_BAD_COMPRESSED_DATA,
"premature end of compressed");
return -1;
}
}
else if (data->extent->compression == VasEBoot_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
VasEBoot_memcpy (buf, data->extent->inl + extoff, csize);
break;
case VasEBoot_BTRFS_EXTENT_REGULAR:
if (!data->extent->laddr)
{
VasEBoot_memset (buf, 0, csize);
break;
}
if (data->extent->compression != VasEBoot_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 == VasEBoot_BTRFS_COMPRESSION_ZLIB)
ret = VasEBoot_zlib_decompress (tmp, zsize, extoff
+ VasEBoot_le_to_cpu64 (data->extent->offset),
buf, csize);
else if (data->extent->compression == VasEBoot_BTRFS_COMPRESSION_LZO)
ret = VasEBoot_btrfs_lzo_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 (VasEBoot_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 (VasEBoot_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 (VasEBoot_BTRFS_ROOT_VOL_OBJECTID);
key_in.offset = 0;
key_in.type = VasEBoot_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 (VasEBoot_ERR_BAD_FS, "no root");
err = VasEBoot_btrfs_read_logical (data, elemaddr, &ri,
sizeof (ri), 0);
if (err)
return err;
key->type = VasEBoot_BTRFS_ITEM_TYPE_DIR_ITEM;
key->offset = 0;
key->object_id = VasEBoot_cpu_to_le64_compile_time (VasEBoot_BTRFS_OBJECT_ID_CHUNK);
*tree = ri.tree;
*type = VasEBoot_BTRFS_DIR_ITEM_TYPE_DIRECTORY;
return VasEBoot_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;
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 != VasEBoot_BTRFS_DIR_ITEM_TYPE_DIRECTORY)
{
VasEBoot_free (path_alloc);
VasEBoot_free (origpath);
return VasEBoot_error (VasEBoot_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 = VasEBoot_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 (VasEBoot_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath);
VasEBoot_free (origpath);
return err;
}
*type = VasEBoot_BTRFS_DIR_ITEM_TYPE_DIRECTORY;
key->object_id = key_out.offset;
path = slash;
continue;
}
key->type = VasEBoot_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 (VasEBoot_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath);
VasEBoot_free (origpath);
return err;
}
struct VasEBoot_btrfs_dir_item *cdirel;
if (elemsize > allocated)
{
allocated = 2 * elemsize;
VasEBoot_free (direl);
direl = VasEBoot_malloc (allocated + 1);
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 (VasEBoot_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath);
VasEBoot_free (origpath);
return err;
}
path = slash;
if (cdirel->type == VasEBoot_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 (VasEBoot_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;
}
tmp = VasEBoot_malloc (VasEBoot_le_to_cpu64 (inode.size)
+ VasEBoot_strlen (path) + 1);
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)
return err;
}
continue;
}
*type = cdirel->type;
switch (cdirel->key.type)
{
case VasEBoot_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 (VasEBoot_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 = VasEBoot_BTRFS_ITEM_TYPE_DIR_ITEM;
key->offset = 0;
key->object_id = VasEBoot_cpu_to_le64_compile_time (VasEBoot_BTRFS_OBJECT_ID_CHUNK);
*tree = ri.tree;
break;
}
case VasEBoot_BTRFS_ITEM_TYPE_INODE_ITEM:
if (*slash && *type == VasEBoot_BTRFS_DIR_ITEM_TYPE_REGULAR)
{
VasEBoot_free (direl);
VasEBoot_free (path_alloc);
err = VasEBoot_error (VasEBoot_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), origpath);
VasEBoot_free (origpath);
return err;
}
*key = cdirel->key;
if (*type == VasEBoot_BTRFS_DIR_ITEM_TYPE_DIRECTORY)
key->type = VasEBoot_BTRFS_ITEM_TYPE_DIR_ITEM;
break;
default:
VasEBoot_free (path_alloc);
VasEBoot_free (origpath);
VasEBoot_free (direl);
return VasEBoot_error (VasEBoot_ERR_BAD_FS, "unrecognised object type 0x%x",
cdirel->key.type);
}
}
VasEBoot_free (direl);
VasEBoot_free (origpath);
VasEBoot_free (path_alloc);
return VasEBoot_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;
if (!data)
return VasEBoot_errno;
err = find_path (data, path, &key_in, &tree, &type);
if (err)
{
VasEBoot_btrfs_unmount (data);
return err;
}
if (type != VasEBoot_BTRFS_DIR_ITEM_TYPE_DIRECTORY)
{
VasEBoot_btrfs_unmount (data);
return VasEBoot_error (VasEBoot_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);
return err;
}
if (key_out.type != VasEBoot_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 != VasEBoot_BTRFS_ITEM_TYPE_DIR_ITEM
|| key_out.object_id != key_in.object_id)
{
r = 0;
break;
}
if (elemsize > allocated)
{
allocated = 2 * elemsize;
VasEBoot_free (direl);
direl = VasEBoot_malloc (allocated + 1);
if (!direl)
{
r = -VasEBoot_errno;
break;
}
}
err = VasEBoot_btrfs_read_logical (data, elemaddr, direl, elemsize, 0);
if (err)
{
r = -err;
break;
}
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;
err = VasEBoot_btrfs_read_inode (data, &inode, cdirel->key.object_id,
tree);
VasEBoot_memset (&info, 0, sizeof (info));
if (err)
VasEBoot_errno = VasEBoot_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 == VasEBoot_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 != VasEBoot_BTRFS_DIR_ITEM_TYPE_REGULAR)
{
VasEBoot_btrfs_unmount (data);
return VasEBoot_error (VasEBoot_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 VasEBoot_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 VasEBoot_UTIL
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 i;
if (embed_type != VasEBoot_EMBED_PCBIOS)
return VasEBoot_error (VasEBoot_ERR_NOT_IMPLEMENTED_YET,
"BtrFS currently supports only PC-BIOS embedding");
if (64 * 2 - 1 < *nsectors)
return VasEBoot_error (VasEBoot_ERR_OUT_OF_RANGE,
N_("your core.img is unusually large. "
"It won't fit in the embedding area"));
*nsectors = 64 * 2 - 1;
if (*nsectors > max_nsectors)
*nsectors = max_nsectors;
*sectors = VasEBoot_malloc (*nsectors * sizeof (**sectors));
if (!*sectors)
return VasEBoot_errno;
for (i = 0; i < *nsectors; i++)
(*sectors)[i] = i + 1;
return VasEBoot_ERR_NONE;
}
#endif
static struct VasEBoot_fs VasEBoot_btrfs_fs = {
.name = "btrfs",
.dir = VasEBoot_btrfs_dir,
.open = VasEBoot_btrfs_open,
.read = VasEBoot_btrfs_read,
.close = VasEBoot_btrfs_close,
.uuid = VasEBoot_btrfs_uuid,
.label = VasEBoot_btrfs_label,
#ifdef VasEBoot_UTIL
.embed = VasEBoot_btrfs_embed,
.reserved_first_sector = 1,
.blocklist_install = 0,
#endif
};
VasEBoot_MOD_INIT (btrfs)
{
VasEBoot_fs_register (&VasEBoot_btrfs_fs);
}
VasEBoot_MOD_FINI (btrfs)
{
VasEBoot_fs_unregister (&VasEBoot_btrfs_fs);
}