695 lines
19 KiB
C
695 lines
19 KiB
C
/*
|
|
* btrfs.c -- readonly btrfs support for syslinux
|
|
* Some data structures are derivated from btrfs-tools-0.19 ctree.h
|
|
* Copyright 2009-2014 Intel Corporation; authors: Alek Du, H. Peter Anvin
|
|
*
|
|
* This program 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, Inc., 53 Temple Place Ste 330,
|
|
* Boston MA 02111-1307, USA; either version 2 of the License, or
|
|
* (at your option) any later version; incorporated herein by reference.
|
|
*
|
|
*/
|
|
|
|
#include <dprintf.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <cache.h>
|
|
#include <core.h>
|
|
#include <disk.h>
|
|
#include <fs.h>
|
|
#include <dirent.h>
|
|
#include <minmax.h>
|
|
#include "btrfs.h"
|
|
|
|
union tree_buf {
|
|
struct btrfs_header header;
|
|
struct btrfs_node node;
|
|
struct btrfs_leaf leaf;
|
|
};
|
|
|
|
/* filesystem instance structure */
|
|
struct btrfs_info {
|
|
u64 fs_tree;
|
|
struct btrfs_super_block sb;
|
|
struct btrfs_chunk_map chunk_map;
|
|
union tree_buf *tree_buf;
|
|
};
|
|
|
|
/* compare function used for bin_search */
|
|
typedef int (*cmp_func)(const void *ptr1, const void *ptr2);
|
|
|
|
/* simple but useful bin search, used for chunk search and btree search */
|
|
static int bin_search(void *ptr, int item_size, void *cmp_item, cmp_func func,
|
|
int min, int max, int *slot)
|
|
{
|
|
int low = min;
|
|
int high = max;
|
|
int mid;
|
|
int ret;
|
|
unsigned long offset;
|
|
void *item;
|
|
|
|
while (low < high) {
|
|
mid = (low + high) / 2;
|
|
offset = mid * item_size;
|
|
|
|
item = ptr + offset;
|
|
ret = func(item, cmp_item);
|
|
|
|
if (ret < 0)
|
|
low = mid + 1;
|
|
else if (ret > 0)
|
|
high = mid;
|
|
else {
|
|
*slot = mid;
|
|
return 0;
|
|
}
|
|
}
|
|
*slot = low;
|
|
return 1;
|
|
}
|
|
|
|
static int btrfs_comp_chunk_map(struct btrfs_chunk_map_item *m1,
|
|
struct btrfs_chunk_map_item *m2)
|
|
{
|
|
if (m1->logical > m2->logical)
|
|
return 1;
|
|
if (m1->logical < m2->logical)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/* insert a new chunk mapping item */
|
|
static void insert_map(struct fs_info *fs, struct btrfs_chunk_map_item *item)
|
|
{
|
|
struct btrfs_info * const bfs = fs->fs_info;
|
|
struct btrfs_chunk_map *chunk_map = &bfs->chunk_map;
|
|
int ret;
|
|
int slot;
|
|
int i;
|
|
|
|
if (chunk_map->map == NULL) { /* first item */
|
|
chunk_map->map_length = BTRFS_MAX_CHUNK_ENTRIES;
|
|
chunk_map->map = malloc(chunk_map->map_length
|
|
* sizeof(chunk_map->map[0]));
|
|
chunk_map->map[0] = *item;
|
|
chunk_map->cur_length = 1;
|
|
return;
|
|
}
|
|
ret = bin_search(chunk_map->map, sizeof(*item), item,
|
|
(cmp_func)btrfs_comp_chunk_map, 0,
|
|
chunk_map->cur_length, &slot);
|
|
if (ret == 0)/* already in map */
|
|
return;
|
|
if (chunk_map->cur_length == BTRFS_MAX_CHUNK_ENTRIES) {
|
|
/* should be impossible */
|
|
printf("too many chunk items\n");
|
|
return;
|
|
}
|
|
for (i = chunk_map->cur_length; i > slot; i--)
|
|
chunk_map->map[i] = chunk_map->map[i-1];
|
|
chunk_map->map[slot] = *item;
|
|
chunk_map->cur_length++;
|
|
}
|
|
|
|
/*
|
|
* from sys_chunk_array or chunk_tree, we can convert a logical address to
|
|
* a physical address we can not support multi device case yet
|
|
*/
|
|
static u64 logical_physical(struct fs_info *fs, u64 logical)
|
|
{
|
|
struct btrfs_info * const bfs = fs->fs_info;
|
|
struct btrfs_chunk_map *chunk_map = &bfs->chunk_map;
|
|
struct btrfs_chunk_map_item item;
|
|
int slot, ret;
|
|
|
|
item.logical = logical;
|
|
ret = bin_search(chunk_map->map, sizeof(chunk_map->map[0]), &item,
|
|
(cmp_func)btrfs_comp_chunk_map, 0,
|
|
chunk_map->cur_length, &slot);
|
|
if (ret == 0)
|
|
slot++;
|
|
else if (slot == 0)
|
|
return -1;
|
|
if (logical >=
|
|
chunk_map->map[slot-1].logical + chunk_map->map[slot-1].length)
|
|
return -1;
|
|
return chunk_map->map[slot-1].physical + logical -
|
|
chunk_map->map[slot-1].logical;
|
|
}
|
|
|
|
/* btrfs has several super block mirrors, need to calculate their location */
|
|
static inline u64 btrfs_sb_offset(int mirror)
|
|
{
|
|
u64 start = 16 * 1024;
|
|
if (mirror)
|
|
return start << (BTRFS_SUPER_MIRROR_SHIFT * mirror);
|
|
return BTRFS_SUPER_INFO_OFFSET;
|
|
}
|
|
|
|
/* find the most recent super block */
|
|
static void btrfs_read_super_block(struct fs_info *fs)
|
|
{
|
|
int i;
|
|
int ret;
|
|
u8 fsid[BTRFS_FSID_SIZE];
|
|
u64 offset;
|
|
u64 transid = 0;
|
|
struct btrfs_super_block buf;
|
|
struct btrfs_info * const bfs = fs->fs_info;
|
|
|
|
bfs->sb.total_bytes = ~0; /* Unknown as of yet */
|
|
|
|
/* find most recent super block */
|
|
for (i = 0; i < BTRFS_SUPER_MIRROR_MAX; i++) {
|
|
offset = btrfs_sb_offset(i);
|
|
if (offset >= bfs->sb.total_bytes)
|
|
break;
|
|
|
|
ret = cache_read(fs, (char *)&buf, offset, sizeof(buf));
|
|
if (ret < sizeof(buf))
|
|
break;
|
|
|
|
if (buf.bytenr != offset ||
|
|
strncmp((char *)(&buf.magic), BTRFS_MAGIC,
|
|
sizeof(buf.magic)))
|
|
continue;
|
|
|
|
if (i == 0)
|
|
memcpy(fsid, buf.fsid, sizeof(fsid));
|
|
else if (memcmp(fsid, buf.fsid, sizeof(fsid)))
|
|
continue;
|
|
|
|
if (buf.generation > transid) {
|
|
memcpy(&bfs->sb, &buf, sizeof(bfs->sb));
|
|
transid = buf.generation;
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline unsigned long btrfs_chunk_item_size(int num_stripes)
|
|
{
|
|
return sizeof(struct btrfs_chunk) +
|
|
sizeof(struct btrfs_stripe) * (num_stripes - 1);
|
|
}
|
|
|
|
static void clear_path(struct btrfs_path *path)
|
|
{
|
|
memset(path, 0, sizeof(*path));
|
|
}
|
|
|
|
static int btrfs_comp_keys(const struct btrfs_disk_key *k1,
|
|
const struct btrfs_disk_key *k2)
|
|
{
|
|
if (k1->objectid > k2->objectid)
|
|
return 1;
|
|
if (k1->objectid < k2->objectid)
|
|
return -1;
|
|
if (k1->type > k2->type)
|
|
return 1;
|
|
if (k1->type < k2->type)
|
|
return -1;
|
|
if (k1->offset > k2->offset)
|
|
return 1;
|
|
if (k1->offset < k2->offset)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/* compare keys but ignore offset, is useful to enumerate all same kind keys */
|
|
static int btrfs_comp_keys_type(const struct btrfs_disk_key *k1,
|
|
const struct btrfs_disk_key *k2)
|
|
{
|
|
if (k1->objectid > k2->objectid)
|
|
return 1;
|
|
if (k1->objectid < k2->objectid)
|
|
return -1;
|
|
if (k1->type > k2->type)
|
|
return 1;
|
|
if (k1->type < k2->type)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/* seach tree directly on disk ... */
|
|
static int search_tree(struct fs_info *fs, u64 loffset,
|
|
struct btrfs_disk_key *key, struct btrfs_path *path)
|
|
{
|
|
struct btrfs_info * const bfs = fs->fs_info;
|
|
union tree_buf *tree_buf = bfs->tree_buf;
|
|
int slot, ret;
|
|
u64 offset;
|
|
|
|
offset = logical_physical(fs, loffset);
|
|
cache_read(fs, &tree_buf->header, offset, sizeof(tree_buf->header));
|
|
if (tree_buf->header.level) {
|
|
/* inner node */
|
|
cache_read(fs, (char *)&tree_buf->node.ptrs[0],
|
|
offset + sizeof tree_buf->header,
|
|
bfs->sb.nodesize - sizeof tree_buf->header);
|
|
path->itemsnr[tree_buf->header.level] = tree_buf->header.nritems;
|
|
path->offsets[tree_buf->header.level] = loffset;
|
|
ret = bin_search(&tree_buf->node.ptrs[0],
|
|
sizeof(struct btrfs_key_ptr),
|
|
key, (cmp_func)btrfs_comp_keys,
|
|
path->slots[tree_buf->header.level],
|
|
tree_buf->header.nritems, &slot);
|
|
if (ret && slot > path->slots[tree_buf->header.level])
|
|
slot--;
|
|
path->slots[tree_buf->header.level] = slot;
|
|
ret = search_tree(fs, tree_buf->node.ptrs[slot].blockptr,
|
|
key, path);
|
|
} else {
|
|
/* leaf node */
|
|
cache_read(fs, (char *)&tree_buf->leaf.items[0],
|
|
offset + sizeof tree_buf->header,
|
|
bfs->sb.leafsize - sizeof tree_buf->header);
|
|
path->itemsnr[tree_buf->header.level] = tree_buf->header.nritems;
|
|
path->offsets[tree_buf->header.level] = loffset;
|
|
ret = bin_search(&tree_buf->leaf.items[0],
|
|
sizeof(struct btrfs_item),
|
|
key, (cmp_func)btrfs_comp_keys,
|
|
path->slots[0],
|
|
tree_buf->header.nritems, &slot);
|
|
if (ret && slot > path->slots[tree_buf->header.level])
|
|
slot--;
|
|
path->slots[tree_buf->header.level] = slot;
|
|
path->item = tree_buf->leaf.items[slot];
|
|
cache_read(fs, (char *)&path->data,
|
|
offset + sizeof tree_buf->header +
|
|
tree_buf->leaf.items[slot].offset,
|
|
tree_buf->leaf.items[slot].size);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* return 0 if leaf found */
|
|
static int next_leaf(struct fs_info *fs, struct btrfs_disk_key *key, struct btrfs_path *path)
|
|
{
|
|
int slot;
|
|
int level = 1;
|
|
|
|
while (level < BTRFS_MAX_LEVEL) {
|
|
if (!path->itemsnr[level]) /* no more nodes */
|
|
return 1;
|
|
slot = path->slots[level] + 1;
|
|
if (slot >= path->itemsnr[level]) {
|
|
level++;
|
|
continue;;
|
|
}
|
|
path->slots[level] = slot;
|
|
path->slots[level-1] = 0; /* reset low level slots info */
|
|
search_tree(fs, path->offsets[level], key, path);
|
|
break;
|
|
}
|
|
if (level == BTRFS_MAX_LEVEL)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* return 0 if slot found */
|
|
static int next_slot(struct fs_info *fs, struct btrfs_disk_key *key,
|
|
struct btrfs_path *path)
|
|
{
|
|
int slot;
|
|
|
|
if (!path->itemsnr[0])
|
|
return 1;
|
|
slot = path->slots[0] + 1;
|
|
if (slot >= path->itemsnr[0])
|
|
return 1;
|
|
path->slots[0] = slot;
|
|
search_tree(fs, path->offsets[0], key, path);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* read chunk_array in super block
|
|
*/
|
|
static void btrfs_read_sys_chunk_array(struct fs_info *fs)
|
|
{
|
|
struct btrfs_info * const bfs = fs->fs_info;
|
|
struct btrfs_chunk_map_item item;
|
|
struct btrfs_disk_key *key;
|
|
struct btrfs_chunk *chunk;
|
|
int cur;
|
|
|
|
/* read chunk array in superblock */
|
|
cur = 0;
|
|
while (cur < bfs->sb.sys_chunk_array_size) {
|
|
key = (struct btrfs_disk_key *)(bfs->sb.sys_chunk_array + cur);
|
|
cur += sizeof(*key);
|
|
chunk = (struct btrfs_chunk *)(bfs->sb.sys_chunk_array + cur);
|
|
cur += btrfs_chunk_item_size(chunk->num_stripes);
|
|
/* insert to mapping table, ignore multi stripes */
|
|
item.logical = key->offset;
|
|
item.length = chunk->length;
|
|
item.devid = chunk->stripe.devid;
|
|
item.physical = chunk->stripe.offset;/*ignore other stripes */
|
|
insert_map(fs, &item);
|
|
}
|
|
}
|
|
|
|
/* read chunk items from chunk_tree and insert them to chunk map */
|
|
static void btrfs_read_chunk_tree(struct fs_info *fs)
|
|
{
|
|
struct btrfs_info * const bfs = fs->fs_info;
|
|
struct btrfs_disk_key search_key;
|
|
struct btrfs_chunk *chunk;
|
|
struct btrfs_chunk_map_item item;
|
|
struct btrfs_path path;
|
|
|
|
if (!(bfs->sb.flags & BTRFS_SUPER_FLAG_METADUMP)) {
|
|
if (bfs->sb.num_devices > 1)
|
|
printf("warning: only support single device btrfs\n");
|
|
/* read chunk from chunk_tree */
|
|
search_key.objectid = BTRFS_FIRST_CHUNK_TREE_OBJECTID;
|
|
search_key.type = BTRFS_CHUNK_ITEM_KEY;
|
|
search_key.offset = 0;
|
|
clear_path(&path);
|
|
search_tree(fs, bfs->sb.chunk_root, &search_key, &path);
|
|
do {
|
|
do {
|
|
if (btrfs_comp_keys_type(&search_key,
|
|
&path.item.key))
|
|
break;
|
|
chunk = (struct btrfs_chunk *)(path.data);
|
|
/* insert to mapping table, ignore stripes */
|
|
item.logical = path.item.key.offset;
|
|
item.length = chunk->length;
|
|
item.devid = chunk->stripe.devid;
|
|
item.physical = chunk->stripe.offset;
|
|
insert_map(fs, &item);
|
|
} while (!next_slot(fs, &search_key, &path));
|
|
if (btrfs_comp_keys_type(&search_key, &path.item.key))
|
|
break;
|
|
} while (!next_leaf(fs, &search_key, &path));
|
|
}
|
|
}
|
|
|
|
static inline u64 btrfs_name_hash(const char *name, int len)
|
|
{
|
|
return btrfs_crc32c((u32)~1, name, len);
|
|
}
|
|
|
|
static struct inode *btrfs_iget_by_inr(struct fs_info *fs, u64 inr)
|
|
{
|
|
struct btrfs_info * const bfs = fs->fs_info;
|
|
struct inode *inode;
|
|
struct btrfs_inode_item inode_item;
|
|
struct btrfs_disk_key search_key;
|
|
struct btrfs_path path;
|
|
int ret;
|
|
|
|
/* FIXME: some BTRFS inode member are u64, while our logical inode
|
|
is u32, we may need change them to u64 later */
|
|
search_key.objectid = inr;
|
|
search_key.type = BTRFS_INODE_ITEM_KEY;
|
|
search_key.offset = 0;
|
|
clear_path(&path);
|
|
ret = search_tree(fs, bfs->fs_tree, &search_key, &path);
|
|
if (ret)
|
|
return NULL;
|
|
inode_item = *(struct btrfs_inode_item *)path.data;
|
|
if (!(inode = alloc_inode(fs, inr, sizeof(struct btrfs_pvt_inode))))
|
|
return NULL;
|
|
inode->ino = inr;
|
|
inode->size = inode_item.size;
|
|
inode->mode = IFTODT(inode_item.mode);
|
|
|
|
if (inode->mode == DT_REG || inode->mode == DT_LNK) {
|
|
struct btrfs_file_extent_item extent_item;
|
|
u64 offset;
|
|
|
|
/* get file_extent_item */
|
|
search_key.type = BTRFS_EXTENT_DATA_KEY;
|
|
search_key.offset = 0;
|
|
clear_path(&path);
|
|
ret = search_tree(fs, bfs->fs_tree, &search_key, &path);
|
|
if (ret)
|
|
return NULL; /* impossible */
|
|
extent_item = *(struct btrfs_file_extent_item *)path.data;
|
|
if (extent_item.type == BTRFS_FILE_EXTENT_INLINE)/* inline file */
|
|
offset = path.offsets[0] + sizeof(struct btrfs_header)
|
|
+ path.item.offset
|
|
+ offsetof(struct btrfs_file_extent_item, disk_bytenr);
|
|
else
|
|
offset = extent_item.disk_bytenr;
|
|
PVT(inode)->offset = offset;
|
|
}
|
|
return inode;
|
|
}
|
|
|
|
static struct inode *btrfs_iget_root(struct fs_info *fs)
|
|
{
|
|
/* BTRFS_FIRST_CHUNK_TREE_OBJECTID(256) actually is first OBJECTID for FS_TREE */
|
|
return btrfs_iget_by_inr(fs, BTRFS_FIRST_CHUNK_TREE_OBJECTID);
|
|
}
|
|
|
|
static struct inode *btrfs_iget(const char *name, struct inode *parent)
|
|
{
|
|
struct fs_info * const fs = parent->fs;
|
|
struct btrfs_info * const bfs = fs->fs_info;
|
|
struct btrfs_disk_key search_key;
|
|
struct btrfs_path path;
|
|
struct btrfs_dir_item dir_item;
|
|
int ret;
|
|
|
|
search_key.objectid = parent->ino;
|
|
search_key.type = BTRFS_DIR_ITEM_KEY;
|
|
search_key.offset = btrfs_name_hash(name, strlen(name));
|
|
clear_path(&path);
|
|
ret = search_tree(fs, bfs->fs_tree, &search_key, &path);
|
|
if (ret)
|
|
return NULL;
|
|
dir_item = *(struct btrfs_dir_item *)path.data;
|
|
|
|
return btrfs_iget_by_inr(fs, dir_item.location.objectid);
|
|
}
|
|
|
|
static int btrfs_readlink(struct inode *inode, char *buf)
|
|
{
|
|
cache_read(inode->fs, buf,
|
|
logical_physical(inode->fs, PVT(inode)->offset),
|
|
inode->size);
|
|
buf[inode->size] = '\0';
|
|
return inode->size;
|
|
}
|
|
|
|
static int btrfs_readdir(struct file *file, struct dirent *dirent)
|
|
{
|
|
struct fs_info * const fs = file->fs;
|
|
struct btrfs_info * const bfs = fs->fs_info;
|
|
struct inode * const inode = file->inode;
|
|
struct btrfs_disk_key search_key;
|
|
struct btrfs_path path;
|
|
struct btrfs_dir_item *dir_item;
|
|
int ret;
|
|
|
|
/*
|
|
* we use file->offset to store last search key.offset, will will search
|
|
* key that lower that offset, 0 means first search and we will search
|
|
* -1UL, which is the biggest possible key
|
|
*/
|
|
search_key.objectid = inode->ino;
|
|
search_key.type = BTRFS_DIR_ITEM_KEY;
|
|
search_key.offset = file->offset - 1;
|
|
clear_path(&path);
|
|
ret = search_tree(fs, bfs->fs_tree, &search_key, &path);
|
|
|
|
if (ret) {
|
|
if (btrfs_comp_keys_type(&search_key, &path.item.key))
|
|
return -1;
|
|
}
|
|
|
|
dir_item = (struct btrfs_dir_item *)path.data;
|
|
file->offset = path.item.key.offset;
|
|
dirent->d_ino = dir_item->location.objectid;
|
|
dirent->d_off = file->offset;
|
|
dirent->d_reclen = offsetof(struct dirent, d_name)
|
|
+ dir_item->name_len + 1;
|
|
dirent->d_type = IFTODT(dir_item->type);
|
|
memcpy(dirent->d_name, dir_item + 1, dir_item->name_len);
|
|
dirent->d_name[dir_item->name_len] = '\0';
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int btrfs_next_extent(struct inode *inode, uint32_t lstart)
|
|
{
|
|
struct btrfs_disk_key search_key;
|
|
struct btrfs_file_extent_item extent_item;
|
|
struct btrfs_path path;
|
|
int ret;
|
|
u64 offset;
|
|
struct fs_info * const fs = inode->fs;
|
|
struct btrfs_info * const bfs = fs->fs_info;
|
|
u32 sec_shift = SECTOR_SHIFT(fs);
|
|
u32 sec_size = SECTOR_SIZE(fs);
|
|
|
|
search_key.objectid = inode->ino;
|
|
search_key.type = BTRFS_EXTENT_DATA_KEY;
|
|
search_key.offset = lstart << sec_shift;
|
|
clear_path(&path);
|
|
ret = search_tree(fs, bfs->fs_tree, &search_key, &path);
|
|
if (ret) { /* impossible */
|
|
printf("btrfs: search extent data error!\n");
|
|
return -1;
|
|
}
|
|
extent_item = *(struct btrfs_file_extent_item *)path.data;
|
|
|
|
if (extent_item.encryption) {
|
|
printf("btrfs: found encrypted data, cannot continue!\n");
|
|
return -1;
|
|
}
|
|
if (extent_item.compression) {
|
|
printf("btrfs: found compressed data, cannot continue!\n");
|
|
return -1;
|
|
}
|
|
|
|
if (extent_item.type == BTRFS_FILE_EXTENT_INLINE) {/* inline file */
|
|
/* we fake a extent here, and PVT of inode will tell us */
|
|
offset = path.offsets[0] + sizeof(struct btrfs_header)
|
|
+ path.item.offset
|
|
+ offsetof(struct btrfs_file_extent_item, disk_bytenr);
|
|
inode->next_extent.len =
|
|
(inode->size + sec_size -1) >> sec_shift;
|
|
} else {
|
|
offset = extent_item.disk_bytenr + extent_item.offset;
|
|
inode->next_extent.len =
|
|
(extent_item.num_bytes + sec_size - 1) >> sec_shift;
|
|
}
|
|
inode->next_extent.pstart = logical_physical(fs, offset) >> sec_shift;
|
|
PVT(inode)->offset = offset;
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t btrfs_getfssec(struct file *file, char *buf, int sectors,
|
|
bool *have_more)
|
|
{
|
|
u32 ret;
|
|
struct fs_info *fs = file->fs;
|
|
u32 off = PVT(file->inode)->offset % SECTOR_SIZE(fs);
|
|
bool handle_inline = false;
|
|
|
|
if (off && !file->offset) {/* inline file first read patch */
|
|
file->inode->size += off;
|
|
handle_inline = true;
|
|
}
|
|
ret = generic_getfssec(file, buf, sectors, have_more);
|
|
if (!ret)
|
|
return ret;
|
|
off = PVT(file->inode)->offset % SECTOR_SIZE(fs);
|
|
if (handle_inline) {/* inline file patch */
|
|
ret -= off;
|
|
memcpy(buf, buf + off, ret);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void btrfs_get_fs_tree(struct fs_info *fs)
|
|
{
|
|
struct btrfs_info * const bfs = fs->fs_info;
|
|
struct btrfs_disk_key search_key;
|
|
struct btrfs_path path;
|
|
struct btrfs_root_item *tree;
|
|
bool subvol_ok = false;
|
|
|
|
/* check if subvol is filled by installer */
|
|
if (*SubvolName) {
|
|
search_key.objectid = BTRFS_FS_TREE_OBJECTID;
|
|
search_key.type = BTRFS_ROOT_REF_KEY;
|
|
search_key.offset = 0;
|
|
clear_path(&path);
|
|
if (search_tree(fs, bfs->sb.root, &search_key, &path))
|
|
next_slot(fs, &search_key, &path);
|
|
do {
|
|
do {
|
|
struct btrfs_root_ref *ref;
|
|
int pathlen;
|
|
|
|
if (btrfs_comp_keys_type(&search_key,
|
|
&path.item.key))
|
|
break;
|
|
ref = (struct btrfs_root_ref *)path.data;
|
|
pathlen = path.item.size - sizeof(struct btrfs_root_ref);
|
|
|
|
if (!strncmp((char*)(ref + 1), SubvolName, pathlen)) {
|
|
subvol_ok = true;
|
|
break;
|
|
}
|
|
} while (!next_slot(fs, &search_key, &path));
|
|
if (subvol_ok)
|
|
break;
|
|
if (btrfs_comp_keys_type(&search_key, &path.item.key))
|
|
break;
|
|
} while (!next_leaf(fs, &search_key, &path));
|
|
if (!subvol_ok) /* should be impossible */
|
|
printf("no subvol found!\n");
|
|
}
|
|
/* find fs_tree from tree_root */
|
|
if (subvol_ok)
|
|
search_key.objectid = path.item.key.offset;
|
|
else /* "default" volume */
|
|
search_key.objectid = BTRFS_FS_TREE_OBJECTID;
|
|
search_key.type = BTRFS_ROOT_ITEM_KEY;
|
|
search_key.offset = -1;
|
|
clear_path(&path);
|
|
search_tree(fs, bfs->sb.root, &search_key, &path);
|
|
tree = (struct btrfs_root_item *)path.data;
|
|
bfs->fs_tree = tree->bytenr;
|
|
}
|
|
|
|
/* init. the fs meta data, return the block size shift bits. */
|
|
static int btrfs_fs_init(struct fs_info *fs)
|
|
{
|
|
struct disk *disk = fs->fs_dev->disk;
|
|
struct btrfs_info *bfs;
|
|
|
|
btrfs_init_crc32c();
|
|
|
|
bfs = zalloc(sizeof(struct btrfs_info));
|
|
if (!bfs)
|
|
return -1;
|
|
|
|
fs->fs_info = bfs;
|
|
|
|
fs->sector_shift = disk->sector_shift;
|
|
fs->sector_size = 1 << fs->sector_shift;
|
|
fs->block_shift = BTRFS_BLOCK_SHIFT;
|
|
fs->block_size = 1 << fs->block_shift;
|
|
|
|
/* Initialize the block cache */
|
|
cache_init(fs->fs_dev, fs->block_shift);
|
|
|
|
btrfs_read_super_block(fs);
|
|
if (bfs->sb.magic != BTRFS_MAGIC_N)
|
|
return -1;
|
|
bfs->tree_buf = malloc(max(bfs->sb.nodesize, bfs->sb.leafsize));
|
|
if (!bfs->tree_buf)
|
|
return -1;
|
|
btrfs_read_sys_chunk_array(fs);
|
|
btrfs_read_chunk_tree(fs);
|
|
btrfs_get_fs_tree(fs);
|
|
|
|
return fs->block_shift;
|
|
}
|
|
|
|
const struct fs_ops btrfs_fs_ops = {
|
|
.fs_name = "btrfs",
|
|
.fs_flags = 0,
|
|
.fs_init = btrfs_fs_init,
|
|
.iget_root = btrfs_iget_root,
|
|
.iget = btrfs_iget,
|
|
.readlink = btrfs_readlink,
|
|
.getfssec = btrfs_getfssec,
|
|
.close_file = generic_close_file,
|
|
.mangle_name = generic_mangle_name,
|
|
.next_extent = btrfs_next_extent,
|
|
.readdir = btrfs_readdir,
|
|
.chdir_start = generic_chdir_start,
|
|
.open_config = generic_open_config,
|
|
.fs_uuid = NULL,
|
|
};
|