blob: 1d8f7e168fe26dde90ab2482110e3491e835f2e3 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* BTRFS filesystem implementation for U-Boot
*
* 2017 Marek Behun, CZ.NIC, marek.behun@nic.cz
*/
#include "btrfs.h"
#include <log.h>
#include <malloc.h>
#include <memalign.h>
static const struct btrfs_csum {
u16 size;
const char name[14];
} btrfs_csums[] = {
[BTRFS_CSUM_TYPE_CRC32] = { 4, "crc32c" },
[BTRFS_CSUM_TYPE_XXHASH] = { 8, "xxhash64" },
[BTRFS_CSUM_TYPE_SHA256] = { 32, "sha256" },
[BTRFS_CSUM_TYPE_BLAKE2] = { 32, "blake2" },
};
u16 btrfs_super_csum_size(const struct btrfs_super_block *sb)
{
const u16 csum_type = btrfs_super_csum_type(sb);
return btrfs_csums[csum_type].size;
}
const char *btrfs_super_csum_name(u16 csum_type)
{
return btrfs_csums[csum_type].name;
}
size_t btrfs_super_num_csums(void)
{
return ARRAY_SIZE(btrfs_csums);
}
u16 btrfs_csum_type_size(u16 csum_type)
{
return btrfs_csums[csum_type].size;
}
int btrfs_comp_keys(struct btrfs_key *a, struct btrfs_key *b)
{
if (a->objectid > b->objectid)
return 1;
if (a->objectid < b->objectid)
return -1;
if (a->type > b->type)
return 1;
if (a->type < b->type)
return -1;
if (a->offset > b->offset)
return 1;
if (a->offset < b->offset)
return -1;
return 0;
}
int btrfs_comp_keys_type(struct btrfs_key *a, struct btrfs_key *b)
{
if (a->objectid > b->objectid)
return 1;
if (a->objectid < b->objectid)
return -1;
if (a->type > b->type)
return 1;
if (a->type < b->type)
return -1;
return 0;
}
static int generic_bin_search(void *addr, int item_size, struct btrfs_key *key,
int max, int *slot)
{
int low = 0, high = max, mid, ret;
struct btrfs_key *tmp;
while (low < high) {
mid = (low + high) / 2;
tmp = (struct btrfs_key *) ((u8 *) addr + mid*item_size);
ret = btrfs_comp_keys(tmp, key);
if (ret < 0) {
low = mid + 1;
} else if (ret > 0) {
high = mid;
} else {
*slot = mid;
return 0;
}
}
*slot = low;
return 1;
}
int btrfs_bin_search(union btrfs_tree_node *p, struct btrfs_key *key,
int *slot)
{
void *addr;
unsigned long size;
if (p->header.level) {
addr = p->node.ptrs;
size = sizeof(struct btrfs_key_ptr);
} else {
addr = p->leaf.items;
size = sizeof(struct btrfs_item);
}
return generic_bin_search(addr, size, key, p->header.nritems, slot);
}
static void clear_path(struct btrfs_path *p)
{
int i;
for (i = 0; i < BTRFS_MAX_LEVEL; ++i) {
p->nodes[i] = NULL;
p->slots[i] = 0;
}
}
void btrfs_free_path(struct btrfs_path *p)
{
int i;
for (i = 0; i < BTRFS_MAX_LEVEL; ++i) {
if (p->nodes[i])
free(p->nodes[i]);
}
clear_path(p);
}
static int read_tree_node(u64 physical, union btrfs_tree_node **buf)
{
ALLOC_CACHE_ALIGN_BUFFER(struct btrfs_header, hdr,
sizeof(struct btrfs_header));
unsigned long size, offset = sizeof(*hdr);
union btrfs_tree_node *res;
u32 i;
if (!btrfs_devread(physical, sizeof(*hdr), hdr))
return -1;
btrfs_header_to_cpu(hdr);
if (hdr->level)
size = sizeof(struct btrfs_node)
+ hdr->nritems * sizeof(struct btrfs_key_ptr);
else
size = btrfs_info.sb.nodesize;
res = malloc_cache_aligned(size);
if (!res) {
debug("%s: malloc failed\n", __func__);
return -1;
}
if (!btrfs_devread(physical + offset, size - offset,
((u8 *) res) + offset)) {
free(res);
return -1;
}
memcpy(&res->header, hdr, sizeof(*hdr));
if (hdr->level)
for (i = 0; i < hdr->nritems; ++i)
btrfs_key_ptr_to_cpu(&res->node.ptrs[i]);
else
for (i = 0; i < hdr->nritems; ++i)
btrfs_item_to_cpu(&res->leaf.items[i]);
*buf = res;
return 0;
}
int btrfs_search_tree(const struct btrfs_root *root, struct btrfs_key *key,
struct btrfs_path *p)
{
u8 lvl, prev_lvl;
int i, slot, ret;
u64 logical, physical;
union btrfs_tree_node *buf;
clear_path(p);
logical = root->bytenr;
for (i = 0; i < BTRFS_MAX_LEVEL; ++i) {
physical = btrfs_map_logical_to_physical(logical);
if (physical == -1ULL)
goto err;
if (read_tree_node(physical, &buf))
goto err;
lvl = buf->header.level;
if (i && prev_lvl != lvl + 1) {
printf("%s: invalid level in header at %llu\n",
__func__, logical);
goto err;
}
prev_lvl = lvl;
ret = btrfs_bin_search(buf, key, &slot);
if (ret < 0)
goto err;
if (ret && slot > 0 && lvl)
slot -= 1;
p->slots[lvl] = slot;
p->nodes[lvl] = buf;
if (lvl) {
logical = buf->node.ptrs[slot].blockptr;
} else {
/*
* The path might be invalid if:
* cur leaf max < searched value < next leaf min
*
* Jump to the next valid element if it exists.
*/
if (slot >= buf->header.nritems)
if (btrfs_next_slot(p) < 0)
goto err;
break;
}
}
return 0;
err:
btrfs_free_path(p);
return -1;
}
static int jump_leaf(struct btrfs_path *path, int dir)
{
struct btrfs_path p;
u32 slot;
int level = 1, from_level, i;
dir = dir >= 0 ? 1 : -1;
p = *path;
while (level < BTRFS_MAX_LEVEL) {
if (!p.nodes[level])
return 1;
slot = p.slots[level];
if ((dir > 0 && slot + dir >= p.nodes[level]->header.nritems)
|| (dir < 0 && !slot))
level++;
else
break;
}
if (level == BTRFS_MAX_LEVEL)
return 1;
p.slots[level] = slot + dir;
level--;
from_level = level;
while (level >= 0) {
u64 logical, physical;
slot = p.slots[level + 1];
logical = p.nodes[level + 1]->node.ptrs[slot].blockptr;
physical = btrfs_map_logical_to_physical(logical);
if (physical == -1ULL)
goto err;
if (read_tree_node(physical, &p.nodes[level]))
goto err;
if (dir > 0)
p.slots[level] = 0;
else
p.slots[level] = p.nodes[level]->header.nritems - 1;
level--;
}
/* Free rewritten nodes in path */
for (i = 0; i <= from_level; ++i)
free(path->nodes[i]);
*path = p;
return 0;
err:
/* Free rewritten nodes in p */
for (i = level + 1; i <= from_level; ++i)
free(p.nodes[i]);
return -1;
}
int btrfs_prev_slot(struct btrfs_path *p)
{
if (!p->slots[0])
return jump_leaf(p, -1);
p->slots[0]--;
return 0;
}
int btrfs_next_slot(struct btrfs_path *p)
{
struct btrfs_leaf *leaf = &p->nodes[0]->leaf;
if (p->slots[0] + 1 >= leaf->header.nritems)
return jump_leaf(p, 1);
p->slots[0]++;
return 0;
}