partial btrfs support. Now able to list and access files as long as all trees are flat
This commit is contained in:
parent
3836e89df1
commit
b18610feb5
1 changed files with 839 additions and 13 deletions
|
@ -24,39 +24,491 @@
|
|||
#include <grub/disk.h>
|
||||
#include <grub/dl.h>
|
||||
#include <grub/types.h>
|
||||
#include <grub/lib/crc.h>
|
||||
|
||||
#define BTRFS_SIGNATURE "_BHRfS_M"
|
||||
|
||||
typedef grub_uint8_t btrfs_checksum_t[0x20];
|
||||
typedef grub_uint16_t btrfs_uuid_t[8];
|
||||
|
||||
struct grub_btrfs_device
|
||||
{
|
||||
grub_uint64_t device_id;
|
||||
grub_uint8_t dummy[0x62 - 8];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct btrfs_superblock
|
||||
{
|
||||
grub_uint8_t dummy1[32];
|
||||
grub_uint16_t uuid[8];
|
||||
grub_uint8_t dummy2[16];
|
||||
btrfs_checksum_t checksum;
|
||||
btrfs_uuid_t uuid;
|
||||
grub_uint8_t dummy[0x10];
|
||||
grub_uint8_t signature[sizeof (BTRFS_SIGNATURE) - 1];
|
||||
grub_uint64_t generation;
|
||||
grub_uint64_t root_tree;
|
||||
grub_uint64_t chunk_tree;
|
||||
grub_uint8_t dummy2[0x20];
|
||||
grub_uint64_t root_dir_objectid;
|
||||
grub_uint8_t dummy3[0x41];
|
||||
struct grub_btrfs_device this_device;
|
||||
char label[0x100];
|
||||
grub_uint8_t dummy4[0x100];
|
||||
grub_uint8_t bootstrap_mapping[0x800];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct btrfs_header
|
||||
{
|
||||
btrfs_checksum_t checksum;
|
||||
btrfs_uuid_t uuid;
|
||||
grub_uint8_t dummy[0x30];
|
||||
grub_uint32_t nitems;
|
||||
grub_uint8_t level;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct grub_btrfs_data
|
||||
{
|
||||
struct btrfs_superblock sblock;
|
||||
unsigned int sblock_number;
|
||||
grub_uint64_t tree;
|
||||
grub_uint64_t inode;
|
||||
};
|
||||
|
||||
struct grub_btrfs_key
|
||||
{
|
||||
grub_uint64_t object_id;
|
||||
#define GRUB_BTRFS_ITEM_TYPE_INODE_ITEM 0x01
|
||||
#define GRUB_BTRFS_ITEM_TYPE_DIR_ITEM 0x54
|
||||
#define GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM 0x6c
|
||||
#define GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM 0x84
|
||||
#define GRUB_BTRFS_ITEM_TYPE_DEVICE 0xd8
|
||||
#define GRUB_BTRFS_ITEM_TYPE_CHUNK 0xe4
|
||||
grub_uint8_t type;
|
||||
grub_uint64_t offset;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct grub_btrfs_chunk_item
|
||||
{
|
||||
grub_uint64_t size;
|
||||
grub_uint64_t dummy;
|
||||
grub_uint64_t stripe_length;
|
||||
grub_uint8_t dummy2[0x14];
|
||||
grub_uint16_t nstripes;
|
||||
grub_uint16_t dummy3;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct grub_btrfs_chunk_stripe
|
||||
{
|
||||
grub_uint64_t device_id;
|
||||
grub_uint64_t offset;
|
||||
btrfs_uuid_t device_uuid;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct grub_btrfs_leaf_node
|
||||
{
|
||||
struct grub_btrfs_key key;
|
||||
grub_uint32_t offset;
|
||||
grub_uint32_t size;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct grub_btrfs_dir_item
|
||||
{
|
||||
struct grub_btrfs_key key;
|
||||
grub_uint8_t dummy[8];
|
||||
grub_uint16_t m;
|
||||
grub_uint16_t n;
|
||||
grub_uint8_t type;
|
||||
char name[0];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct grub_btrfs_leaf_descriptor
|
||||
{
|
||||
unsigned depth;
|
||||
unsigned allocated;
|
||||
struct {
|
||||
grub_disk_addr_t addr;
|
||||
unsigned iter;
|
||||
unsigned maxiter;
|
||||
int leaf;
|
||||
} *data;
|
||||
};
|
||||
|
||||
struct grub_btrfs_root_item
|
||||
{
|
||||
grub_uint8_t dummy[0xb0];
|
||||
grub_uint64_t tree;
|
||||
grub_uint64_t inode;
|
||||
};
|
||||
|
||||
struct grub_btrfs_inode
|
||||
{
|
||||
grub_uint8_t dummy[0x10];
|
||||
grub_uint64_t size;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct grub_btrfs_extent_data
|
||||
{
|
||||
grub_uint64_t dummy;
|
||||
grub_uint64_t size;
|
||||
grub_uint8_t compression;
|
||||
grub_uint8_t encryption;
|
||||
grub_uint16_t encoding;
|
||||
grub_uint8_t type;
|
||||
union
|
||||
{
|
||||
char inl[0];
|
||||
grub_uint64_t laddr;
|
||||
};
|
||||
} __attribute__ ((packed));
|
||||
|
||||
#define GRUB_BTRFS_EXTENT_INLINE 0
|
||||
#define GRUB_BTRFS_EXTENT_REGULAR 1
|
||||
|
||||
|
||||
#define GRUB_BTRFS_OBJECT_ID_CHUNK 0x100
|
||||
|
||||
static grub_disk_addr_t superblock_sectors[] = { 64 * 2, 64 * 1024 * 2,
|
||||
256 * 1048576 * 2,
|
||||
1048576ULL * 1048576ULL * 2 };
|
||||
|
||||
static grub_err_t
|
||||
grub_btrfs_read_logical (struct grub_btrfs_data *data,
|
||||
grub_disk_t disk, grub_disk_addr_t addr,
|
||||
void *buf, grub_size_t size);
|
||||
|
||||
static int
|
||||
key_cmp (const struct grub_btrfs_key *a, const struct grub_btrfs_key *b)
|
||||
{
|
||||
if (grub_cpu_to_le64 (a->object_id) < grub_cpu_to_le64 (b->object_id))
|
||||
return -1;
|
||||
if (grub_cpu_to_le64 (a->object_id) > grub_cpu_to_le64 (b->object_id))
|
||||
return +1;
|
||||
|
||||
if (a->type < b->type)
|
||||
return -1;
|
||||
if (a->type > b->type)
|
||||
return +1;
|
||||
|
||||
if (grub_cpu_to_le64 (a->offset) < grub_cpu_to_le64 (b->offset))
|
||||
return -1;
|
||||
if (grub_cpu_to_le64 (a->offset) > grub_cpu_to_le64 (b->offset))
|
||||
return +1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
free_iterator (struct grub_btrfs_leaf_descriptor *desc)
|
||||
{
|
||||
grub_free (desc->data);
|
||||
}
|
||||
|
||||
static int
|
||||
next (struct grub_btrfs_data *data, grub_disk_t disk,
|
||||
struct grub_btrfs_leaf_descriptor *desc,
|
||||
grub_disk_addr_t *outaddr, grub_size_t *outsize,
|
||||
struct grub_btrfs_key *key_out)
|
||||
{
|
||||
int i;
|
||||
grub_err_t err;
|
||||
struct grub_btrfs_leaf_node leaf;
|
||||
|
||||
if (desc->depth == 0)
|
||||
return 0;
|
||||
for (i = desc->depth - 1; i >= 0; i--)
|
||||
{
|
||||
desc->data[i].iter++;
|
||||
if (desc->data[i].iter
|
||||
< desc->data[desc->depth - 1].maxiter)
|
||||
break;
|
||||
desc->depth--;
|
||||
}
|
||||
if (i == -1)
|
||||
return 0;
|
||||
while (!desc->data[desc->depth - 1].leaf)
|
||||
{
|
||||
grub_printf ("No trees\n");
|
||||
return -grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "no trees yet");
|
||||
}
|
||||
err = grub_btrfs_read_logical (data, disk,
|
||||
desc->data[desc->depth - 1].iter
|
||||
* sizeof (leaf)
|
||||
+ sizeof (struct btrfs_header)
|
||||
+ desc->data[desc->depth - 1].addr, &leaf,
|
||||
sizeof (leaf));
|
||||
if (err)
|
||||
return -err;
|
||||
*outsize = grub_le_to_cpu32 (leaf.size);
|
||||
*outaddr = desc->data[desc->depth - 1].addr + sizeof (struct btrfs_header)
|
||||
+ grub_le_to_cpu32 (leaf.offset);
|
||||
*key_out = leaf.key;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static grub_err_t
|
||||
save_ref (struct grub_btrfs_leaf_descriptor *desc,
|
||||
grub_disk_addr_t addr, unsigned i, unsigned m, int l)
|
||||
{
|
||||
desc->depth++;
|
||||
if (desc->allocated > desc->depth)
|
||||
{
|
||||
void *newdata;
|
||||
desc->allocated *= 2;
|
||||
newdata = grub_realloc (desc->data, sizeof (desc->data[0])
|
||||
* desc->allocated);
|
||||
if (!newdata)
|
||||
return grub_errno;
|
||||
}
|
||||
desc->data[desc->depth - 1].addr = addr;
|
||||
desc->data[desc->depth - 1].iter = i;
|
||||
desc->data[desc->depth - 1].maxiter = m;
|
||||
desc->data[desc->depth - 1].leaf = l;
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
static grub_err_t
|
||||
lower_bound (struct grub_btrfs_data *data, grub_disk_t disk,
|
||||
const struct grub_btrfs_key *key_in,
|
||||
struct grub_btrfs_key *key_out,
|
||||
grub_disk_addr_t root,
|
||||
grub_disk_addr_t *outaddr, grub_size_t *outsize,
|
||||
struct grub_btrfs_leaf_descriptor *desc)
|
||||
{
|
||||
grub_disk_addr_t addr = root;
|
||||
struct btrfs_header head;
|
||||
grub_err_t err;
|
||||
unsigned i;
|
||||
struct grub_btrfs_leaf_node leaf, leaf_last;
|
||||
int have_last = 0;
|
||||
|
||||
if (desc)
|
||||
{
|
||||
desc->allocated = 16;
|
||||
desc->depth = 0;
|
||||
desc->data = grub_malloc (sizeof (desc->data[0]) * desc->allocated);
|
||||
if (!desc->data)
|
||||
return grub_errno;
|
||||
}
|
||||
|
||||
while (1)
|
||||
{
|
||||
/* FIXME: preread few leafs into buffer. */
|
||||
err = grub_btrfs_read_logical (data, disk, addr, &head, sizeof (head));
|
||||
if (err)
|
||||
return err;
|
||||
if (head.level)
|
||||
{
|
||||
grub_printf ("No trees\n");
|
||||
return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
|
||||
"trees aren't implemented yet");
|
||||
}
|
||||
addr += sizeof (head);
|
||||
for (i = 0; i < grub_le_to_cpu32 (head.nitems); i++)
|
||||
{
|
||||
err = grub_btrfs_read_logical (data, disk, addr + i * sizeof (leaf),
|
||||
&leaf, sizeof (leaf));
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
grub_dprintf ("btrfs",
|
||||
"%" PRIxGRUB_UINT64_T " %x %" PRIxGRUB_UINT64_T "\n",
|
||||
leaf.key.object_id, leaf.key.type, leaf.key.offset);
|
||||
|
||||
if (key_cmp (&leaf.key, key_in) == 0)
|
||||
{
|
||||
grub_memcpy (key_out, &leaf.key, sizeof(*key_out));
|
||||
*outsize = grub_le_to_cpu32 (leaf.size);
|
||||
*outaddr = addr + grub_le_to_cpu32 (leaf.offset);
|
||||
if (desc)
|
||||
return save_ref (desc, addr - sizeof (head), i,
|
||||
grub_le_to_cpu32 (head.nitems), 1);
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
if (key_cmp (&leaf.key, key_in) > 0)
|
||||
break;
|
||||
|
||||
have_last = 1;
|
||||
leaf_last = leaf;
|
||||
}
|
||||
|
||||
if (have_last)
|
||||
{
|
||||
grub_memcpy (key_out, &leaf_last.key, sizeof(*key_out));
|
||||
*outsize = grub_le_to_cpu32 (leaf_last.size);
|
||||
*outaddr = addr + grub_le_to_cpu32 (leaf_last.offset);
|
||||
if (desc)
|
||||
return save_ref (desc, addr - sizeof (head), i - 1,
|
||||
grub_le_to_cpu32 (head.nitems), 1);
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
*outsize = 0;
|
||||
*outaddr = 0;
|
||||
grub_memset (key_out, 0, sizeof (*key_out));
|
||||
if (desc)
|
||||
return save_ref (desc, addr - sizeof (head), -1,
|
||||
grub_le_to_cpu32 (head.nitems), 1);
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
static grub_err_t
|
||||
grub_btrfs_read_logical (struct grub_btrfs_data *data,
|
||||
grub_disk_t disk, grub_disk_addr_t addr,
|
||||
void *buf, grub_size_t size)
|
||||
{
|
||||
while (size > 0)
|
||||
{
|
||||
grub_uint8_t *ptr;
|
||||
struct grub_btrfs_key *key;
|
||||
struct grub_btrfs_chunk_item *chunk;
|
||||
struct grub_btrfs_chunk_stripe *stripe;
|
||||
grub_size_t csize;
|
||||
grub_err_t err;
|
||||
grub_disk_addr_t paddr;
|
||||
grub_uint64_t stripen;
|
||||
grub_uint32_t stripe_length;
|
||||
grub_uint32_t stripe_offset;
|
||||
struct grub_btrfs_key key_out;
|
||||
int challoc = 0;
|
||||
for (ptr = data->sblock.bootstrap_mapping;
|
||||
ptr < data->sblock.bootstrap_mapping
|
||||
+ sizeof (data->sblock.bootstrap_mapping)
|
||||
- sizeof (struct grub_btrfs_key);
|
||||
)
|
||||
{
|
||||
key = (struct grub_btrfs_key *) ptr;
|
||||
if (key->type != GRUB_BTRFS_ITEM_TYPE_CHUNK)
|
||||
break;
|
||||
chunk = (struct grub_btrfs_chunk_item *) (key + 1);
|
||||
grub_dprintf ("btrfs", "%" PRIxGRUB_UINT64_T " %" PRIxGRUB_UINT64_T " \n",
|
||||
grub_le_to_cpu64 (key->offset),
|
||||
grub_le_to_cpu64 (chunk->size));
|
||||
if (grub_le_to_cpu64 (key->offset) <= addr
|
||||
&& addr < grub_le_to_cpu64 (key->offset)
|
||||
+ grub_le_to_cpu64 (chunk->size))
|
||||
goto chunk_found;
|
||||
ptr += sizeof (*key) + sizeof (*chunk)
|
||||
+ sizeof (*stripe) * grub_le_to_cpu16 (chunk->nstripes);
|
||||
}
|
||||
struct grub_btrfs_key key_in;
|
||||
grub_size_t chsize;
|
||||
grub_disk_addr_t chaddr;
|
||||
key_in.object_id = GRUB_BTRFS_OBJECT_ID_CHUNK;
|
||||
key_in.type = GRUB_BTRFS_ITEM_TYPE_CHUNK;
|
||||
key_in.offset = addr;
|
||||
err = lower_bound (data, disk,
|
||||
&key_in, &key_out,
|
||||
grub_le_to_cpu64 (data->sblock.chunk_tree),
|
||||
&chaddr, &chsize, NULL);
|
||||
if (err)
|
||||
return err;
|
||||
key = &key_out;
|
||||
if (key->type != GRUB_BTRFS_ITEM_TYPE_CHUNK
|
||||
|| !(grub_le_to_cpu64 (key->offset) <= addr))
|
||||
return grub_error (GRUB_ERR_BAD_FS,
|
||||
"couldn't find the chunk descriptor");
|
||||
|
||||
chunk = grub_malloc (chsize);
|
||||
if (!chunk)
|
||||
return grub_errno;
|
||||
|
||||
challoc = 1;
|
||||
err = grub_btrfs_read_logical (data, disk, chaddr,
|
||||
chunk, chsize);
|
||||
if (err)
|
||||
{
|
||||
grub_free (chunk);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!(addr < grub_le_to_cpu64 (key->offset)
|
||||
+ grub_le_to_cpu64 (chunk->size)))
|
||||
return grub_error (GRUB_ERR_BAD_FS,
|
||||
"couldn't find the chunk descriptor");
|
||||
|
||||
chunk_found:
|
||||
stripe_length = grub_divmod64 (grub_le_to_cpu64 (chunk->size),
|
||||
grub_le_to_cpu16 (chunk->nstripes),
|
||||
NULL);
|
||||
stripen = grub_divmod64 (addr - grub_le_to_cpu64 (key->offset),
|
||||
stripe_length, &stripe_offset);
|
||||
stripe = (struct grub_btrfs_chunk_stripe *) (chunk + 1);
|
||||
stripe += stripen;
|
||||
csize = grub_le_to_cpu64 (key->offset) + grub_le_to_cpu64 (chunk->size)
|
||||
- addr;
|
||||
if (csize > size)
|
||||
csize = size;
|
||||
if (grub_le_to_cpu64 (stripe->device_id) != grub_le_to_cpu64 (data->sblock.this_device.device_id))
|
||||
{
|
||||
if (challoc)
|
||||
grub_free (chunk);
|
||||
return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
|
||||
"multidevice isn't implemented yet");
|
||||
}
|
||||
grub_dprintf ("btrfs", "chunk 0x%" PRIxGRUB_UINT64_T
|
||||
"+0x%" PRIxGRUB_UINT64_T " (%d stripes of %"
|
||||
PRIxGRUB_UINT64_T ") stripe %" PRIxGRUB_UINT64_T
|
||||
" maps to 0x%" PRIxGRUB_UINT64_T "\n",
|
||||
grub_le_to_cpu64 (key->offset),
|
||||
grub_le_to_cpu64 (chunk->size),
|
||||
grub_le_to_cpu16 (chunk->nstripes),
|
||||
grub_le_to_cpu64 (chunk->stripe_length),
|
||||
stripen,
|
||||
stripe->offset);
|
||||
paddr = stripe->offset + stripe_offset;
|
||||
|
||||
grub_dprintf ("btrfs", "reading paddr 0x%" PRIxGRUB_UINT64_T
|
||||
" for laddr 0x%" PRIxGRUB_UINT64_T"\n", paddr,
|
||||
addr);
|
||||
err = grub_disk_read (disk, paddr >> GRUB_DISK_SECTOR_BITS,
|
||||
paddr & (GRUB_DISK_SECTOR_SIZE - 1), csize, buf);
|
||||
size -= csize;
|
||||
buf = (grub_uint8_t *) buf + csize;
|
||||
addr += csize;
|
||||
if (challoc)
|
||||
grub_free (chunk);
|
||||
}
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
static struct grub_btrfs_data *
|
||||
grub_btrfs_mount (grub_disk_t disk)
|
||||
{
|
||||
struct grub_btrfs_data *data = grub_malloc (sizeof (*data));
|
||||
unsigned i;
|
||||
grub_err_t err = GRUB_ERR_NONE;
|
||||
|
||||
if (! data)
|
||||
return NULL;
|
||||
|
||||
if (grub_disk_read (disk, 128, 0, sizeof (data->sblock),
|
||||
&data->sblock) != GRUB_ERR_NONE)
|
||||
goto fail;
|
||||
for (i = 0; i < ARRAY_SIZE (superblock_sectors); i++)
|
||||
{
|
||||
struct btrfs_superblock sblock;
|
||||
err = grub_disk_read (disk, superblock_sectors[i], 0,
|
||||
sizeof (sblock), &sblock);
|
||||
if (err == GRUB_ERR_OUT_OF_RANGE)
|
||||
break;
|
||||
|
||||
if (grub_memcmp ((char *) data->sblock.signature, BTRFS_SIGNATURE, sizeof (BTRFS_SIGNATURE) - 1))
|
||||
if (grub_memcmp ((char *) sblock.signature, BTRFS_SIGNATURE,
|
||||
sizeof (BTRFS_SIGNATURE) - 1))
|
||||
break;
|
||||
if (i == 0 || grub_le_to_cpu64 (sblock.generation)
|
||||
> grub_le_to_cpu64 (data->sblock.generation))
|
||||
{
|
||||
grub_memcpy (&data->sblock, &sblock, sizeof (sblock));
|
||||
data->sblock_number = i;
|
||||
}
|
||||
}
|
||||
|
||||
if ((err == GRUB_ERR_OUT_OF_RANGE || !err) && i == 0)
|
||||
{
|
||||
grub_error (GRUB_ERR_BAD_FS, "not a Btrfs filesystem");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (err == GRUB_ERR_OUT_OF_RANGE)
|
||||
grub_errno = err = GRUB_ERR_NONE;
|
||||
|
||||
grub_dprintf ("btrfs", "using superblock %d\n", data->sblock_number);
|
||||
|
||||
return data;
|
||||
|
||||
fail:
|
||||
|
@ -65,28 +517,381 @@ grub_btrfs_mount (grub_disk_t disk)
|
|||
}
|
||||
|
||||
static grub_err_t
|
||||
grub_btrfs_open (struct grub_file *file __attribute__ ((unused)),
|
||||
const char *name __attribute__ ((unused)))
|
||||
find_path (struct grub_btrfs_data *data,
|
||||
grub_disk_t disk,
|
||||
const char *path, struct grub_btrfs_key *key,
|
||||
grub_uint64_t *tree)
|
||||
{
|
||||
return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "only detection is supported for Btrfs");
|
||||
const char *slash;
|
||||
grub_err_t err;
|
||||
grub_disk_addr_t elemaddr;
|
||||
grub_size_t elemsize;
|
||||
grub_size_t allocated = 0;
|
||||
struct grub_btrfs_dir_item *direl = NULL;
|
||||
struct grub_btrfs_key key_out;
|
||||
|
||||
*tree = data->sblock.root_tree;
|
||||
key->object_id = data->sblock.root_dir_objectid;
|
||||
|
||||
while (1)
|
||||
{
|
||||
key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
|
||||
key->offset = 0;
|
||||
while (path[0] == '/')
|
||||
path++;
|
||||
if (!path[0])
|
||||
break;
|
||||
slash = grub_strchr (path, '/');
|
||||
if (!slash)
|
||||
slash = path + grub_strlen (path);
|
||||
key->offset = grub_cpu_to_le64 (~grub_getcrc32c (1, path, slash - path));
|
||||
|
||||
err = lower_bound (data, disk, key, &key_out, *tree,
|
||||
&elemaddr, &elemsize, NULL);
|
||||
if (err)
|
||||
{
|
||||
grub_free (direl);
|
||||
return err;
|
||||
}
|
||||
if (key_cmp (key, &key_out) != 0)
|
||||
{
|
||||
grub_free (direl);
|
||||
return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
|
||||
}
|
||||
|
||||
struct grub_btrfs_dir_item *cdirel;
|
||||
if (elemsize > allocated)
|
||||
{
|
||||
allocated = 2 * elemsize;
|
||||
grub_free (direl);
|
||||
direl = grub_malloc (allocated + 1);
|
||||
if (!direl)
|
||||
return grub_errno;
|
||||
}
|
||||
|
||||
err = grub_btrfs_read_logical (data, disk, elemaddr,
|
||||
direl, elemsize);
|
||||
if (err)
|
||||
{
|
||||
grub_free (direl);
|
||||
return err;
|
||||
}
|
||||
|
||||
for (cdirel = direl;
|
||||
(grub_uint8_t *) cdirel - (grub_uint8_t *) direl
|
||||
< (grub_ssize_t) elemsize;
|
||||
cdirel = (void *) ((grub_uint8_t *) (direl + 1)
|
||||
+ grub_le_to_cpu16 (cdirel->n)
|
||||
+ grub_le_to_cpu16 (cdirel->m)))
|
||||
{
|
||||
char c;
|
||||
c = cdirel->name[grub_le_to_cpu16 (cdirel->n)];
|
||||
cdirel->name[grub_le_to_cpu16 (cdirel->n)] = 0;
|
||||
if (grub_strncmp (cdirel->name, path, slash - path) == 0)
|
||||
break;
|
||||
cdirel->name[grub_le_to_cpu16 (cdirel->n)] = c;
|
||||
}
|
||||
if ((grub_uint8_t *) cdirel - (grub_uint8_t *) direl
|
||||
>= (grub_ssize_t) elemsize)
|
||||
{
|
||||
grub_free (direl);
|
||||
return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
|
||||
}
|
||||
|
||||
path = slash;
|
||||
|
||||
switch (cdirel->key.type)
|
||||
{
|
||||
case GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM:
|
||||
{
|
||||
struct grub_btrfs_root_item ri;
|
||||
err = lower_bound (data, disk, &cdirel->key, &key_out, *tree,
|
||||
&elemaddr, &elemsize, NULL);
|
||||
if (err)
|
||||
return err;
|
||||
if (cdirel->key.object_id != key_out.object_id
|
||||
|| cdirel->key.type != key_out.type)
|
||||
return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
|
||||
err = grub_btrfs_read_logical (data, disk, elemaddr,
|
||||
&ri, sizeof (ri));
|
||||
if (err)
|
||||
return err;
|
||||
key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
|
||||
key->offset = 0;
|
||||
key->object_id = GRUB_BTRFS_OBJECT_ID_CHUNK;
|
||||
*tree = grub_le_to_cpu64 (ri.tree);
|
||||
break;
|
||||
}
|
||||
case GRUB_BTRFS_ITEM_TYPE_INODE_ITEM:
|
||||
if (*slash)
|
||||
return grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found");
|
||||
*key = cdirel->key;
|
||||
break;
|
||||
default:
|
||||
return grub_error (GRUB_ERR_BAD_FS, "unrecognised object type 0x%x",
|
||||
cdirel->key.type);
|
||||
}
|
||||
}
|
||||
|
||||
grub_free (direl);
|
||||
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
static grub_err_t
|
||||
grub_btrfs_dir (grub_device_t device,
|
||||
const char *path __attribute__ ((unused)),
|
||||
int (*hook) (const char *filename,
|
||||
const struct grub_dirhook_info *info)
|
||||
__attribute__ ((unused)))
|
||||
const struct grub_dirhook_info *info))
|
||||
{
|
||||
struct grub_btrfs_data *data = grub_btrfs_mount (device->disk);
|
||||
if (grub_errno)
|
||||
struct grub_btrfs_key key_in, key_out;
|
||||
grub_err_t err;
|
||||
grub_disk_addr_t elemaddr;
|
||||
grub_size_t elemsize;
|
||||
grub_size_t allocated = 0;
|
||||
struct grub_btrfs_dir_item *direl = NULL;
|
||||
struct grub_btrfs_leaf_descriptor desc;
|
||||
int r;
|
||||
grub_uint64_t tree;
|
||||
|
||||
if (!data)
|
||||
return grub_errno;
|
||||
|
||||
err = find_path (data, device->disk, path, &key_in, &tree);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = lower_bound (data, device->disk, &key_in, &key_out,
|
||||
tree,
|
||||
&elemaddr, &elemsize, &desc);
|
||||
if (err)
|
||||
return err;
|
||||
if (key_out.type != GRUB_BTRFS_ITEM_TYPE_DIR_ITEM
|
||||
|| key_out.object_id != key_in.object_id)
|
||||
{
|
||||
r = next (data, device->disk, &desc, &elemaddr, &elemsize, &key_out);
|
||||
if (r <= 0)
|
||||
{
|
||||
free_iterator (&desc);
|
||||
return -r;
|
||||
}
|
||||
}
|
||||
do
|
||||
{
|
||||
struct grub_dirhook_info info;
|
||||
struct grub_btrfs_dir_item *cdirel;
|
||||
if (key_out.type != GRUB_BTRFS_ITEM_TYPE_DIR_ITEM
|
||||
|| key_out.object_id != key_in.object_id)
|
||||
{
|
||||
r = 0;
|
||||
break;
|
||||
}
|
||||
if (elemsize > allocated)
|
||||
{
|
||||
allocated = 2 * elemsize;
|
||||
grub_free (direl);
|
||||
direl = grub_malloc (allocated + 1);
|
||||
if (!direl)
|
||||
{
|
||||
free_iterator (&desc);
|
||||
return grub_errno;
|
||||
}
|
||||
}
|
||||
|
||||
err = grub_btrfs_read_logical (data, device->disk, elemaddr,
|
||||
direl, elemsize);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
for (cdirel = direl;
|
||||
(grub_uint8_t *) cdirel - (grub_uint8_t *) direl
|
||||
< (grub_ssize_t) elemsize;
|
||||
cdirel = (void *) ((grub_uint8_t *) (direl + 1)
|
||||
+ grub_le_to_cpu16 (cdirel->n)
|
||||
+ grub_le_to_cpu16 (cdirel->m)))
|
||||
{
|
||||
char c;
|
||||
c = cdirel->name[grub_le_to_cpu16 (cdirel->n)];
|
||||
cdirel->name[grub_le_to_cpu16 (cdirel->n)] = 0;
|
||||
grub_memset (&info, 0, sizeof (info));
|
||||
info.dir = (cdirel->type == 2);
|
||||
if (hook (cdirel->name, &info))
|
||||
goto out;
|
||||
cdirel->name[grub_le_to_cpu16 (cdirel->n)] = c;
|
||||
}
|
||||
r = next (data, device->disk, &desc, &elemaddr, &elemsize, &key_out);
|
||||
}
|
||||
while (r > 0);
|
||||
|
||||
out:
|
||||
grub_free (direl);
|
||||
|
||||
free_iterator (&desc);
|
||||
grub_free (data);
|
||||
|
||||
return -r;
|
||||
}
|
||||
|
||||
static grub_err_t
|
||||
grub_btrfs_open (struct grub_file *file, const char *name)
|
||||
{
|
||||
struct grub_btrfs_data *data = grub_btrfs_mount (file->device->disk);
|
||||
struct grub_btrfs_key key_in, key_out;
|
||||
grub_err_t err;
|
||||
grub_disk_addr_t elemaddr;
|
||||
grub_size_t elemsize;
|
||||
struct grub_btrfs_inode inode;
|
||||
|
||||
if (!data)
|
||||
return grub_errno;
|
||||
|
||||
err = find_path (data, file->device->disk, name, &key_in, &data->tree);
|
||||
if (err)
|
||||
{
|
||||
grub_free (data);
|
||||
return err;
|
||||
}
|
||||
data->inode = key_in.object_id;
|
||||
key_in.type = GRUB_BTRFS_ITEM_TYPE_INODE_ITEM;
|
||||
|
||||
err = lower_bound (data, file->device->disk, &key_in, &key_out,
|
||||
data->tree,
|
||||
&elemaddr, &elemsize, NULL);
|
||||
if (err)
|
||||
return err;
|
||||
if (data->inode != key_out.object_id
|
||||
|| key_out.type != GRUB_BTRFS_ITEM_TYPE_INODE_ITEM)
|
||||
return grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a regular file");
|
||||
|
||||
err = grub_btrfs_read_logical (data, file->device->disk, elemaddr,
|
||||
&inode, sizeof (inode));
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
file->data = data;
|
||||
file->size = grub_le_to_cpu64 (inode.size);
|
||||
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
static grub_err_t
|
||||
grub_btrfs_close (grub_file_t file)
|
||||
{
|
||||
grub_free (file->data);
|
||||
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
static grub_ssize_t
|
||||
grub_btrfs_read (grub_file_t file, char *buf, grub_size_t len)
|
||||
{
|
||||
struct grub_btrfs_data *data = file->data;
|
||||
grub_off_t pos = file->offset;
|
||||
grub_disk_addr_t elemaddr;
|
||||
grub_size_t elemsize;
|
||||
struct grub_btrfs_key key_in, key_out;
|
||||
|
||||
while (len)
|
||||
{
|
||||
grub_size_t csize;
|
||||
struct grub_btrfs_extent_data *extent;
|
||||
grub_err_t err;
|
||||
key_in.object_id = data->inode;
|
||||
key_in.type = GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM;
|
||||
key_in.offset = grub_cpu_to_le64 (pos);
|
||||
err = lower_bound (data, file->device->disk, &key_in, &key_out,
|
||||
data->tree,
|
||||
&elemaddr, &elemsize, NULL);
|
||||
if (err)
|
||||
return -1;
|
||||
if (key_out.object_id != data->inode
|
||||
|| key_out.type != GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM)
|
||||
{
|
||||
grub_error (GRUB_ERR_BAD_FS, "extent not found");
|
||||
grub_printf ("no extent\n");
|
||||
return -1;
|
||||
}
|
||||
extent = grub_malloc (elemsize);
|
||||
if (!extent)
|
||||
return grub_errno;
|
||||
|
||||
err = grub_btrfs_read_logical (data, file->device->disk, elemaddr,
|
||||
extent, elemsize);
|
||||
if (err)
|
||||
{
|
||||
grub_free (extent);
|
||||
return err;
|
||||
}
|
||||
if (grub_le_to_cpu64 (extent->size) + grub_le_to_cpu64 (key_out.offset)
|
||||
<= pos)
|
||||
{
|
||||
grub_free (extent);
|
||||
return grub_error (GRUB_ERR_BAD_FS, "extent not found");
|
||||
}
|
||||
grub_dprintf ("btrfs", "extent 0x%" PRIxGRUB_UINT64_T "+0x%"
|
||||
PRIxGRUB_UINT64_T "\n",
|
||||
grub_le_to_cpu64 (key_out.offset),
|
||||
grub_le_to_cpu64 (extent->size));
|
||||
csize = grub_le_to_cpu64 (extent->size)
|
||||
+ grub_le_to_cpu64 (key_out.offset) - pos;
|
||||
if (csize > len)
|
||||
csize = len;
|
||||
|
||||
if (extent->encryption)
|
||||
{
|
||||
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
|
||||
"encryption not supported");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (extent->compression)
|
||||
{
|
||||
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
|
||||
"compression not supported");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
if (extent->encoding)
|
||||
{
|
||||
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
|
||||
"encoding not supported");
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (extent->type)
|
||||
{
|
||||
case GRUB_BTRFS_EXTENT_INLINE:
|
||||
grub_memcpy (buf, extent->inl, csize);
|
||||
grub_free (extent);
|
||||
break;
|
||||
case GRUB_BTRFS_EXTENT_REGULAR:
|
||||
if (!extent->laddr)
|
||||
{
|
||||
grub_memset (buf, 0, csize);
|
||||
break;
|
||||
}
|
||||
err = grub_btrfs_read_logical (data, file->device->disk,
|
||||
grub_le_to_cpu64 (extent->laddr),
|
||||
buf, csize);
|
||||
grub_free (extent);
|
||||
if (err)
|
||||
return -1;
|
||||
break;
|
||||
default:
|
||||
grub_free (extent);
|
||||
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
|
||||
"unsupported extent type 0x%x\n", extent->type);
|
||||
return -1;
|
||||
}
|
||||
buf += csize;
|
||||
pos += csize;
|
||||
len -= csize;
|
||||
}
|
||||
return pos - file->offset;
|
||||
}
|
||||
|
||||
static grub_err_t
|
||||
grub_btrfs_uuid (grub_device_t device, char **uuid)
|
||||
{
|
||||
|
@ -113,12 +918,33 @@ grub_btrfs_uuid (grub_device_t device, char **uuid)
|
|||
return grub_errno;
|
||||
}
|
||||
|
||||
static grub_err_t
|
||||
grub_btrfs_label (grub_device_t device, char **label)
|
||||
{
|
||||
struct grub_btrfs_data *data;
|
||||
|
||||
*label = NULL;
|
||||
|
||||
data = grub_btrfs_mount (device->disk);
|
||||
if (! data)
|
||||
return grub_errno;
|
||||
|
||||
*label = grub_strndup (data->sblock.label, sizeof (data->sblock.label));
|
||||
|
||||
grub_free (data);
|
||||
|
||||
return grub_errno;
|
||||
}
|
||||
|
||||
static struct grub_fs grub_btrfs_fs =
|
||||
{
|
||||
.name = "btrfs",
|
||||
.dir = grub_btrfs_dir,
|
||||
.open = grub_btrfs_open,
|
||||
.read = grub_btrfs_read,
|
||||
.close = grub_btrfs_close,
|
||||
.uuid = grub_btrfs_uuid,
|
||||
.label = grub_btrfs_label,
|
||||
};
|
||||
|
||||
GRUB_MOD_INIT(btrfs)
|
||||
|
|
Loading…
Reference in a new issue