mirror of
https://github.com/linux-apfs/apfsprogs.git
synced 2026-08-18 02:19:50 -07:00
2094 lines
61 KiB
C
2094 lines
61 KiB
C
/*
|
|
* Copyright (C) 2019 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <apfs/raw.h>
|
|
#include <apfs/sha256.h>
|
|
#include <apfs/types.h>
|
|
#include "apfsck.h"
|
|
#include "btree.h"
|
|
#include "dir.h"
|
|
#include "extents.h"
|
|
#include "htable.h"
|
|
#include "inode.h"
|
|
#include "key.h"
|
|
#include "object.h"
|
|
#include "snapshot.h"
|
|
#include "spaceman.h"
|
|
#include "super.h"
|
|
#include "xattr.h"
|
|
|
|
bool ongoing_query;
|
|
|
|
/**
|
|
* node_min_table_size - Return the minimum size for a node's table of contents
|
|
* @node: the node
|
|
*/
|
|
static int node_min_table_size(struct node *node)
|
|
{
|
|
u32 type = node->object.subtype;
|
|
bool leaf = (node->flags) & APFS_BTNODE_LEAF;
|
|
int key_size, val_size, toc_size;
|
|
int space, count;
|
|
|
|
/* Trees with fixed key/value sizes preallocate the whole table */
|
|
switch (type) {
|
|
case APFS_OBJECT_TYPE_OMAP:
|
|
key_size = sizeof(struct apfs_omap_key);
|
|
val_size = leaf ? sizeof(struct apfs_omap_val) : sizeof(__le64);
|
|
toc_size = sizeof(struct apfs_kvoff);
|
|
break;
|
|
case APFS_OBJECT_TYPE_SPACEMAN_FREE_QUEUE:
|
|
key_size = sizeof(struct apfs_spaceman_free_queue_key);
|
|
val_size = sizeof(__le64); /* We assume no ghosts here */
|
|
toc_size = sizeof(struct apfs_kvoff);
|
|
break;
|
|
case APFS_OBJECT_TYPE_OMAP_SNAPSHOT:
|
|
key_size = sizeof(__le64);
|
|
val_size = leaf ? sizeof(struct apfs_omap_snapshot) : sizeof(__le64);
|
|
toc_size = sizeof(struct apfs_kvoff);
|
|
break;
|
|
case APFS_OBJECT_TYPE_FEXT_TREE:
|
|
key_size = sizeof(struct apfs_fext_tree_key);
|
|
val_size = leaf ? sizeof(struct apfs_fext_tree_val) : sizeof(__le64);
|
|
toc_size = sizeof(struct apfs_kvoff);
|
|
break;
|
|
case APFS_OBJECT_TYPE_FUSION_MIDDLE_TREE:
|
|
key_size = sizeof(struct apfs_fusion_mt_key);
|
|
val_size = leaf ? sizeof(struct apfs_fusion_mt_val) : sizeof(__le64);
|
|
toc_size = sizeof(struct apfs_kvoff);
|
|
break;
|
|
default:
|
|
/* It should at least have room for one record */
|
|
return sizeof(struct apfs_kvloc);
|
|
}
|
|
|
|
/* The footer of root nodes is ignored for some reason */
|
|
space = sb->s_blocksize - sizeof(struct apfs_btree_node_phys);
|
|
count = space / (key_size + val_size + toc_size);
|
|
return count * toc_size;
|
|
}
|
|
|
|
/**
|
|
* node_is_valid - Check basic sanity of the node index
|
|
* @node: node to check
|
|
*/
|
|
static bool node_is_valid(struct node *node)
|
|
{
|
|
u16 flags = node->flags;
|
|
int records = node->records;
|
|
int index_size = node->key - node->toc;
|
|
int entry_size, min_index_size;
|
|
|
|
if ((flags & APFS_BTNODE_MASK) != flags)
|
|
return false;
|
|
|
|
if (!node_is_root(node) && !records)
|
|
return false; /* Empty children should just be deleted */
|
|
|
|
if (node->toc != sizeof(struct apfs_btree_node_phys))
|
|
return false; /* The table of contents follows the header */
|
|
|
|
if (node->data > sb->s_blocksize -
|
|
(node_is_root(node) ? sizeof(struct apfs_btree_info) : 0))
|
|
return false; /* The value area must start before it ends... */
|
|
|
|
entry_size = (node_has_fixed_kv_size(node)) ?
|
|
sizeof(struct apfs_kvoff) : sizeof(struct apfs_kvloc);
|
|
|
|
min_index_size = node_min_table_size(node);
|
|
if (index_size < min_index_size)
|
|
return false;
|
|
if (node_has_fixed_kv_size(node) && index_size != min_index_size) {
|
|
/*
|
|
* Free queue nodes have ghost records, which makes no sense if
|
|
* their index is not allowed to grow bigger.
|
|
*/
|
|
if (node->object.subtype != APFS_OBJECT_TYPE_SPACEMAN_FREE_QUEUE)
|
|
return false;
|
|
}
|
|
|
|
/* All records must have an entry in the table of contents */
|
|
return records * entry_size <= index_size;
|
|
}
|
|
|
|
/**
|
|
* node_parse_key_free_list - Parse a node's free key list into a bitmap
|
|
* @node: the node to parse
|
|
*
|
|
* The bitmap will be set in @node->free_key_bmap.
|
|
*/
|
|
static void node_parse_key_free_list(struct node *node)
|
|
{
|
|
struct apfs_nloc *free = &node->raw->btn_key_free_list;
|
|
void *area_raw = (void *)node->raw + node->key;
|
|
int area_len = node->free - node->key;
|
|
int total = le16_to_cpu(free->len);
|
|
int off;
|
|
|
|
/* Each bit represents a byte in the key area */
|
|
node->free_key_bmap = malloc(DIV_ROUND_UP(area_len, 8));
|
|
if (!node->free_key_bmap)
|
|
system_error();
|
|
memset(node->free_key_bmap, 0xFF, DIV_ROUND_UP(area_len, 8));
|
|
|
|
off = le16_to_cpu(free->off);
|
|
while (total > 0) {
|
|
int len, i;
|
|
|
|
/* Tiny free areas may not be in the list */
|
|
if (off == APFS_BTOFF_INVALID)
|
|
break;
|
|
|
|
if (off + sizeof(*free) > area_len)
|
|
report("B-tree node",
|
|
"no room for free list entry in key area.");
|
|
|
|
free = area_raw + off;
|
|
len = le16_to_cpu(free->len);
|
|
if (len < sizeof(*free))
|
|
report("B-tree node", "free key is too small.");
|
|
|
|
if (off + len > area_len)
|
|
report("B-tree node", "free key is out-of-bounds.");
|
|
|
|
for (i = off; i < off + len; ++i) {
|
|
u8 *byte = node->free_key_bmap + i / 8;
|
|
u8 flag = 1 << i % 8;
|
|
|
|
if (!(*byte & flag))
|
|
report("B-tree node",
|
|
"byte listed twice in free key list.");
|
|
*byte ^= flag;
|
|
}
|
|
total -= len;
|
|
|
|
off = le16_to_cpu(free->off);
|
|
}
|
|
|
|
if (off != APFS_BTOFF_INVALID)
|
|
report("B-tree node", "bad last key in free list.");
|
|
}
|
|
|
|
/**
|
|
* node_parse_val_free_list - Parse a node's free value list into a bitmap
|
|
* @node: the node to parse
|
|
*
|
|
* The bitmap will be set in @node->free_val_bmap.
|
|
*/
|
|
static void node_parse_val_free_list(struct node *node)
|
|
{
|
|
struct apfs_nloc *free = &node->raw->btn_val_free_list;
|
|
void *end_raw = node->raw;
|
|
int area_len;
|
|
int total = le16_to_cpu(free->len);
|
|
int off;
|
|
|
|
/* Only the root has a footer */
|
|
area_len = sb->s_blocksize - node->data -
|
|
(node_is_root(node) ? sizeof(struct apfs_btree_info) : 0);
|
|
end_raw = (void *)node->raw + node->data + area_len;
|
|
|
|
/* Each bit represents a byte in the value area */
|
|
node->free_val_bmap = malloc(DIV_ROUND_UP(area_len, 8));
|
|
if (!node->free_val_bmap)
|
|
system_error();
|
|
memset(node->free_val_bmap, 0xFF, DIV_ROUND_UP(area_len, 8));
|
|
|
|
off = le16_to_cpu(free->off);
|
|
while (total > 0) {
|
|
int len, i;
|
|
|
|
/* Tiny free areas may not be in the list */
|
|
if (off == APFS_BTOFF_INVALID)
|
|
break;
|
|
|
|
if (off < sizeof(*free))
|
|
report("B-tree node",
|
|
"no room for free list entry in value area.");
|
|
|
|
free = end_raw - off;
|
|
len = le16_to_cpu(free->len);
|
|
if (len < sizeof(*free))
|
|
report("B-tree node", "free value is too small.");
|
|
|
|
if (area_len < off || len > off)
|
|
report("B-tree node", "free value is out-of-bounds.");
|
|
|
|
for (i = area_len - off; i < area_len - off + len; ++i) {
|
|
u8 *byte = node->free_val_bmap + i / 8;
|
|
u8 flag = 1 << i % 8;
|
|
|
|
if (!(*byte & flag))
|
|
report("B-tree node",
|
|
"byte listed twice in free value list.");
|
|
*byte ^= flag;
|
|
}
|
|
total -= len;
|
|
|
|
off = le16_to_cpu(free->off);
|
|
}
|
|
|
|
if (off != APFS_BTOFF_INVALID)
|
|
report("B-tree node", "bad last value in free list.");
|
|
}
|
|
|
|
/**
|
|
* node_prepare_bitmaps - Do the basic setup of the nodes allocation bitmaps
|
|
* @node: the node to parse
|
|
*
|
|
* The @node->free_key_bmap and @free_val_bmap bitmaps will be set entirely
|
|
* from the information in the linked lists. The @node->used_key_bmap and
|
|
* @node->used_val_bmap will only be allocated, to be set later when parsing
|
|
* the keys.
|
|
*
|
|
* TODO: should we check that the free space in the node is zeroed?
|
|
*/
|
|
static void node_prepare_bitmaps(struct node *node)
|
|
{
|
|
int keys_len;
|
|
int values_len;
|
|
|
|
assert(node->raw);
|
|
assert(!node->free_key_bmap);
|
|
assert(!node->free_val_bmap);
|
|
|
|
keys_len = node->free - node->key;
|
|
|
|
/* Each bit represents a byte in the key area */
|
|
node->used_key_bmap = calloc(1, DIV_ROUND_UP(keys_len, 8));
|
|
if (!node->used_key_bmap)
|
|
system_error();
|
|
|
|
/* Only the root has a footer */
|
|
values_len = sb->s_blocksize - node->data -
|
|
(node_is_root(node) ? sizeof(struct apfs_btree_info) : 0);
|
|
|
|
/* Each bit represents a byte in the value area */
|
|
node->used_val_bmap = calloc(1, DIV_ROUND_UP(values_len, 8));
|
|
if (!node->used_val_bmap)
|
|
system_error();
|
|
|
|
node_parse_key_free_list(node);
|
|
node_parse_val_free_list(node);
|
|
}
|
|
|
|
/**
|
|
* verify_block_hash - Verify that a block has the given hash
|
|
* @raw: pointer to the block contents
|
|
* @hash: pointer to the SHA-256 hash
|
|
*/
|
|
static void verify_block_hash(u8 *raw, u8 *hash)
|
|
{
|
|
SHA256_CTX ctx = {0};
|
|
u8 true_hash[APFS_HASH_CCSHA256_SIZE] = {0};
|
|
|
|
if (!hash)
|
|
return;
|
|
|
|
sha256_init(&ctx);
|
|
sha256_update(&ctx, raw, sb->s_blocksize);
|
|
sha256_final(&ctx, true_hash);
|
|
|
|
if (memcmp(hash, true_hash, APFS_HASH_CCSHA256_SIZE) != 0)
|
|
report("Sealed volume", "incorrect hash for node.");
|
|
}
|
|
|
|
/**
|
|
* read_node - Read a node header from disk
|
|
* @oid: object id for the node
|
|
* @btree: tree structure, with the omap_table already set
|
|
* @hash: SHA-256 hash to check against (NULL if none)
|
|
*
|
|
* Returns a pointer to the resulting node structure.
|
|
*/
|
|
static struct node *read_node(u64 oid, struct btree *btree, u8 *hash)
|
|
{
|
|
struct apfs_btree_node_phys *raw;
|
|
struct node *node;
|
|
u32 obj_type, obj_subtype;
|
|
bool noheader;
|
|
|
|
node = calloc(1, sizeof(*node));
|
|
if (!node)
|
|
system_error();
|
|
node->btree = btree;
|
|
|
|
noheader = btree_is_catalog(btree) && apfs_volume_is_sealed();
|
|
|
|
/* The free-space queue is the only tree with ephemeral nodes so far */
|
|
if (btree_is_free_queue(btree))
|
|
raw = read_ephemeral_object(oid, &node->object);
|
|
else if (noheader)
|
|
raw = read_object_noheader(oid, btree->omap_table, &node->object);
|
|
else
|
|
raw = read_object(oid, btree->omap_table, &node->object);
|
|
node->raw = raw;
|
|
|
|
if (hash)
|
|
verify_block_hash((u8 *)raw, hash);
|
|
|
|
node->level = le16_to_cpu(raw->btn_level);
|
|
node->flags = le16_to_cpu(raw->btn_flags);
|
|
node->records = le32_to_cpu(raw->btn_nkeys);
|
|
node->toc = sizeof(*raw) + le16_to_cpu(raw->btn_table_space.off);
|
|
node->key = node->toc + le16_to_cpu(raw->btn_table_space.len);
|
|
node->free = node->key + le16_to_cpu(raw->btn_free_space.off);
|
|
node->data = node->free + le16_to_cpu(raw->btn_free_space.len);
|
|
|
|
if (!node_is_valid(node)) {
|
|
report("B-tree node", "block 0x%llx is not sane.",
|
|
(unsigned long long)node->object.block_nr);
|
|
}
|
|
|
|
if ((bool)(node->flags & APFS_BTNODE_NOHEADER) != noheader)
|
|
report("B-tree node", "wrong setting of hashed flag.");
|
|
if ((bool)(node->flags & APFS_BTNODE_HASHED) != noheader)
|
|
report("B-tree node", "wrong setting of hashed flag.");
|
|
|
|
obj_type = node->object.type;
|
|
if (!noheader && node_is_root(node) && obj_type != APFS_OBJECT_TYPE_BTREE)
|
|
report("B-tree node", "wrong object type for root.");
|
|
if (!noheader && !node_is_root(node) && obj_type != APFS_OBJECT_TYPE_BTREE_NODE)
|
|
report("B-tree node", "wrong object type for nonroot.");
|
|
|
|
obj_subtype = node->object.subtype;
|
|
if (btree_is_omap(btree) && obj_subtype != APFS_OBJECT_TYPE_OMAP)
|
|
report("Object map node", "wrong object subtype.");
|
|
if (!noheader && btree_is_catalog(btree) && obj_subtype != APFS_OBJECT_TYPE_FSTREE)
|
|
report("Catalog node", "wrong object subtype.");
|
|
if (btree_is_extentref(btree) && obj_subtype !=
|
|
APFS_OBJECT_TYPE_BLOCKREFTREE)
|
|
report("Extent reference tree node", "wrong object subtype.");
|
|
if (btree_is_snap_meta(btree) && obj_subtype !=
|
|
APFS_OBJECT_TYPE_SNAPMETATREE)
|
|
report("Snapshot metadata node", "wrong object subtype.");
|
|
if (btree_is_free_queue(btree) && obj_subtype !=
|
|
APFS_OBJECT_TYPE_SPACEMAN_FREE_QUEUE)
|
|
report("Free queue node", "wrong object subtype.");
|
|
if (btree_is_snapshots(btree) && obj_subtype != APFS_OBJECT_TYPE_OMAP_SNAPSHOT)
|
|
report("Omap snapshot tree node", "wrong object subtype.");
|
|
if (btree_is_fext(btree) && obj_subtype != APFS_OBJECT_TYPE_FEXT_TREE)
|
|
report("File extents tree node", "wrong object subtype.");
|
|
|
|
node_prepare_bitmaps(node);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* node_free - Free a node structure
|
|
* @node: node to free
|
|
*
|
|
* This function works under the assumption that the node flags are not
|
|
* corrupted, but we are not yet checking that (TODO).
|
|
*/
|
|
static void node_free(struct node *node)
|
|
{
|
|
struct object *obj = NULL;
|
|
|
|
if (node_is_root(node))
|
|
return; /* The root nodes are needed by the sb until the end */
|
|
obj = &node->object;
|
|
|
|
/*
|
|
* Ephemeral objects may wrap around so they are actually copied to
|
|
* memory, not just mmapped. Hacky, like much of the fsck.
|
|
*/
|
|
if ((obj->flags & APFS_OBJ_STORAGETYPE_MASK) == APFS_OBJ_EPHEMERAL)
|
|
free(node->raw);
|
|
else
|
|
munmap(node->raw, obj->size);
|
|
|
|
free(node->free_key_bmap);
|
|
free(node->free_val_bmap);
|
|
free(node->used_key_bmap);
|
|
free(node->used_val_bmap);
|
|
free(node);
|
|
}
|
|
|
|
/**
|
|
* node_locate_key - Locate the key of a node record
|
|
* @node: node to be searched
|
|
* @index: number of the entry to locate
|
|
* @off: on return will hold the offset in the block
|
|
*
|
|
* Returns the length of the key. The function checks that this length fits
|
|
* within the key area; callers must use the returned value to make sure they
|
|
* never operate outside its bounds.
|
|
*/
|
|
static int node_locate_key(struct node *node, int index, int *off)
|
|
{
|
|
struct apfs_btree_node_phys *raw;
|
|
int len, off_in_area;
|
|
|
|
if (index >= node->records)
|
|
report("B-tree node", "requested index out-of-bounds.");
|
|
|
|
raw = node->raw;
|
|
if (node_has_fixed_kv_size(node)) {
|
|
struct apfs_kvoff *entry;
|
|
|
|
entry = (struct apfs_kvoff *)raw->btn_data + index;
|
|
len = btree_is_snapshots(node->btree) || btree_is_fusion_mt(node->btree) ? 8 : 16;
|
|
off_in_area = le16_to_cpu(entry->k);
|
|
} else {
|
|
/* These node types have variable length keys and data */
|
|
struct apfs_kvloc *entry;
|
|
|
|
entry = (struct apfs_kvloc *)raw->btn_data + index;
|
|
len = le16_to_cpu(entry->k.len);
|
|
off_in_area = le16_to_cpu(entry->k.off);
|
|
}
|
|
|
|
/* Translate offset in key area to offset in block */
|
|
*off = node->key + off_in_area;
|
|
if (*off + len > node->free)
|
|
report("B-tree", "key is out-of-bounds.");
|
|
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* node_locate_data - Locate the data of a node record
|
|
* @node: node to be searched
|
|
* @index: number of the entry to locate
|
|
* @off: on return will hold the offset in the block
|
|
*
|
|
* Returns the length of the data. The function checks that this length fits
|
|
* within the value area; callers must use the returned value to make sure they
|
|
* never operate outside its bounds.
|
|
*/
|
|
static int node_locate_data(struct node *node, int index, int *off)
|
|
{
|
|
struct apfs_btree_node_phys *raw;
|
|
int len, off_in_area, area_len;
|
|
|
|
if (index >= node->records)
|
|
report("B-tree", "requested index out-of-bounds.");
|
|
|
|
/* Only the root has a footer */
|
|
area_len = sb->s_blocksize - node->data -
|
|
(node_is_root(node) ? sizeof(struct apfs_btree_info) : 0);
|
|
|
|
raw = node->raw;
|
|
if (node_has_fixed_kv_size(node)) {
|
|
/* These node types have fixed length keys and data */
|
|
struct apfs_kvoff *entry;
|
|
struct btree *btree = node->btree;
|
|
|
|
entry = (struct apfs_kvoff *)raw->btn_data + index;
|
|
|
|
if (btree_is_free_queue(btree)) {
|
|
/* A free-space queue record may have no value */
|
|
if (le16_to_cpu(entry->v) == APFS_BTOFF_INVALID)
|
|
return 0;
|
|
len = 8;
|
|
}
|
|
if (btree_is_omap(btree))
|
|
len = node_is_leaf(node) ? 16 : 8;
|
|
if (btree_is_snapshots(btree))
|
|
len = node_is_leaf(node) ? sizeof(struct apfs_omap_snapshot) : 8;
|
|
if (btree_is_fext(btree))
|
|
len = node_is_leaf(node) ? sizeof(struct apfs_fext_tree_val) : 8;
|
|
if (btree_is_fusion_mt(btree))
|
|
len = node_is_leaf(node) ? sizeof(struct apfs_fusion_mt_val) : 8;
|
|
|
|
/* Value offsets are backwards from the end of the value area */
|
|
off_in_area = area_len - le16_to_cpu(entry->v);
|
|
} else {
|
|
/* These node types have variable length keys and data */
|
|
struct apfs_kvloc *entry;
|
|
|
|
entry = (struct apfs_kvloc *)raw->btn_data + index;
|
|
len = le16_to_cpu(entry->v.len);
|
|
|
|
/* Value offsets are backwards from the end of the value area */
|
|
off_in_area = area_len - le16_to_cpu(entry->v.off);
|
|
}
|
|
|
|
*off = node->data + off_in_area;
|
|
if (*off < node->data || off_in_area >= area_len)
|
|
report("B-tree", "value is out-of-bounds.");
|
|
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* bmap_mark_as_used - Mark a region of a node as used in the allocation bitmap
|
|
* @bitmap: bitmap to update
|
|
* @off: offset of the region, relative to its area in the node
|
|
* @len: length of the region in the node
|
|
*/
|
|
static void bmap_mark_as_used(u8 *bitmap, int off, int len)
|
|
{
|
|
u8 *byte;
|
|
u8 flag;
|
|
int i;
|
|
|
|
for (i = off; i < off + len; ++i) {
|
|
byte = bitmap + i / 8;
|
|
flag = 1 << i % 8;
|
|
|
|
if (*byte & flag)
|
|
report("B-tree node", "overlapping record data.");
|
|
*byte |= flag;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* compare_bmaps - Compare record allocation bitmaps for free and used space
|
|
* @free_bmap: bitmap compiled from the free space linked list
|
|
* @used_bmap: bitmap compiled from the table of contents
|
|
* @area_len: length of the area to check in the node
|
|
*
|
|
* Verifies that @free_bmap and @used_bmap are consistent, and returns the
|
|
* total number of free bytes (including those not counted in @free_bmap due
|
|
* to fragmentation).
|
|
*/
|
|
static int compare_bmaps(u8 *free_bmap, u8 *used_bmap, int area_len)
|
|
{
|
|
int unused = 0;
|
|
int i, j;
|
|
|
|
for (i = 0; i < area_len / 8; ++i) {
|
|
for (j = 0; j < 8; ++j) {
|
|
u8 mask = 1 << j;
|
|
|
|
if (!(used_bmap[i] & mask))
|
|
++unused;
|
|
}
|
|
|
|
if ((free_bmap[i] | used_bmap[i]) != free_bmap[i])
|
|
report("B-tree node",
|
|
"used record space listed as free.");
|
|
}
|
|
|
|
/* Last byte has some undefined bits by the end, so be careful */
|
|
for (j = 0; j < area_len % 8; ++j) {
|
|
u8 mask = 1 << j;
|
|
|
|
if (!(used_bmap[area_len / 8] & mask))
|
|
++unused;
|
|
if (used_bmap[area_len / 8] & mask &&
|
|
!(free_bmap[area_len / 8] & mask))
|
|
report("B-tree node",
|
|
"used record space listed as free.");
|
|
}
|
|
|
|
return unused;
|
|
}
|
|
|
|
/**
|
|
* node_compare_bmaps - Check consistency of the allocation bitmaps for a node
|
|
* @node: node to check
|
|
*
|
|
* Verifies that the bitmaps built from the free lists match the actual unused
|
|
* space bitmaps, built from the table of contents. This function shouldn't be
|
|
* called, of course, until all the bitmaps are assembled.
|
|
*/
|
|
static void node_compare_bmaps(struct node *node)
|
|
{
|
|
struct apfs_nloc *free_head;
|
|
int area_len;
|
|
int free_count;
|
|
|
|
/*
|
|
* First check the key area.
|
|
*/
|
|
free_head = &node->raw->btn_key_free_list;
|
|
|
|
area_len = node->free - node->key;
|
|
free_count = compare_bmaps(node->free_key_bmap, node->used_key_bmap,
|
|
area_len);
|
|
|
|
if (free_count != le16_to_cpu(free_head->len))
|
|
report("B-tree", "wrong free space total for key area.");
|
|
|
|
/*
|
|
* Now check the value area.
|
|
*/
|
|
free_head = &node->raw->btn_val_free_list;
|
|
|
|
/* Only the root has a footer */
|
|
area_len = sb->s_blocksize - node->data -
|
|
(node_is_root(node) ? sizeof(struct apfs_btree_info) : 0);
|
|
free_count = compare_bmaps(node->free_val_bmap, node->used_val_bmap,
|
|
area_len);
|
|
|
|
if (free_count != le16_to_cpu(free_head->len))
|
|
report("B-tree", "wrong free space total for value area.");
|
|
}
|
|
|
|
/**
|
|
* parse_cat_record - Parse a catalog record value and check for corruption
|
|
* @key: pointer to the raw key
|
|
* @val: pointer to the raw value
|
|
* @len: length of the raw value
|
|
*
|
|
* Internal consistency of @key must be checked before calling this function.
|
|
*/
|
|
static void parse_cat_record(void *key, void *val, int len)
|
|
{
|
|
switch (cat_type(key)) {
|
|
case APFS_TYPE_INODE:
|
|
parse_inode_record(key, val, len);
|
|
break;
|
|
case APFS_TYPE_DIR_REC:
|
|
parse_dentry_record(key, val, len);
|
|
break;
|
|
case APFS_TYPE_FILE_EXTENT:
|
|
parse_extent_record(key, val, len);
|
|
break;
|
|
case APFS_TYPE_SIBLING_LINK:
|
|
parse_sibling_record(key, val, len);
|
|
break;
|
|
case APFS_TYPE_XATTR:
|
|
parse_xattr_record(key, val, len);
|
|
break;
|
|
case APFS_TYPE_SIBLING_MAP:
|
|
parse_sibling_map_record(key, val, len);
|
|
break;
|
|
case APFS_TYPE_DSTREAM_ID:
|
|
parse_dstream_id_record(key, val, len);
|
|
break;
|
|
case APFS_TYPE_DIR_STATS:
|
|
parse_dir_stats_record(key, val, len);
|
|
break;
|
|
case APFS_TYPE_CRYPTO_STATE:
|
|
parse_crypto_state_record(key, val, len);
|
|
break;
|
|
case APFS_TYPE_FILE_INFO:
|
|
parse_file_info_record(key, val, len);
|
|
break;
|
|
default:
|
|
report(NULL, "Bug!");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* parse_fusion_mt_record - Parse a fusion mt value and check for corruption
|
|
* @key: pointer to the raw key
|
|
* @val: pointer to the raw value
|
|
* @len: length of the raw value
|
|
*
|
|
* Internal consistency of @key must be checked before calling this function.
|
|
*/
|
|
static void parse_fusion_mt_record(struct apfs_fusion_mt_key *key, struct apfs_fusion_mt_val *val, int len)
|
|
{
|
|
char *data_tier2 = NULL, *data_main = NULL;
|
|
u64 paddr_tier2, paddr_main;
|
|
u64 offset_tier2, offset_main;
|
|
u32 blkcnt;
|
|
u64 length;
|
|
u32 flags;
|
|
|
|
if (len != sizeof(*val))
|
|
report("Fusion middle-tree record", "wrong value size.");
|
|
|
|
paddr_tier2 = le64_to_cpu(key->fmk_paddr);
|
|
offset_tier2 = paddr_tier2 << sb->s_blocksize_bits;
|
|
if (offset_tier2 < APFS_FUSION_TIER2_DEVICE_BYTE_ADDR)
|
|
report("Fusion middle-tree record", "key address is in main.");
|
|
|
|
paddr_main = le64_to_cpu(val->fmv_lba);
|
|
offset_main = paddr_main << sb->s_blocksize_bits;
|
|
if (offset_main >= APFS_FUSION_TIER2_DEVICE_BYTE_ADDR)
|
|
report("Fusion middle-tree record", "value address is in tier 2.");
|
|
|
|
blkcnt = le32_to_cpu(val->fmv_length);
|
|
|
|
/*
|
|
* The tier 2 blocks get counted when they are actually used; blocks in
|
|
* the writeback cache were already counted by check_fusion_wbc().
|
|
*/
|
|
if (!range_in_wbc(paddr_main, blkcnt))
|
|
container_bmap_mark_as_used(paddr_main, blkcnt);
|
|
|
|
flags = le32_to_cpu(val->fmv_flags);
|
|
if ((flags & APFS_FUSION_MT_ALLFLAGS) != flags)
|
|
report("Fusion middle-tree record", "invalid flags in use.");
|
|
if ((flags & APFS_FUSION_MT_TENANT) && (flags & APFS_FUSION_MT_DIRTY))
|
|
report("Fusion middle-tree record", "can't be both dirty and tenant.");
|
|
if (flags & APFS_FUSION_MT_TENANT)
|
|
report_unknown("Tenant mt record");
|
|
|
|
if (!(flags & APFS_FUSION_MT_DIRTY)) {
|
|
length = (u64)blkcnt * sb->s_blocksize;
|
|
/*
|
|
* This may actually not check the whole range if the fsck runs
|
|
* in a 32-bit computer. I guess it doesn't matter.
|
|
*/
|
|
data_tier2 = apfs_mmap(NULL, length, PROT_READ, MAP_PRIVATE, offset_tier2);
|
|
if (data_tier2 == MAP_FAILED)
|
|
system_error();
|
|
data_main = apfs_mmap(NULL, length, PROT_READ, MAP_PRIVATE, offset_main);
|
|
if (data_main == MAP_FAILED)
|
|
system_error();
|
|
if (memcmp(data_tier2, data_main, length) != 0)
|
|
report("Fusion middle-tree record", "missing dirty flag.");
|
|
munmap(data_tier2, length);
|
|
data_tier2 = NULL;
|
|
munmap(data_main, length);
|
|
data_main = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* free_omap_record - Free an object map record list after a final check
|
|
* @entry: the entry to free
|
|
*/
|
|
static void free_omap_record_list(struct htable_entry *entry)
|
|
{
|
|
struct omap_record_list *list = (struct omap_record_list *)entry;
|
|
struct omap_record *curr_rec = list->o_records;
|
|
|
|
while (curr_rec) {
|
|
struct omap_record *next_rec = NULL;
|
|
|
|
if (curr_rec->flags & APFS_OMAP_VAL_DELETED) {
|
|
if (curr_rec->seen)
|
|
report("Omap record", "deleted but still in use.");
|
|
} else if (!curr_rec->seen) {
|
|
/*
|
|
* Old implementations that are unaware of extended
|
|
* snapshot metadata are allowed to leak those blocks
|
|
* when a snapshot gets deleted.
|
|
*/
|
|
if (vsb && curr_rec->xid < sb->s_xid) {
|
|
struct apfs_obj_phys *raw = NULL;
|
|
struct object obj = {0};
|
|
|
|
raw = read_object_nocheck(curr_rec->bno, sb->s_blocksize, &obj);
|
|
if (obj.type != APFS_OBJECT_TYPE_SNAP_META_EXT || obj.subtype != APFS_OBJECT_TYPE_INVALID)
|
|
report("Leaked omap record", "unexpected object type.");
|
|
container_bmap_mark_as_used(curr_rec->bno, 1);
|
|
++vsb->v_block_count;
|
|
munmap(raw, obj.size);
|
|
} else {
|
|
report("Omap record", "oid-xid combination is never used.");
|
|
}
|
|
}
|
|
|
|
next_rec = curr_rec->next;
|
|
free(curr_rec);
|
|
curr_rec = next_rec;
|
|
}
|
|
|
|
free(entry);
|
|
}
|
|
|
|
/**
|
|
* free_omap_table - Free a hash table for omap records, and all its entries
|
|
* @table: table to free
|
|
*/
|
|
void free_omap_table(struct htable_entry **table)
|
|
{
|
|
free_htable(table, free_omap_record_list);
|
|
}
|
|
|
|
/**
|
|
* omap_list_clear_seen_for_snap - Clear seen_for_snap for all omap recs in list
|
|
* @entry: a list of omap records
|
|
*/
|
|
static void omap_list_clear_seen_for_snap(struct htable_entry *entry)
|
|
{
|
|
struct omap_record_list *list = (struct omap_record_list *)entry;
|
|
struct omap_record *rec = NULL;
|
|
|
|
for (rec = list->o_records; rec; rec = rec->next)
|
|
rec->seen_for_snap = false;
|
|
}
|
|
|
|
/**
|
|
* omap_htable_clear_seen_for_snap - Clear seen_for_snap on all omap recs
|
|
* @table: the hash table of omap records to be cleared
|
|
*/
|
|
void omap_htable_clear_seen_for_snap(struct htable_entry **table)
|
|
{
|
|
return apply_on_htable(table, omap_list_clear_seen_for_snap);
|
|
}
|
|
|
|
/**
|
|
* get_omap_record - Find or create an omap record structure in a hash table
|
|
* @oid: object id to be mapped
|
|
* @xid: transaction id
|
|
* @table: the hash table of omap records to be searched
|
|
*
|
|
* Returns the omap record structure, after creating it if necessary.
|
|
*/
|
|
static struct omap_record *get_omap_record(u64 oid, u64 xid, struct htable_entry **table)
|
|
{
|
|
struct omap_record_list *list = NULL;
|
|
struct omap_record **omap_p = NULL;
|
|
struct omap_record *omap = NULL;
|
|
struct omap_record *new = NULL;
|
|
|
|
list = (struct omap_record_list *)get_htable_entry(oid, sizeof(struct omap_record), table);
|
|
|
|
omap_p = &list->o_records;
|
|
omap = *omap_p;
|
|
while (omap) {
|
|
if (xid == omap->xid)
|
|
return omap;
|
|
if (xid < omap->xid)
|
|
break;
|
|
omap_p = &omap->next;
|
|
omap = *omap_p;
|
|
}
|
|
new = calloc(1, sizeof(*new));
|
|
if (!new)
|
|
system_error();
|
|
new->xid = xid;
|
|
new->oid = oid;
|
|
new->next = omap;
|
|
*omap_p = new;
|
|
return new;
|
|
}
|
|
|
|
/**
|
|
* get_latest_omap_record - Find the most recent omap record before a given xid
|
|
* @oid: object id to be mapped
|
|
* @xid: transaction id
|
|
* @table: the hash table of omap records to be searched
|
|
*
|
|
* Returns the omap record structure, or NULL if there is none matches.
|
|
*/
|
|
struct omap_record *get_latest_omap_record(u64 oid, u64 xid, struct htable_entry **table)
|
|
{
|
|
struct omap_record_list *list = NULL;
|
|
struct omap_record *omap = NULL, *prev_omap = NULL;
|
|
|
|
list = (struct omap_record_list *)get_htable_entry(oid, sizeof(struct omap_record), table);
|
|
omap = list->o_records;
|
|
while (omap) {
|
|
if (xid < omap->xid)
|
|
break;
|
|
prev_omap = omap;
|
|
omap = prev_omap->next;
|
|
}
|
|
return prev_omap;
|
|
}
|
|
|
|
/**
|
|
* parse_omap_record - Parse an object map record value and check for corruption
|
|
* @key: pointer to the raw key
|
|
* @val: pointer to the raw value
|
|
* @len: length of the raw value
|
|
*
|
|
* Internal consistency of @key must be checked before calling this function.
|
|
*/
|
|
static void parse_omap_record(struct apfs_omap_key *key,
|
|
struct apfs_omap_val *val, int len)
|
|
{
|
|
struct omap_record *omap_rec;
|
|
u32 flags;
|
|
u32 size;
|
|
|
|
/* In the omap, keys and values must be aligned to eight bytes */
|
|
if ((u64)key & 7 || (u64)val & 7)
|
|
report("Omap record", "bad alignment for key or value.");
|
|
|
|
if (len != sizeof(*val))
|
|
report("Omap record", "wrong size of value.");
|
|
|
|
if (vsb) {
|
|
/* We are parsing a volume's object map */
|
|
omap_rec = get_omap_record(le64_to_cpu(key->ok_oid), le64_to_cpu(key->ok_xid), vsb->v_omap_table);
|
|
} else {
|
|
/* We are parsing the container's object map */
|
|
omap_rec = get_omap_record(le64_to_cpu(key->ok_oid), le64_to_cpu(key->ok_xid), sb->s_omap_table);
|
|
}
|
|
|
|
if (omap_rec->bno)
|
|
report("Object map", "two entries with the same oid-xid.");
|
|
omap_rec->bno = le64_to_cpu(val->ov_paddr);
|
|
|
|
omap_rec->flags = flags = le32_to_cpu(val->ov_flags);
|
|
if ((flags & APFS_OMAP_VAL_FLAGS_VALID_MASK) != flags)
|
|
report("Omap record", "invalid flag in use.");
|
|
if (flags & APFS_OMAP_VAL_SAVED)
|
|
report("Omap record", "saved flag is set.");
|
|
if ((bool)(flags & APFS_OMAP_VAL_ENCRYPTED) != (vsb && vsb->v_encrypted))
|
|
report("Omap record", "wrong encryption flag.");
|
|
if (flags & APFS_OMAP_VAL_CRYPTO_GENERATION)
|
|
report_unknown("Crypto generation flag");
|
|
|
|
size = le32_to_cpu(val->ov_size);
|
|
if (size & (sb->s_blocksize - 1))
|
|
report("Omap record", "size isn't multiple of block size.");
|
|
if (size != sb->s_blocksize)
|
|
report_unknown("Objects with more than one block");
|
|
}
|
|
|
|
/**
|
|
* parse_subtree - Parse a subtree and check for corruption
|
|
* @root: root node of the subtree
|
|
* @last_key: parent key, that must come before all the keys in this subtree;
|
|
* on return, this will hold the last key of this subtree, that
|
|
* must come before the next key of the parent node
|
|
* @name_buf: buffer to store the name of @last_key when its node is freed
|
|
* (can be NULL if the keys have no name)
|
|
*/
|
|
static void parse_subtree(struct node *root,
|
|
struct key *last_key, char *name_buf)
|
|
{
|
|
struct btree *btree = root->btree;
|
|
struct key curr_key;
|
|
bool is_sealed_cat;
|
|
int i;
|
|
|
|
if (node_is_leaf(root)) {
|
|
if (root->level != 0)
|
|
report("B-tree", "nonleaf node flagged as leaf.");
|
|
btree->key_count += root->records;
|
|
}
|
|
++btree->node_count;
|
|
|
|
if (btree_is_omap(btree) && !node_has_fixed_kv_size(root))
|
|
report("Object map", "key size should be fixed.");
|
|
if (btree_is_catalog(btree) && node_has_fixed_kv_size(root))
|
|
report("Catalog", "key size should not be fixed.");
|
|
if (btree_is_free_queue(btree) && !node_has_fixed_kv_size(root))
|
|
report("Free-space queue", "key size should be fixed.");
|
|
if (btree_is_snap_meta(btree) && node_has_fixed_kv_size(root))
|
|
report("Snap meta tree", "key size shouldn't be fixed.");
|
|
if (btree_is_snapshots(btree) && !node_has_fixed_kv_size(root))
|
|
report("Omap snapshot tree", "key size should be fixed.");
|
|
if (btree_is_fext(btree) && !node_has_fixed_kv_size(root))
|
|
report("File extents tree", "key size should be fixed.");
|
|
if (btree_is_fusion_mt(btree) && !node_has_fixed_kv_size(root))
|
|
report("Fusion middle-tree", "key size should be fixed.");
|
|
|
|
/* This makes little sense, but it appears to be true */
|
|
if (btree_is_extentref(btree) && node_has_fixed_kv_size(root))
|
|
report("Extent reference tree", "key size shouldn't be fixed.");
|
|
|
|
is_sealed_cat = btree_is_catalog(btree) && apfs_volume_is_sealed();
|
|
|
|
for (i = 0; i < root->records; ++i) {
|
|
struct node *child;
|
|
void *raw = root->raw;
|
|
void *raw_key, *raw_val;
|
|
int off, len;
|
|
u64 child_id;
|
|
u8 *child_hash = NULL;
|
|
|
|
len = node_locate_key(root, i, &off);
|
|
if (len > btree->longest_key)
|
|
btree->longest_key = len;
|
|
bmap_mark_as_used(root->used_key_bmap, off - root->key, len);
|
|
raw_key = raw + off;
|
|
|
|
if (btree_is_omap(btree)) {
|
|
read_omap_key(raw_key, len, &curr_key);
|
|
|
|
/* When a key is added, the node is updated */
|
|
if (curr_key.number > root->object.xid)
|
|
report("Object map",
|
|
"node xid is older than key xid.");
|
|
}
|
|
if (btree_is_catalog(btree))
|
|
read_cat_key(raw_key, len, &curr_key);
|
|
if (btree_is_extentref(btree))
|
|
read_extentref_key(raw_key, len, &curr_key);
|
|
if (btree_is_free_queue(btree))
|
|
read_free_queue_key(raw_key, len, &curr_key);
|
|
if (btree_is_snap_meta(btree))
|
|
read_snap_key(raw_key, len, &curr_key);
|
|
if (btree_is_snapshots(btree))
|
|
read_omap_snap_key(raw_key, len, &curr_key);
|
|
if (btree_is_fext(btree))
|
|
read_fext_key(raw_key, len, &curr_key);
|
|
if (btree_is_fusion_mt(btree))
|
|
read_fusion_mt_key(raw_key, len, &curr_key);
|
|
|
|
if (keycmp(last_key, &curr_key) > 0)
|
|
report("B-tree", "keys are out of order.");
|
|
if (!i && !node_is_root(root) && keycmp(last_key, &curr_key))
|
|
report("B-tree", "index key absent from child node.");
|
|
|
|
if (i != 0 && node_is_leaf(root) &&
|
|
!keycmp(last_key, &curr_key))
|
|
report("B-tree", "leaf keys are repeated.");
|
|
*last_key = curr_key;
|
|
|
|
len = node_locate_data(root, i, &off);
|
|
bmap_mark_as_used(root->used_val_bmap, off - root->data, len);
|
|
raw_val = raw + off;
|
|
|
|
if (node_is_leaf(root)) {
|
|
if (len > btree->longest_val)
|
|
btree->longest_val = len;
|
|
if (btree_is_catalog(btree))
|
|
parse_cat_record(raw_key, raw_val, len);
|
|
if (btree_is_omap(btree))
|
|
parse_omap_record(raw_key, raw_val, len);
|
|
if (btree_is_free_queue(btree))
|
|
parse_free_queue_record(raw_key, raw_val, len,
|
|
btree);
|
|
if (btree_is_extentref(btree))
|
|
/* Physical extents must not overlap */
|
|
last_key->id = parse_phys_ext_record(raw_key,
|
|
raw_val, len);
|
|
if (btree_is_snap_meta(btree))
|
|
parse_snap_record(raw_key, raw_val, len);
|
|
if (btree_is_snapshots(btree))
|
|
parse_omap_snap_record(raw_key, raw_val, len);
|
|
if (btree_is_fext(btree))
|
|
parse_fext_record(raw_key, raw_val, len);
|
|
if (btree_is_fusion_mt(btree))
|
|
parse_fusion_mt_record(raw_key, raw_val, len);
|
|
continue;
|
|
}
|
|
|
|
if (is_sealed_cat) {
|
|
struct apfs_btn_index_node_val *index_val = NULL;
|
|
u64 base_oid = btree->root->object.oid;
|
|
|
|
if (len != sizeof(*index_val))
|
|
report("B-tree", "wrong size of hashed nonleaf record value.");
|
|
index_val = (struct apfs_btn_index_node_val *)raw_val;
|
|
/*
|
|
* The reference is wrong here, binv_child_oid is not
|
|
* the id for the child node, it's an offset from the
|
|
* id of the root node. As usual, I have no idea what
|
|
* this is about.
|
|
*/
|
|
child_id = le64_to_cpu(index_val->binv_child_oid) + base_oid;
|
|
child_hash = index_val->binv_child_hash;
|
|
} else {
|
|
if (len != 8)
|
|
report("B-tree", "wrong size of nonleaf record value.");
|
|
child_id = le64_to_cpu(*(__le64 *)(raw_val));
|
|
}
|
|
child = read_node(child_id, btree, child_hash);
|
|
|
|
if (child->level != root->level - 1)
|
|
report("B-tree", "node levels are corrupted.");
|
|
if (node_is_root(child))
|
|
report("B-tree", "nonroot node is flagged as root.");
|
|
|
|
/* If a physical node changes, the parent must update the bno */
|
|
if ((btree_is_omap(btree) || btree_is_extentref(btree) || btree_is_snap_meta(btree) || btree_is_snapshots(btree) || btree_is_fext(btree)) && root->object.xid < child->object.xid)
|
|
report("Physical tree",
|
|
"xid of node is older than xid of its child.");
|
|
|
|
parse_subtree(child, last_key, name_buf);
|
|
node_free(child);
|
|
}
|
|
|
|
/* All records of @root are processed, so it's a good time for this */
|
|
node_compare_bmaps(root);
|
|
|
|
/*
|
|
* last_key->name is just a pointer to the memory-mapped on-disk name
|
|
* of the key. Since the caller will free the node, make a copy.
|
|
*/
|
|
if (node_is_leaf(root) && last_key->name) {
|
|
assert(name_buf);
|
|
strcpy(name_buf, last_key->name);
|
|
last_key->name = name_buf;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* check_btree_footer_flags - Check consistency of b-tree footer flags
|
|
* @flags: the flags
|
|
* @btree: the b-tree being checked
|
|
* @ctx: context string for corruption reports
|
|
*/
|
|
static void check_btree_footer_flags(u32 flags, struct btree *btree, char *ctx)
|
|
{
|
|
bool aligned, is_free_queue, is_physical, is_sealed_cat;
|
|
|
|
if ((flags & APFS_BTREE_FLAGS_VALID_MASK) != flags)
|
|
report(ctx, "invalid flag in use.");
|
|
if (flags & (APFS_BTREE_NONPERSISTENT))
|
|
report(ctx, "nonpersistent flag is set.");
|
|
|
|
/* TODO: are these really the only allowed settings for the flag? */
|
|
aligned = btree_is_omap(btree) || btree_is_free_queue(btree) || btree_is_snapshots(btree) || btree_is_fext(btree) || btree_is_fusion_mt(btree);
|
|
if (aligned != !(flags & APFS_BTREE_KV_NONALIGNED))
|
|
report(ctx, "wrong alignment flag.");
|
|
|
|
is_free_queue = btree_is_free_queue(btree);
|
|
if (is_free_queue != (bool)(flags & (APFS_BTREE_ALLOW_GHOSTS)))
|
|
report(ctx, "wrong setting of ghosts flag.");
|
|
if (is_free_queue != (bool)(flags & (APFS_BTREE_EPHEMERAL)))
|
|
report(ctx, "wrong setting of ephemeral flag.");
|
|
|
|
is_physical = !(btree_is_catalog(btree) || btree_is_free_queue(btree));
|
|
if (is_physical != (bool)(flags & APFS_BTREE_PHYSICAL))
|
|
report(ctx, "wrong setting of physical flag.");
|
|
|
|
is_sealed_cat = btree_is_catalog(btree) && apfs_volume_is_sealed();
|
|
if (is_sealed_cat != (bool)(flags & APFS_BTREE_HASHED))
|
|
report(ctx, "wrong setting of hashed flag.");
|
|
if (is_sealed_cat != (bool)(flags & APFS_BTREE_NOHEADER))
|
|
report(ctx, "wrong setting of no-header flag.");
|
|
}
|
|
|
|
/**
|
|
* check_btree_footer - Check that btree_info matches the collected stats
|
|
* @btree: b-tree to check
|
|
*/
|
|
static void check_btree_footer(struct btree *btree)
|
|
{
|
|
struct node *root = btree->root;
|
|
struct apfs_btree_info *info;
|
|
char *ctx;
|
|
|
|
switch (btree->type) {
|
|
case BTREE_TYPE_OMAP:
|
|
ctx = "Object map";
|
|
break;
|
|
case BTREE_TYPE_CATALOG:
|
|
ctx = "Catalog";
|
|
break;
|
|
case BTREE_TYPE_EXTENTREF:
|
|
ctx = "Extent reference tree";
|
|
break;
|
|
case BTREE_TYPE_SNAP_META:
|
|
ctx = "Snapshot metadata tree";
|
|
break;
|
|
case BTREE_TYPE_FREE_QUEUE:
|
|
ctx = "Free-space queue";
|
|
break;
|
|
case BTREE_TYPE_SNAPSHOTS:
|
|
ctx = "Omap snapshot tree";
|
|
break;
|
|
case BTREE_TYPE_FEXT:
|
|
ctx = "File extents tree";
|
|
break;
|
|
case BTREE_TYPE_FUSION_MT:
|
|
ctx = "Fusion middle-tree";
|
|
break;
|
|
default:
|
|
report(NULL, "Bug!");
|
|
}
|
|
|
|
/* This flag is not part of the footer, but its check fits best here */
|
|
if (!node_is_root(root))
|
|
report(ctx, "wrong flag in root node.");
|
|
|
|
info = (void *)root->raw + sb->s_blocksize - sizeof(*info);
|
|
if (le32_to_cpu(info->bt_fixed.bt_node_size) != sb->s_blocksize)
|
|
report_unknown("Objects with more than one block");
|
|
|
|
check_btree_footer_flags(le32_to_cpu(info->bt_fixed.bt_flags),
|
|
btree, ctx);
|
|
|
|
if (le64_to_cpu(info->bt_key_count) != btree->key_count)
|
|
report(ctx, "wrong key count in info footer.");
|
|
if (le64_to_cpu(info->bt_node_count) != btree->node_count)
|
|
report(ctx, "wrong node count in info footer.");
|
|
|
|
if (btree_is_omap(btree)) {
|
|
u32 longest_key = le32_to_cpu(info->bt_longest_key);
|
|
u32 longest_val = le32_to_cpu(info->bt_longest_val);
|
|
|
|
if (le32_to_cpu(info->bt_fixed.bt_key_size) !=
|
|
sizeof(struct apfs_omap_key))
|
|
report(ctx, "wrong key size in info footer.");
|
|
|
|
if (le32_to_cpu(info->bt_fixed.bt_val_size) !=
|
|
sizeof(struct apfs_omap_val))
|
|
report(ctx, "wrong value size in info footer.");
|
|
|
|
/* Containers with no volumes do exist */
|
|
if ((longest_key || btree->key_count) && longest_key != sizeof(struct apfs_omap_key))
|
|
report(ctx, "wrong maximum key size in info footer.");
|
|
if ((longest_val || btree->key_count) && longest_val != sizeof(struct apfs_omap_val))
|
|
report(ctx, "wrong maximum value size in info footer.");
|
|
return;
|
|
}
|
|
|
|
if (btree_is_free_queue(btree)) {
|
|
if (le32_to_cpu(info->bt_fixed.bt_key_size) !=
|
|
sizeof(struct apfs_spaceman_free_queue_key))
|
|
report(ctx, "wrong key size in info footer.");
|
|
|
|
/* Ghost records may also exist, but they don't count here */
|
|
if (le32_to_cpu(info->bt_fixed.bt_val_size) != 8)
|
|
report(ctx, "wrong value size in info footer.");
|
|
|
|
if (le32_to_cpu(info->bt_longest_key) !=
|
|
sizeof(struct apfs_spaceman_free_queue_key))
|
|
report(ctx, "wrong maximum key size in info footer.");
|
|
|
|
if (le32_to_cpu(info->bt_longest_val) < btree->longest_val)
|
|
report(ctx, "wrong maximum value size in info footer.");
|
|
|
|
return;
|
|
}
|
|
|
|
if (btree_is_snapshots(btree)) {
|
|
if (le32_to_cpu(info->bt_fixed.bt_key_size) != 8)
|
|
report(ctx, "wrong key size in info footer.");
|
|
if (le32_to_cpu(info->bt_fixed.bt_val_size) != sizeof(struct apfs_omap_snapshot))
|
|
report(ctx, "wrong value size in info footer.");
|
|
if (le32_to_cpu(info->bt_longest_key) != 8)
|
|
report(ctx, "wrong maximum key size in info footer.");
|
|
if (le32_to_cpu(info->bt_longest_val) != sizeof(struct apfs_omap_snapshot))
|
|
report(ctx, "wrong maximum value size in info footer.");
|
|
return;
|
|
}
|
|
|
|
if (btree_is_fext(btree)) {
|
|
if (le32_to_cpu(info->bt_fixed.bt_key_size) != sizeof(struct apfs_fext_tree_key))
|
|
report(ctx, "wrong key size in info footer.");
|
|
if (le32_to_cpu(info->bt_fixed.bt_val_size) != sizeof(struct apfs_fext_tree_val))
|
|
report(ctx, "wrong value size in info footer.");
|
|
if (le32_to_cpu(info->bt_longest_key) != sizeof(struct apfs_fext_tree_key))
|
|
report(ctx, "wrong maximum key size in info footer.");
|
|
if (le32_to_cpu(info->bt_longest_val) != sizeof(struct apfs_fext_tree_val))
|
|
report(ctx, "wrong maximum value size in info footer.");
|
|
return;
|
|
}
|
|
|
|
if (btree_is_fusion_mt(btree)) {
|
|
u32 longest_key = le32_to_cpu(info->bt_longest_key);
|
|
u32 longest_val = le32_to_cpu(info->bt_longest_val);
|
|
|
|
if (le32_to_cpu(info->bt_fixed.bt_key_size) != sizeof(struct apfs_fusion_mt_key))
|
|
report(ctx, "wrong key size in info footer.");
|
|
if (le32_to_cpu(info->bt_fixed.bt_val_size) != sizeof(struct apfs_fusion_mt_val))
|
|
report(ctx, "wrong value size in info footer.");
|
|
|
|
/* The fusion middle-tree may be empty */
|
|
if ((longest_key || btree->key_count) && longest_key != sizeof(struct apfs_fusion_mt_key))
|
|
report(ctx, "wrong maximum key size in info footer.");
|
|
if ((longest_val || btree->key_count) && longest_val != sizeof(struct apfs_fusion_mt_val))
|
|
report(ctx, "wrong maximum value size in info footer.");
|
|
return;
|
|
}
|
|
|
|
/* The remaining trees don't report fixed key/value sizes */
|
|
if (le32_to_cpu(info->bt_fixed.bt_key_size) != 0)
|
|
report(ctx, "key size should not be set.");
|
|
if (le32_to_cpu(info->bt_fixed.bt_val_size) != 0)
|
|
report(ctx, "value size should not be set.");
|
|
|
|
if (btree_is_catalog(btree) || btree_is_snap_meta(btree)) {
|
|
if (le32_to_cpu(info->bt_longest_key) < btree->longest_key)
|
|
report(ctx, "wrong maximum key size in info footer.");
|
|
if (le32_to_cpu(info->bt_longest_val) < btree->longest_val)
|
|
report(ctx, "wrong maximum value size in info footer.");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* The extentref only seems to have records of this one type.
|
|
* No idea why it reports keys/values of variable size...
|
|
*/
|
|
if (btree_is_extentref(btree)) {
|
|
u32 longest_key = le32_to_cpu(info->bt_longest_key);
|
|
u32 longest_val = le32_to_cpu(info->bt_longest_val);
|
|
|
|
if ((longest_key || btree->key_count) &&
|
|
longest_key != sizeof(struct apfs_phys_ext_key))
|
|
report(ctx, "wrong maximum key size in info footer.");
|
|
if ((longest_val || btree->key_count) &&
|
|
longest_val != sizeof(struct apfs_phys_ext_val))
|
|
report(ctx, "wrong maximum val size in info footer.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* parse_free_queue_btree - Parse and check a free-space queue tree
|
|
* @oid: object id for the b-tree root
|
|
* @index: position of this free queue in the free queue array
|
|
*
|
|
* Returns a pointer to the free queue structure.
|
|
*/
|
|
struct free_queue *parse_free_queue_btree(u64 oid, int index)
|
|
{
|
|
struct free_queue *sfq;
|
|
struct btree *btree;
|
|
struct key last_key = {0};
|
|
|
|
sfq = calloc(1, sizeof(*sfq));
|
|
if (!sfq)
|
|
system_error();
|
|
btree = &sfq->sfq_btree;
|
|
sfq->sfq_index = index;
|
|
|
|
if (oid == 0) {
|
|
/* I've seen null fq's in fresh containers with no volumes */
|
|
return sfq;
|
|
}
|
|
|
|
btree->type = BTREE_TYPE_FREE_QUEUE;
|
|
btree->omap_table = NULL; /* These are ephemeral objects */
|
|
btree->root = read_node(oid, btree, NULL /* hash */);
|
|
parse_subtree(btree->root, &last_key, NULL /* name_buf */);
|
|
|
|
check_btree_footer(btree);
|
|
return sfq;
|
|
}
|
|
|
|
/**
|
|
* parse_snap_meta_btree - Parse and check a snapshot metadata tree
|
|
* @oid: object id for the b-tree root
|
|
*
|
|
* Returns a pointer to the btree struct for the snapshot metadata tree.
|
|
*/
|
|
struct btree *parse_snap_meta_btree(u64 oid)
|
|
{
|
|
struct btree *snap;
|
|
struct key last_key = {0};
|
|
char name_buf[256];
|
|
|
|
snap = calloc(1, sizeof(*snap));
|
|
if (!snap)
|
|
system_error();
|
|
snap->type = BTREE_TYPE_SNAP_META;
|
|
snap->omap_table = NULL; /* These are physical objects */
|
|
snap->root = read_node(oid, snap, NULL /* hash */);
|
|
|
|
parse_subtree(snap->root, &last_key, name_buf);
|
|
|
|
check_btree_footer(snap);
|
|
return snap;
|
|
}
|
|
|
|
/**
|
|
* parse_cat_btree - Parse a catalog tree and check for corruption
|
|
* @oid: object id for the catalog root
|
|
* @omap_table: hash table of object map records for the b-tree
|
|
*
|
|
* Returns a pointer to the btree struct for the catalog.
|
|
*/
|
|
struct btree *parse_cat_btree(u64 oid, struct htable_entry **omap_table)
|
|
{
|
|
struct btree *cat;
|
|
struct key last_key = {0};
|
|
char name_buf[256];
|
|
|
|
cat = calloc(1, sizeof(*cat));
|
|
if (!cat)
|
|
system_error();
|
|
|
|
/*
|
|
* We need to set this here so that parse_xattr_record() can read
|
|
* dstream contents. Maybe all parse_*_btree() functions should do
|
|
* this, for consistency...
|
|
*/
|
|
vsb->v_cat = cat;
|
|
|
|
cat->type = BTREE_TYPE_CATALOG;
|
|
cat->omap_table = omap_table;
|
|
cat->root = read_node(oid, cat, apfs_volume_is_sealed() ? vsb->v_hash : NULL);
|
|
|
|
parse_subtree(cat->root, &last_key, name_buf);
|
|
|
|
check_btree_footer(cat);
|
|
return cat;
|
|
}
|
|
|
|
/**
|
|
* parse_fext_btree - Parse a fext tree and check for corruption
|
|
* @oid: object id for the fext root
|
|
*
|
|
* Returns a pointer to the btree struct for the catalog.
|
|
*/
|
|
struct btree *parse_fext_btree(u64 oid)
|
|
{
|
|
struct btree *fext = NULL;
|
|
struct key last_key = {0};
|
|
|
|
fext = calloc(1, sizeof(*fext));
|
|
if (!fext)
|
|
system_error();
|
|
|
|
fext->type = BTREE_TYPE_FEXT;
|
|
fext->omap_table = NULL;
|
|
fext->root = read_node(oid, fext, NULL /* hash */);
|
|
|
|
parse_subtree(fext->root, &last_key, NULL);
|
|
|
|
check_btree_footer(fext);
|
|
return fext;
|
|
}
|
|
|
|
/**
|
|
* check_omap_flags - Check consistency of object map flags
|
|
* @flags: the flags
|
|
*/
|
|
static void check_omap_flags(u32 flags)
|
|
{
|
|
if ((flags & APFS_OMAP_FLAGS_VALID_MASK) != flags)
|
|
report("Object map", "invalid flag in use.");
|
|
|
|
if (flags & (APFS_OMAP_ENCRYPTING | APFS_OMAP_DECRYPTING |
|
|
APFS_OMAP_KEYROLLING | APFS_OMAP_CRYPTO_GENERATION))
|
|
report_unknown("Omap encryption");
|
|
|
|
if (vsb && (flags & APFS_OMAP_MANUALLY_MANAGED))
|
|
report("Volume object map", "is manually managed.");
|
|
if (!vsb && !(flags & APFS_OMAP_MANUALLY_MANAGED))
|
|
report("Container object map", "isn't manually managed.");
|
|
}
|
|
|
|
static struct btree *parse_snapshot_tree(u64 oid)
|
|
{
|
|
struct btree *snaps = NULL;
|
|
struct key last_key = {0};
|
|
|
|
snaps = calloc(1, sizeof(*snaps));
|
|
if (!snaps)
|
|
system_error();
|
|
|
|
snaps->type = BTREE_TYPE_SNAPSHOTS;
|
|
snaps->omap_table = NULL;
|
|
snaps->root = read_node(oid, snaps, NULL /* hash */);
|
|
|
|
parse_subtree(snaps->root, &last_key, NULL);
|
|
|
|
check_btree_footer(snaps);
|
|
return snaps;
|
|
}
|
|
|
|
/**
|
|
* parse_omap_btree - Parse an object map and check for corruption
|
|
* @oid: object id for the omap
|
|
*
|
|
* Returns a pointer to the btree struct for the omap.
|
|
*/
|
|
struct btree *parse_omap_btree(u64 oid)
|
|
{
|
|
struct apfs_omap_phys *raw;
|
|
struct btree *omap;
|
|
struct key last_key = {0};
|
|
struct object obj;
|
|
|
|
/* Many checks are missing, of course */
|
|
raw = read_object(oid, NULL /* omap_table */, &obj);
|
|
if (obj.type != APFS_OBJECT_TYPE_OMAP)
|
|
report("Object map", "wrong object type.");
|
|
if (obj.subtype != APFS_OBJECT_TYPE_INVALID)
|
|
report("Object map", "wrong object subtype.");
|
|
|
|
check_omap_flags(le32_to_cpu(raw->om_flags));
|
|
|
|
if (raw->om_snapshot_tree_oid) {
|
|
if (!vsb)
|
|
report("Container omap", "has snapshot tree.");
|
|
vsb->v_snapshots = parse_snapshot_tree(le64_to_cpu(raw->om_snapshot_tree_oid));
|
|
if (vsb->v_snapshots->key_count != le32_to_cpu(raw->om_snap_count))
|
|
report("Omap snapshot tree", "snap count doesn't match keys.");
|
|
if (vsb->v_snap_max_xid != le64_to_cpu(raw->om_most_recent_snap))
|
|
report("Omap snapshot tree", "latest xid doesn't match keys.");
|
|
} else if (raw->om_snap_count || raw->om_most_recent_snap) {
|
|
report("Object map", "has snapshots but no snapshot tree.");
|
|
}
|
|
|
|
/* Oddly, the type is still reported even when the tree is not set */
|
|
if (le32_to_cpu(raw->om_snapshot_tree_type) !=
|
|
(APFS_OBJECT_TYPE_BTREE | APFS_OBJ_PHYSICAL))
|
|
report("Object map", "wrong type for snapshot tree.");
|
|
|
|
if (raw->om_pending_revert_min || raw->om_pending_revert_max)
|
|
report_unknown("Revert in progress");
|
|
|
|
omap = calloc(1, sizeof(*omap));
|
|
if (!omap)
|
|
system_error();
|
|
omap->type = BTREE_TYPE_OMAP;
|
|
omap->omap_table = NULL; /* The omap doesn't have an omap of its own */
|
|
omap->root = read_node(le64_to_cpu(raw->om_tree_oid), omap, NULL /* hash */);
|
|
|
|
/* The tree type reported by the omap must match the root node */
|
|
if (raw->om_tree_type != omap->root->raw->btn_o.o_type)
|
|
report("Object map", "wrong type for tree.");
|
|
|
|
parse_subtree(omap->root, &last_key, NULL /* name_buf */);
|
|
|
|
check_btree_footer(omap);
|
|
munmap(raw, obj.size);
|
|
return omap;
|
|
}
|
|
|
|
/**
|
|
* parse_fusion_middle_tree - Parse a fusion mt and check for corruption
|
|
* @oid: object id for the fusion middle-tree
|
|
*
|
|
* Returns a pointer to the btree struct for the fusion middle-tree.
|
|
*/
|
|
struct btree *parse_fusion_middle_tree(u64 oid)
|
|
{
|
|
struct btree *mt = NULL;
|
|
struct key last_key = {0};
|
|
|
|
if (apfs_is_fusion_drive() != (bool)oid)
|
|
report("Fusion middle-tree", "oid incorrectly set/unset.");
|
|
if (!oid)
|
|
return NULL;
|
|
|
|
mt = calloc(1, sizeof(*mt));
|
|
if (!mt)
|
|
system_error();
|
|
|
|
mt->type = BTREE_TYPE_FUSION_MT;
|
|
mt->omap_table = NULL;
|
|
mt->root = read_node(oid, mt, NULL /* hash */);
|
|
|
|
parse_subtree(mt->root, &last_key, NULL);
|
|
|
|
check_btree_footer(mt);
|
|
return mt;
|
|
}
|
|
|
|
/**
|
|
* parse_extentref_btree - Parse and check an extent reference tree
|
|
* @oid: object id for the b-tree root
|
|
*
|
|
* Returns a pointer to the btree struct for the extent reference tree.
|
|
*/
|
|
struct btree *parse_extentref_btree(u64 oid)
|
|
{
|
|
struct btree *extref;
|
|
struct key last_key = {0};
|
|
|
|
extref = calloc(1, sizeof(*extref));
|
|
if (!extref)
|
|
system_error();
|
|
extref->type = BTREE_TYPE_EXTENTREF;
|
|
extref->omap_table = NULL; /* These are physical objects */
|
|
extref->root = read_node(oid, extref, NULL /* hash */);
|
|
|
|
parse_subtree(extref->root, &last_key, NULL /* name_buf */);
|
|
|
|
check_btree_footer(extref);
|
|
return extref;
|
|
}
|
|
|
|
/**
|
|
* child_from_query - Read the child id found by a successful nonleaf query
|
|
* @query: the query that found the record
|
|
*
|
|
* Returns the child id in the nonleaf node record.
|
|
*/
|
|
static u64 child_from_query(struct query *query)
|
|
{
|
|
void *raw = query->node->raw;
|
|
|
|
/* This check is actually redundant, at least for now */
|
|
if (query->len != 8) /* The data on a nonleaf node is the child id */
|
|
report("B-tree", "wrong size of nonleaf record value.");
|
|
|
|
return le64_to_cpu(*(__le64 *)(raw + query->off));
|
|
}
|
|
|
|
/**
|
|
* extref_rec_from_query - Read the info found by a successful extentref query
|
|
* @query: the query for the extent reference record
|
|
* @extref: extent reference record struct to receive the result
|
|
*/
|
|
static void extref_rec_from_query(struct query *query,
|
|
struct extref_record *extref)
|
|
{
|
|
struct apfs_phys_ext_val *extref_val;
|
|
struct apfs_phys_ext_key *extref_key;
|
|
void *raw = query->node->raw;
|
|
int kind;
|
|
|
|
if (query->len != sizeof(*extref_val))
|
|
report("Extent reference record", "wrong size of value.");
|
|
|
|
extref_val = (struct apfs_phys_ext_val *)(raw + query->off);
|
|
extref_key = (struct apfs_phys_ext_key *)(raw + query->key_off);
|
|
|
|
/* The physical address is used as the id in the extentref tree */
|
|
extref->phys_addr = cat_cnid(&extref_key->hdr);
|
|
extref->blocks = le64_to_cpu(extref_val->len_and_kind) &
|
|
APFS_PEXT_LEN_MASK;
|
|
extref->owner = le64_to_cpu(extref_val->owning_obj_id);
|
|
extref->refcnt = le32_to_cpu(extref_val->refcnt);
|
|
|
|
kind = le64_to_cpu(extref_val->len_and_kind) >> APFS_PEXT_KIND_SHIFT;
|
|
extref->update = kind == APFS_KIND_UPDATE;
|
|
}
|
|
|
|
/*
|
|
* In-memory fext record
|
|
*/
|
|
struct fext_record {
|
|
u64 log_addr; /* Logical address */
|
|
u64 phys_bno; /* Physical block number */
|
|
u64 length; /* Length (in bytes) */
|
|
};
|
|
|
|
/**
|
|
* fext_rec_from_query - Read the info found by a successful fext query
|
|
* @query: the query for the fext record
|
|
* @fext: fext record struct to receive the result
|
|
*/
|
|
static void fext_rec_from_query(struct query *query, struct fext_record *fext)
|
|
{
|
|
struct apfs_fext_tree_val *fext_val = NULL;
|
|
struct apfs_fext_tree_key *fext_key = NULL;
|
|
void *raw = query->node->raw;
|
|
|
|
if (query->len != sizeof(*fext_val))
|
|
report("Fext record", "wrong size of value.");
|
|
|
|
fext_val = (struct apfs_fext_tree_val *)(raw + query->off);
|
|
fext_key = (struct apfs_fext_tree_key *)(raw + query->key_off);
|
|
|
|
fext->log_addr = le64_to_cpu(fext_key->logical_addr);
|
|
fext->phys_bno = le64_to_cpu(fext_val->phys_block_num);
|
|
fext->length = le64_to_cpu(fext_val->len_and_flags) & APFS_FILE_EXTENT_LEN_MASK;
|
|
}
|
|
|
|
/**
|
|
* fext_tree_lookup - Map a logical address to a physical one in a sealed volume
|
|
* @oid: dstream id
|
|
* @logaddr: logical address inside the dstream
|
|
* @bno: on return, the physical block number
|
|
*
|
|
* Returns 0 on success, or -1 if nothing was found.
|
|
*/
|
|
int fext_tree_lookup(u64 oid, u64 logaddr, u64 *bno)
|
|
{
|
|
struct query *query = NULL;
|
|
struct key key = {0};
|
|
struct fext_record fext = {0};
|
|
u64 off;
|
|
int ret = -1;
|
|
|
|
query = alloc_query(vsb->v_fext->root, NULL /* parent */);
|
|
|
|
init_fext_key(oid, logaddr, &key);
|
|
query->key = &key;
|
|
query->flags |= QUERY_FEXT;
|
|
|
|
/*
|
|
* The fext nodes have already been parsed, and the allocation
|
|
* bitmap has been updated accordingly. This global variable tells
|
|
* read_object() to ignore the bitmap this time.
|
|
*/
|
|
ongoing_query = true;
|
|
if (btree_query(&query))
|
|
goto fail;
|
|
ongoing_query = false;
|
|
|
|
fext_rec_from_query(query, &fext);
|
|
if (fext.phys_bno == 0) {
|
|
*bno = 0;
|
|
} else {
|
|
off = (logaddr - fext.log_addr) >> sb->s_blocksize_bits;
|
|
*bno = fext.phys_bno + off;
|
|
}
|
|
|
|
ret = 0;
|
|
fail:
|
|
free_query(query);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* extent_from_query - Read the info found by a successful file extent query
|
|
* @query: the query for the extent record
|
|
* @fext: fext record struct to receive the result
|
|
*/
|
|
static void extent_from_query(struct query *query, struct fext_record *fext)
|
|
{
|
|
struct apfs_file_extent_val *ext;
|
|
struct apfs_file_extent_key *ext_key;
|
|
void *raw = query->node->raw;
|
|
|
|
if (query->len != sizeof(*ext))
|
|
report("File extent record", "wrong size of value.");
|
|
|
|
ext = (struct apfs_file_extent_val *)(raw + query->off);
|
|
ext_key = (struct apfs_file_extent_key *)(raw + query->key_off);
|
|
|
|
fext->log_addr = le64_to_cpu(ext_key->logical_addr);
|
|
fext->phys_bno = le64_to_cpu(ext->phys_block_num);
|
|
fext->length = le64_to_cpu(ext->len_and_flags) & APFS_FILE_EXTENT_LEN_MASK;
|
|
}
|
|
|
|
/**
|
|
* file_extent_lookup - Map a logical address to a physical one in an unsealed volume
|
|
* @oid: dstream id
|
|
* @logaddr: logical address inside the dstream
|
|
* @bno: on return, the physical block number
|
|
*
|
|
* Returns 0 on success, or -1 if nothing was found.
|
|
*/
|
|
int file_extent_lookup(u64 oid, u64 logaddr, u64 *bno)
|
|
{
|
|
struct query *query = NULL;
|
|
struct key key = {0};
|
|
struct fext_record fext = {0};
|
|
u64 off;
|
|
int ret = -1;
|
|
|
|
query = alloc_query(vsb->v_cat->root, NULL /* parent */);
|
|
|
|
init_file_extent_key(oid, logaddr, &key);
|
|
query->key = &key;
|
|
query->flags |= QUERY_CAT;
|
|
|
|
/*
|
|
* The catalog nodes have already been parsed, and the allocation
|
|
* bitmap has been updated accordingly. This global variable tells
|
|
* read_object() to ignore the bitmap this time.
|
|
*/
|
|
ongoing_query = true;
|
|
if (btree_query(&query))
|
|
goto fail;
|
|
ongoing_query = false;
|
|
|
|
extent_from_query(query, &fext);
|
|
if (fext.phys_bno == 0) {
|
|
*bno = 0;
|
|
} else {
|
|
off = (logaddr - fext.log_addr) >> sb->s_blocksize_bits;
|
|
*bno = fext.phys_bno + off;
|
|
}
|
|
|
|
ret = 0;
|
|
fail:
|
|
free_query(query);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* extentref_tree_lookup - Find best match for an extent in an extentref tree
|
|
* @tbl: root of the extent reference tree to be searched
|
|
* @bno: first block number for the extent
|
|
* @extref: extentref record struct to receive the result
|
|
*
|
|
* Returns 0 on success, or -1 if nothing was found.
|
|
*/
|
|
static int extentref_tree_lookup(struct node *tbl, u64 bno, struct extref_record *extref)
|
|
{
|
|
struct query *query;
|
|
struct key key;
|
|
int ret = -1;
|
|
|
|
query = alloc_query(tbl, NULL /* parent */);
|
|
|
|
init_extref_key(bno, &key);
|
|
query->key = &key;
|
|
query->flags |= QUERY_EXTENTREF;
|
|
|
|
/*
|
|
* The extentref nodes have already been parsed, and the allocation
|
|
* bitmap has been updated accordingly. This global variable tells
|
|
* read_object() to ignore the bitmap this time.
|
|
*/
|
|
ongoing_query = true;
|
|
if (btree_query(&query))
|
|
goto fail;
|
|
ongoing_query = false;
|
|
|
|
extref_rec_from_query(query, extref);
|
|
ret = 0;
|
|
fail:
|
|
free_query(query);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* extentref_update_lookup - Find latest snap phys extent for an updated block
|
|
* @bno: block number
|
|
* @extref: extentref record struct to receive the result
|
|
*/
|
|
void extentref_update_lookup(u64 bno, struct extref_record *extref)
|
|
{
|
|
struct listed_btree *ext_tree = NULL;
|
|
int ret;
|
|
|
|
/* We look at the most recent snapshots first */
|
|
for (ext_tree = vsb->v_snap_extrefs; ext_tree; ext_tree = ext_tree->next) {
|
|
ret = extentref_tree_lookup(ext_tree->btree->root, bno, extref);
|
|
if (ret == 0 && extref->phys_addr <= bno && extref->phys_addr + extref->blocks > bno)
|
|
return;
|
|
}
|
|
|
|
report("Physical extent record", "update of nonexistent record.");
|
|
}
|
|
|
|
/**
|
|
* alloc_query - Allocates a query structure
|
|
* @node: node to be searched
|
|
* @parent: query for the parent node
|
|
*
|
|
* Callers other than btree_query() should set @parent to NULL, and @node
|
|
* to the root of the b-tree. They should also initialize most of the query
|
|
* fields themselves; when @parent is not NULL the query will inherit them.
|
|
*
|
|
* Returns the allocated query.
|
|
*/
|
|
struct query *alloc_query(struct node *node, struct query *parent)
|
|
{
|
|
struct query *query;
|
|
|
|
query = malloc(sizeof(*query));
|
|
if (!query)
|
|
system_error();
|
|
|
|
query->node = node;
|
|
query->key = parent ? parent->key : NULL;
|
|
query->flags = parent ? parent->flags & ~(QUERY_DONE | QUERY_NEXT) : 0;
|
|
query->parent = parent;
|
|
/* Start the search with the last record and go backwards */
|
|
query->index = node->records;
|
|
query->depth = parent ? parent->depth + 1 : 0;
|
|
|
|
return query;
|
|
}
|
|
|
|
/**
|
|
* free_query - Free a query structure
|
|
* @query: query to free
|
|
*
|
|
* Also frees the ancestor queries, if they are kept.
|
|
*/
|
|
void free_query(struct query *query)
|
|
{
|
|
while (query) {
|
|
struct query *parent = query->parent;
|
|
|
|
node_free(query->node);
|
|
free(query);
|
|
query = parent;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* key_from_query - Read the current key from a query structure
|
|
* @query: the query, with @query->key_off and @query->key_len already set
|
|
* @key: return parameter for the key
|
|
*
|
|
* Reads the key into @key after some basic sanity checks.
|
|
*/
|
|
static void key_from_query(struct query *query, struct key *key)
|
|
{
|
|
void *raw = query->node->raw;
|
|
void *raw_key = (void *)(raw + query->key_off);
|
|
|
|
switch (query->flags & QUERY_TREE_MASK) {
|
|
case QUERY_CAT:
|
|
read_cat_key(raw_key, query->key_len, key);
|
|
break;
|
|
case QUERY_OMAP:
|
|
read_omap_key(raw_key, query->key_len, key);
|
|
break;
|
|
case QUERY_EXTENTREF:
|
|
read_extentref_key(raw_key, query->key_len, key);
|
|
break;
|
|
case QUERY_FEXT:
|
|
read_fext_key(raw_key, query->key_len, key);
|
|
break;
|
|
default:
|
|
report(NULL, "Bug!");
|
|
}
|
|
|
|
if (query->flags & QUERY_MULTIPLE) {
|
|
/* A multiple query must ignore these fields */
|
|
key->number = 0;
|
|
key->name = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* node_next - Find the next matching record in the current node
|
|
* @query: multiple query in execution
|
|
*
|
|
* Returns 0 on success, -EAGAIN if the next record is in another node, and
|
|
* -ENODATA if no more matching records exist.
|
|
*/
|
|
static int node_next(struct query *query)
|
|
{
|
|
struct node *node = query->node;
|
|
struct key curr_key;
|
|
int cmp;
|
|
u64 bno = node->object.block_nr;
|
|
|
|
if (query->flags & QUERY_DONE)
|
|
/* Nothing left to search; the query failed */
|
|
return -ENODATA;
|
|
|
|
if (!query->index) /* The next record may be in another node */
|
|
return -EAGAIN;
|
|
--query->index;
|
|
|
|
query->key_len = node_locate_key(node, query->index, &query->key_off);
|
|
key_from_query(query, &curr_key);
|
|
|
|
cmp = keycmp(&curr_key, query->key);
|
|
|
|
if (cmp > 0)
|
|
report("B-tree", "records are out of order.");
|
|
|
|
if (cmp != 0 && node_is_leaf(node) && query->flags & QUERY_EXACT)
|
|
return -ENODATA;
|
|
|
|
query->len = node_locate_data(node, query->index, &query->off);
|
|
if (query->len == 0) {
|
|
report("B-tree", "corrupted record value in node 0x%llx.",
|
|
(unsigned long long)bno);
|
|
}
|
|
|
|
if (cmp != 0) {
|
|
/*
|
|
* This is the last entry that can be relevant in this node.
|
|
* Keep searching the children, but don't return to this level.
|
|
*/
|
|
query->flags |= QUERY_DONE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* node_query - Execute a query on a single node
|
|
* @query: the query to execute
|
|
*
|
|
* The search will start at index @query->index, looking for the key that comes
|
|
* right before @query->key, according to the order given by keycmp().
|
|
*
|
|
* The @query->index will be updated to the last index checked. This is
|
|
* important when searching for multiple entries, since the query may need
|
|
* to remember where it was on this level. If we are done with this node, the
|
|
* query will be flagged as QUERY_DONE, and the search will end in failure
|
|
* as soon as we return to this level. The function may also return -EAGAIN,
|
|
* to signal that the search should go on in a different branch.
|
|
*
|
|
* On success returns 0; the offset of the data within the block will be saved
|
|
* in @query->off, and its length in @query->len. The function checks that this
|
|
* length fits within the block; callers must use the returned value to make
|
|
* sure they never operate outside its bounds.
|
|
*
|
|
* -ENODATA will be returned if no appropriate entry was found.
|
|
*/
|
|
static int node_query(struct query *query)
|
|
{
|
|
struct node *node = query->node;
|
|
int left, right;
|
|
int cmp;
|
|
u64 bno = node->object.block_nr;
|
|
|
|
if (query->flags & QUERY_NEXT)
|
|
return node_next(query);
|
|
|
|
/* Search by bisection */
|
|
cmp = 1;
|
|
left = 0;
|
|
do {
|
|
struct key curr_key;
|
|
if (cmp > 0) {
|
|
right = query->index - 1;
|
|
if (right < left)
|
|
return -ENODATA;
|
|
query->index = (left + right) / 2;
|
|
} else {
|
|
left = query->index;
|
|
query->index = DIV_ROUND_UP(left + right, 2);
|
|
}
|
|
|
|
query->key_len = node_locate_key(node, query->index,
|
|
&query->key_off);
|
|
key_from_query(query, &curr_key);
|
|
|
|
cmp = keycmp(&curr_key, query->key);
|
|
if (cmp == 0 && !(query->flags & QUERY_MULTIPLE))
|
|
break;
|
|
} while (left != right);
|
|
|
|
if (cmp > 0)
|
|
return -ENODATA;
|
|
|
|
if (cmp != 0 && node_is_leaf(query->node) && query->flags & QUERY_EXACT)
|
|
return -ENODATA;
|
|
|
|
if (query->flags & QUERY_MULTIPLE) {
|
|
if (cmp != 0) /* Last relevant entry in level */
|
|
query->flags |= QUERY_DONE;
|
|
query->flags |= QUERY_NEXT;
|
|
}
|
|
|
|
query->len = node_locate_data(node, query->index, &query->off);
|
|
if (query->len == 0) {
|
|
report("B-tree", "corrupted record value in node 0x%llx.",
|
|
(unsigned long long)bno);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* btree_query - Execute a query on a b-tree
|
|
* @query: the query to execute
|
|
*
|
|
* Searches the b-tree starting at @query->index in @query->node, looking for
|
|
* the record corresponding to @query->key.
|
|
*
|
|
* Returns 0 in case of success and sets the @query->len, @query->off and
|
|
* @query->index fields to the results of the query. @query->node will now
|
|
* point to the leaf node holding the record.
|
|
*
|
|
* In case of failure returns -ENODATA.
|
|
*/
|
|
int btree_query(struct query **query)
|
|
{
|
|
struct node *node = (*query)->node;
|
|
struct query *parent;
|
|
struct btree *btree = node->btree;
|
|
u64 child_id;
|
|
int err;
|
|
|
|
assert(ongoing_query);
|
|
|
|
next_node:
|
|
if ((*query)->depth >= 12) {
|
|
/* This is the maximum depth allowed by the module */
|
|
report("B-tree", "is too deep.");
|
|
}
|
|
|
|
err = node_query(*query);
|
|
if (err == -EAGAIN) {
|
|
if (!(*query)->parent) /* We are at the root of the tree */
|
|
return -ENODATA;
|
|
|
|
/* Move back up one level and continue the query */
|
|
parent = (*query)->parent;
|
|
(*query)->parent = NULL; /* Don't free the parent */
|
|
free_query(*query);
|
|
*query = parent;
|
|
goto next_node;
|
|
}
|
|
if (err)
|
|
return err;
|
|
if (node_is_leaf((*query)->node)) /* All done */
|
|
return 0;
|
|
|
|
/* Now go a level deeper and search the child */
|
|
child_id = child_from_query(*query);
|
|
node = read_node(child_id, btree, NULL /* hash */);
|
|
|
|
if ((*query)->flags & QUERY_MULTIPLE) {
|
|
/*
|
|
* We are looking for multiple entries, so we must remember
|
|
* the parent node and index to continue the search later.
|
|
*/
|
|
*query = alloc_query(node, *query);
|
|
} else {
|
|
/* Reuse the same query structure to search the child */
|
|
node_free((*query)->node);
|
|
(*query)->node = node;
|
|
(*query)->index = node->records;
|
|
(*query)->depth++;
|
|
}
|
|
goto next_node;
|
|
}
|