apfs: implement block allocation

Define the on-disk structures for the space manager in a new spaceman.h
header file; complete apfs_read_spaceman() and move it into spaceman.c.
With this in place, implement apfs_spaceman_allocate_block(), which
allocates a single block, as well as any more that are needed for the
spaceman metadata itself.

To test the new function, allocate a new block for the container's
object map.

I would have preferred to implement just the block allocation in this
patch, which is messy enough, but it's necessary to release the old
blocks for the filesystem to remain consistent.  Don't mark them as free
in the bitmaps yet, just add them to the free queue b-tree using the new
apfs_free_queue_insert().  To keep things simple, don't implement a
full-featured apfs_btree_insert() yet; this first draft will hit ENOSPC
very soon, but it's good enough for testing.

Free queue queries are needed here, so implement the key functions
apfs_read_free_queue_key() and apfs_init_free_queue_key(); also adjust
apfs_node_locate_data(), and allow a return value of 0 to be valid, as
expected for ghost records.

To know where to put the new entry, apfs_btree_insert() expects the
index reported by apfs_node_query() to always come right before the
correct location; set it to -1 on ENODATA instead of ignoring it.

Signed-off-by: Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
This commit is contained in:
Ernesto A. Fernández
2019-07-15 18:45:42 -03:00
parent f56396e3b3
commit 3bdb0a8032
10 changed files with 877 additions and 53 deletions
+2 -2
View File
@@ -5,5 +5,5 @@
obj-$(CONFIG_APFS_FS) += apfs.o
apfs-y := btree.o dir.o extents.o file.o inode.o key.o message.o \
namei.o node.o object.o super.o symlink.o unicode.o xattr.o
apfs-y := btree.o dir.o extents.o file.o inode.o key.o message.o namei.o \
node.o object.o spaceman.o super.o symlink.o unicode.o xattr.o
+60
View File
@@ -251,3 +251,63 @@ struct apfs_node *apfs_omap_read_node(struct super_block *sb, u64 id)
return result;
}
/**
* apfs_btree_insert - Insert a new record into a b-tree
* @query: query run to search for the record
* @key: on-disk record key
* @key_len: length of @key
* @val: on-disk record value (NULL for ghost records)
* @val_len: length of @val (0 for ghost records)
*
* The new record is placed right before the one found by @query. Returns 0 on
* success, or a negative error code in case of failure.
*/
int apfs_btree_insert(struct apfs_query *query, void *key, int key_len,
void *val, int val_len)
{
struct apfs_node *node = query->node;
struct super_block *sb = node->object.sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_btree_node_phys *node_raw;
struct apfs_kvoff *toc_entry;
struct apfs_btree_info *info;
/*
* This function is a very rough first draft; all we need is to add
* a few records to an empty free queue tree.
*/
ASSERT(apfs_node_has_fixed_kv_size(node));
ASSERT(!val && !val_len);
ASSERT(apfs_node_is_root(node) && apfs_node_is_leaf(node));
node_raw = (void *)query->node->object.bh->b_data;
ASSERT(sbi->s_xid == le64_to_cpu(node_raw->btn_o.o_xid));
/* TODO: support toc resizing and record fragmentation */
if (sizeof(*node_raw) +
(node->records + 1) * sizeof(*toc_entry) > node->key)
return -ENOSPC;
if (node->free + key_len > node->data)
return -ENOSPC;
query->index++; /* The query returned the record right before @key */
toc_entry = (struct apfs_kvoff *)node_raw->btn_data + query->index;
memmove(toc_entry + 1, toc_entry,
(node->records - query->index) * sizeof(*toc_entry));
toc_entry->v = cpu_to_le16(APFS_BTOFF_INVALID); /* Ghost record */
memcpy((void *)node_raw + node->free, key, key_len);
toc_entry->k = cpu_to_le16(node->free - node->key);
node->free += key_len;
le16_add_cpu(&node_raw->btn_free_space.off, key_len);
le16_add_cpu(&node_raw->btn_free_space.len, -key_len);
node_raw->btn_nkeys = cpu_to_le32(++node->records);
info = (void *)node_raw + sb->s_blocksize - sizeof(*info);
le64_add_cpu(&info->bt_key_count, 1);
apfs_obj_set_csum(sb, &node_raw->btn_o);
mark_buffer_dirty(node->object.bh);
return 0;
}
+3
View File
@@ -16,6 +16,7 @@ struct super_block;
#define APFS_QUERY_TREE_MASK 0007 /* Which b-tree we query */
#define APFS_QUERY_OMAP 0001 /* This is a b-tree object map query */
#define APFS_QUERY_CAT 0002 /* This is a catalog tree query */
#define APFS_QUERY_FREE_QUEUE 0004 /* This is a free queue query */
#define APFS_QUERY_NEXT 0010 /* Find next of multiple matches */
#define APFS_QUERY_EXACT 0020 /* Search for an exact match */
#define APFS_QUERY_DONE 0040 /* The search at this level is over */
@@ -51,5 +52,7 @@ extern int apfs_btree_query(struct super_block *sb, struct apfs_query **query);
extern struct apfs_node *apfs_omap_read_node(struct super_block *sb, u64 id);
extern int apfs_omap_lookup_block(struct super_block *sb,
struct apfs_node *tbl, u64 id, u64 *block);
extern int apfs_btree_insert(struct apfs_query *query, void *key, int key_len,
void *val, int val_len);
#endif /* _APFS_BTREE_H */
+24
View File
@@ -147,6 +147,30 @@ int apfs_read_cat_key(void *raw, int size, struct apfs_key *key)
return 0;
}
/**
* apfs_read_free_queue_key - Parse an on-disk free queue key
* @raw: pointer to the raw key
* @size: size of the raw key
* @key: apfs_key structure to store the result
*
* Returns 0 on success, or a negative error code otherwise.
*/
int apfs_read_free_queue_key(void *raw, int size, struct apfs_key *key)
{
struct apfs_spaceman_free_queue_key *raw_key;
if (size < sizeof(struct apfs_spaceman_free_queue_key))
return -EFSCORRUPTED;
raw_key = raw;
key->id = le64_to_cpu(raw_key->sfqk_xid);
key->type = 0;
key->number = le64_to_cpu(raw_key->sfqk_paddr);
key->name = NULL;
return 0;
}
/**
* apfs_read_omap_key - Parse an on-disk object map key
* @raw: pointer to the raw key
+24
View File
@@ -12,6 +12,14 @@
struct super_block;
/*
* Structure of a key in a free-space queue b-tree
*/
struct apfs_spaceman_free_queue_key {
__le64 sfqk_xid;
__le64 sfqk_paddr;
} __packed;
/*
* Structure of a key in an object map B-tree
*/
@@ -115,6 +123,21 @@ struct apfs_key {
u8 type; /* Record type (0 for the omap) */
};
/**
* apfs_init_free_queue_key - Initialize an in-memory key for a free queue query
* @xid: transaction id
* @paddr: block number
* @key: apfs_key structure to initialize
*/
static inline void apfs_init_free_queue_key(u64 xid, u64 paddr,
struct apfs_key *key)
{
key->id = xid;
key->type = 0;
key->number = paddr;
key->name = NULL;
}
/**
* apfs_init_omap_key - Initialize an in-memory key for an omap query
* @oid: object id
@@ -180,6 +203,7 @@ extern int apfs_filename_cmp(struct super_block *sb,
extern int apfs_keycmp(struct super_block *sb,
struct apfs_key *k1, struct apfs_key *k2);
extern int apfs_read_cat_key(void *raw, int size, struct apfs_key *key);
extern int apfs_read_free_queue_key(void *raw, int size, struct apfs_key *key);
extern int apfs_read_omap_key(void *raw, int size, struct apfs_key *key);
#endif /* _APFS_KEY_H */
+23 -9
View File
@@ -169,9 +169,10 @@ static int apfs_node_locate_key(struct apfs_node *node, int index, int *off)
* @index: number of the entry to locate
* @off: on return will hold the offset in the block
*
* Returns the length of the data, or 0 in case of failure. 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.
* Returns the length of the data, which may be 0 in case of corruption or if
* the record is a ghost. 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.
*/
static int apfs_node_locate_data(struct apfs_node *node, int index, int *off)
{
@@ -186,10 +187,18 @@ static int apfs_node_locate_data(struct apfs_node *node, int index, int *off)
if (apfs_node_has_fixed_kv_size(node)) {
/* These node types have fixed length keys and data */
struct apfs_kvoff *entry;
u32 subtype = le32_to_cpu(raw->btn_o.o_subtype);
entry = (struct apfs_kvoff *)raw->btn_data + index;
/* Node type decides length */
len = apfs_node_is_leaf(node) ? 16 : 8;
if (subtype == APFS_OBJECT_TYPE_SPACEMAN_FREE_QUEUE) {
/* A free-space queue record may have no value */
if (le16_to_cpu(entry->v) == APFS_BTOFF_INVALID)
return 0;
len = 8;
} else {
/* This is an object-map node */
len = apfs_node_is_leaf(node) ? 16 : 8;
}
/*
* Data offsets are counted backwards from the end of the
* block, or from the beginning of the footer when it exists
@@ -246,6 +255,9 @@ static int apfs_key_from_query(struct apfs_query *query, struct apfs_key *key)
case APFS_QUERY_OMAP:
err = apfs_read_omap_key(raw_key, query->key_len, key);
break;
case APFS_QUERY_FREE_QUEUE:
err = apfs_read_free_queue_key(raw_key, query->key_len, key);
break;
default:
/* Not implemented yet */
err = -EINVAL;
@@ -358,8 +370,10 @@ int apfs_node_query(struct super_block *sb, struct apfs_query *query)
struct apfs_key curr_key;
if (cmp > 0) {
right = query->index - 1;
if (right < left)
if (right < left) {
query->index = -1;
return -ENODATA;
}
query->index = (left + right) / 2;
} else {
left = query->index;
@@ -377,8 +391,10 @@ int apfs_node_query(struct super_block *sb, struct apfs_query *query)
break;
} while (left != right);
if (cmp > 0)
if (cmp > 0) {
query->index = -1;
return -ENODATA;
}
if (cmp != 0 && apfs_node_is_leaf(query->node) &&
query->flags & APFS_QUERY_EXACT)
@@ -391,8 +407,6 @@ int apfs_node_query(struct super_block *sb, struct apfs_query *query)
}
query->len = apfs_node_locate_data(node, query->index, &query->off);
if (query->len == 0)
return -EFSCORRUPTED;
return 0;
}
+524
View File
File diff suppressed because it is too large Load Diff
+180
View File
@@ -0,0 +1,180 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* linux/fs/apfs/spaceman.h
*
* Copyright (C) 2019 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
*/
#ifndef _APFS_SPACEMAN_H
#define _APFS_SPACEMAN_H
#include <linux/types.h>
/*
* On-disk allocation info for a chunk of blocks
*/
struct apfs_chunk_info {
__le64 ci_xid;
__le64 ci_addr;
__le32 ci_block_count;
__le32 ci_free_count;
__le64 ci_bitmap_addr;
} __packed;
/* Constants for the chunk info block */
#define APFS_CI_COUNT_MASK 0x000FFFFF
#define APFS_CI_COUNT_RESERVED_MASK 0xFFF00000
/*
* Structure of a block with an array of chunk allocation info structures
*/
struct apfs_chunk_info_block {
struct apfs_obj_phys cib_o;
__le32 cib_index;
__le32 cib_chunk_info_count;
struct apfs_chunk_info cib_chunk_info[];
} __packed;
/*
* Structure of a block with an array of addresses to chunk information blocks
*/
struct apfs_cib_addr_block {
struct apfs_obj_phys cab_o;
__le32 cab_index;
__le32 cab_cib_count;
__le64 cab_cib_addr[];
} __packed;
/*
* On-disk structure for a free queue
*/
struct apfs_spaceman_free_queue {
__le64 sfq_count;
__le64 sfq_tree_oid;
__le64 sfq_oldest_xid;
__le16 sfq_tree_node_limit;
__le16 sfq_pad16;
__le32 sfq_pad32;
__le64 sfq_reserved;
} __packed;
/* Indexes for a free queue array */
enum {
APFS_SFQ_IP = 0,
APFS_SFQ_MAIN = 1,
APFS_SFQ_TIER2 = 2,
APFS_SFQ_COUNT = 3
};
/*
* On-disk structure for device allocation information
*/
struct apfs_spaceman_device {
__le64 sm_block_count;
__le64 sm_chunk_count;
__le32 sm_cib_count;
__le32 sm_cab_count;
__le64 sm_free_count;
__le32 sm_addr_offset;
__le32 sm_reserved;
__le64 sm_reserved2;
} __packed;
/* Indexes for a device array */
enum {
APFS_SD_MAIN = 0,
APFS_SD_TIER2 = 1,
APFS_SD_COUNT = 2
};
/*
* On-disk structure to describe allocation zone boundaries
*/
struct apfs_spaceman_allocation_zone_boundaries {
__le64 saz_zone_start;
__le64 saz_zone_end;
} __packed;
/* Allocation zone constants */
#define APFS_SM_ALLOCZONE_INVALID_END_BOUNDARY 0
#define APFS_SM_ALLOCZONE_NUM_PREVIOUS_BOUNDARIES 7
struct apfs_spaceman_allocation_zone_info_phys {
struct apfs_spaceman_allocation_zone_boundaries saz_current_boundaries;
struct apfs_spaceman_allocation_zone_boundaries
saz_previous_boundaries[APFS_SM_ALLOCZONE_NUM_PREVIOUS_BOUNDARIES];
__le16 saz_zone_id;
__le16 saz_previous_boundary_index;
__le32 saz_reserved;
} __packed;
/* Datazone constants */
#define APFS_SM_DATAZONE_ALLOCZONE_COUNT 8
struct apfs_spaceman_datazone_info_phys {
struct apfs_spaceman_allocation_zone_info_phys
sdz_allocation_zones[APFS_SD_COUNT][APFS_SM_DATAZONE_ALLOCZONE_COUNT];
} __packed;
/* Internal-pool bitmap constants */
#define APFS_SPACEMAN_IP_BM_TX_MULTIPLIER 16
#define APFS_SPACEMAN_IP_BM_INDEX_INVALID 0xFFFF
#define APFS_SPACEMAN_IP_BM_BLOCK_COUNT_MAX 0xFFFE
/* Space manager flags */
#define APFS_SM_FLAG_VERSIONED 0x00000001
#define APFS_SM_FLAGS_VALID_MASK APFS_SM_FLAG_VERSIONED
/*
* On-disk structure for the space manager
*/
struct apfs_spaceman_phys {
struct apfs_obj_phys sm_o;
__le32 sm_block_size;
__le32 sm_blocks_per_chunk;
__le32 sm_chunks_per_cib;
__le32 sm_cibs_per_cab;
struct apfs_spaceman_device sm_dev[APFS_SD_COUNT];
__le32 sm_flags;
__le32 sm_ip_bm_tx_multiplier;
__le64 sm_ip_block_count;
__le32 sm_ip_bm_size_in_blocks;
__le32 sm_ip_bm_block_count;
__le64 sm_ip_bm_base;
__le64 sm_ip_base;
__le64 sm_fs_reserve_block_count;
__le64 sm_fs_reserve_alloc_count;
struct apfs_spaceman_free_queue sm_fq[APFS_SFQ_COUNT];
__le16 sm_ip_bm_free_head;
__le16 sm_ip_bm_free_tail;
__le32 sm_ip_bm_xid_offset;
__le32 sm_ip_bitmap_offset;
__le32 sm_ip_bm_free_next_offset;
__le32 sm_version;
__le32 sm_struct_size;
struct apfs_spaceman_datazone_info_phys sm_datazone;
} __packed;
/*
* Space manager data in memory.
*/
struct apfs_spaceman {
struct apfs_spaceman_phys *sm_raw; /* On-disk spaceman structure */
struct buffer_head *sm_bh; /* Buffer head for @sm_raw */
int sm_struct_size; /* Actual size of @sm_raw */
u32 sm_blocks_per_chunk; /* Blocks covered by a bitmap block */
u32 sm_chunks_per_cib; /* Chunk count in a chunk-info block */
u64 sm_block_count; /* Block count for the container */
u64 sm_chunk_count; /* Number of bitmap blocks */
u32 sm_cib_count; /* Number of chunk-info blocks */
u64 sm_free_count; /* Number of free blocks */
u32 sm_addr_offset; /* Offset of cib addresses in @sm_raw */
};
extern int apfs_read_spaceman(struct super_block *sb);
extern int apfs_free_queue_insert(struct super_block *sb, u64 bno);
extern int apfs_spaceman_allocate_block(struct super_block *sb, u64 *bno);
#endif /* _APFS_SPACEMAN_H */
+34 -42
View File
@@ -244,6 +244,40 @@ static int apfs_map_volume_super(struct super_block *sb)
goto fail;
}
/* Add the object map to the next transaction */
if (!(sb->s_flags & SB_RDONLY)) {
struct buffer_head *new_bh;
struct apfs_obj_phys *new_obj;
u64 new_bno;
err = apfs_spaceman_allocate_block(sb, &new_bno);
if (err)
goto fail;
new_bh = sb_bread(sb, new_bno);
if (!new_bh) {
err = -EINVAL;
goto fail;
}
memcpy(new_bh->b_data, bh->b_data, sb->s_blocksize);
err = apfs_free_queue_insert(sb, bh->b_blocknr);
brelse(bh);
msb_omap_raw = (struct apfs_omap_phys *)new_bh->b_data;
bh = new_bh;
new_bh = NULL;
if (err)
goto fail;
new_obj = &msb_omap_raw->om_o;
new_obj->o_xid = cpu_to_le64(sbi->s_xid);
new_obj->o_oid = cpu_to_le64(new_bno);
apfs_obj_set_csum(sb, new_obj);
mark_buffer_dirty(bh);
msb_raw->nx_omap_oid = cpu_to_le64(new_bno);
mark_buffer_dirty(sbi->s_mobject.bh);
}
/* Get the Volume Block */
vb = le64_to_cpu(msb_omap_raw->om_tree_oid);
msb_omap_raw = NULL;
@@ -582,48 +616,6 @@ static void apfs_checkpoint_end(struct super_block *sb)
sync_dirty_buffer(bh);
}
/**
* apfs_read_spaceman - Find and read the space manager
* @sb: superblock structure
*
* For now only reads the space manager structure from disk and runs a few
* checks for testing. Returns 0 on success, or a negative error code in
* case of failure.
*/
static int apfs_read_spaceman(struct super_block *sb)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_nx_superblock *raw_sb = sbi->s_msb_raw;
struct buffer_head *bh;
struct apfs_obj_phys *obj;
u64 oid = le64_to_cpu(raw_sb->nx_spaceman_oid);
int err = 0;
if (sb->s_flags & SB_RDONLY) /* The space manager won't be needed */
return 0;
bh = apfs_read_ephemeral_object(sb, oid);
if (IS_ERR(bh))
return PTR_ERR(bh);
obj = (struct apfs_obj_phys *)bh->b_data;
if (sbi->s_flags & APFS_CHECK_NODES && !apfs_obj_verify_csum(sb, obj)) {
apfs_err(sb, "bad checksum for the space manager");
err = -EFSBADCRC;
goto fail;
}
if (le64_to_cpu(obj->o_oid) != oid)
err = -EFSCORRUPTED;
if (le32_to_cpu(obj->o_type) != (APFS_OBJECT_TYPE_SPACEMAN |
APFS_OBJ_EPHEMERAL))
err = -EFSCORRUPTED;
fail:
brelse(bh);
return err;
}
/**
* apfs_read_omap - Find and read the omap root node
* @sb: superblock structure
+3
View File
@@ -11,6 +11,7 @@
#include <linux/fs.h>
#include <linux/types.h>
#include "object.h"
#include "spaceman.h"
/*
* Structure used to store a range of physical blocks
@@ -311,6 +312,8 @@ struct apfs_sb_info {
/* TODO: handle block sizes above the maximum of PAGE_SIZE? */
unsigned long s_blocksize;
unsigned char s_blocksize_bits;
struct apfs_spaceman s_spaceman;
};
static inline struct apfs_sb_info *APFS_SB(struct super_block *sb)