You've already forked linux-apfs-rw
mirror of
https://github.com/linux-apfs/linux-apfs-rw.git
synced 2026-05-01 15:01:34 -07:00
Implement deletion of ephemeral blocks
Since I don't deal well with internal fragmentation, free queue nodes that have been flushed end up getting full and splitting anyway. The result are two almost empty nodes, so the following flushes quickly require a node deletion. Implement it. I still need to deal with internal fragmentation soon, though. In my test filesystem, the free queue for the internal pool is not really allowed to ever have more than one node, and ignoring that restriction could bring problems with the Apple driver. Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
This commit is contained in:
@@ -644,6 +644,7 @@ extern int apfs_obj_verify_csum(struct super_block *sb,
|
||||
extern void apfs_obj_set_csum(struct super_block *sb,
|
||||
struct apfs_obj_phys *obj);
|
||||
extern int apfs_create_cpoint_map(struct super_block *sb, u64 oid, u64 bno);
|
||||
extern int apfs_remove_cpoint_map(struct super_block *sb, u64 bno);
|
||||
extern struct buffer_head *apfs_read_ephemeral_object(struct super_block *sb,
|
||||
u64 oid);
|
||||
extern struct buffer_head *apfs_read_object_block(struct super_block *sb,
|
||||
@@ -661,6 +662,7 @@ extern int apfs_read_catalog(struct super_block *sb, bool write);
|
||||
|
||||
/* transaction.c */
|
||||
extern void apfs_cpoint_data_allocate(struct super_block *sb, u64 *bno);
|
||||
extern int apfs_cpoint_data_free(struct super_block *sb, u64 bno);
|
||||
extern int apfs_transaction_start(struct super_block *sb);
|
||||
extern int apfs_transaction_commit(struct super_block *sb);
|
||||
extern int apfs_transaction_join(struct super_block *sb,
|
||||
|
||||
@@ -261,6 +261,14 @@ int apfs_delete_node(struct apfs_query *query)
|
||||
u64 bno = node->object.block_nr;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* For ephemeral nodes, it's important to do this before actually
|
||||
* deleting the node, because that involves moving blocks around.
|
||||
*/
|
||||
err = apfs_btree_remove(query->parent);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
switch (query->flags & APFS_QUERY_TREE_MASK) {
|
||||
case APFS_QUERY_CAT:
|
||||
err = apfs_free_queue_insert(sb, bno);
|
||||
@@ -272,7 +280,7 @@ int apfs_delete_node(struct apfs_query *query)
|
||||
vsb_raw = APFS_SB(sb)->s_vsb_raw;
|
||||
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
||||
le64_add_cpu(&vsb_raw->apfs_fs_alloc_count, -1);
|
||||
break;
|
||||
return 0;
|
||||
case APFS_QUERY_OMAP:
|
||||
err = apfs_free_queue_insert(sb, bno);
|
||||
if (err)
|
||||
@@ -281,12 +289,18 @@ int apfs_delete_node(struct apfs_query *query)
|
||||
vsb_raw = APFS_SB(sb)->s_vsb_raw;
|
||||
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
||||
le64_add_cpu(&vsb_raw->apfs_fs_alloc_count, -1);
|
||||
break;
|
||||
return 0;
|
||||
case APFS_QUERY_FREE_QUEUE:
|
||||
err = apfs_cpoint_data_free(sb, bno);
|
||||
if (err)
|
||||
return err;
|
||||
err = apfs_remove_cpoint_map(sb, bno);
|
||||
if (err)
|
||||
return err;
|
||||
return 0;
|
||||
default:
|
||||
/* TODO: ephemeral nodes */
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
return apfs_btree_remove(query->parent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -85,6 +85,30 @@ static int apfs_cpm_lookup_oid(struct super_block *sb,
|
||||
return -EAGAIN; /* The mapping may still be in the next block */
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_read_cpm_block - Read the checkpoint mapping block
|
||||
* @sb: super block structure
|
||||
*
|
||||
* Only a single cpm block is supported for now. Returns the buffer head for
|
||||
* the block on success, or NULL in case of failure.
|
||||
*/
|
||||
static struct buffer_head *apfs_read_cpm_block(struct super_block *sb)
|
||||
{
|
||||
struct apfs_nx_superblock *raw_sb = APFS_NXI(sb)->nx_raw;
|
||||
u64 desc_base = le64_to_cpu(raw_sb->nx_xp_desc_base);
|
||||
u32 desc_index = le32_to_cpu(raw_sb->nx_xp_desc_index);
|
||||
u32 desc_blks = le32_to_cpu(raw_sb->nx_xp_desc_blocks);
|
||||
u32 desc_len = le32_to_cpu(raw_sb->nx_xp_desc_len);
|
||||
u64 cpm_bno;
|
||||
|
||||
if (!desc_blks || desc_len < 2)
|
||||
return NULL;
|
||||
|
||||
/* Last block in area is superblock; we want the last mapping block */
|
||||
cpm_bno = desc_base + (desc_index + desc_len - 2) % desc_blks;
|
||||
return apfs_sb_bread(sb, cpm_bno);
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_create_cpoint_map - Create a checkpoint mapping
|
||||
* @sb: filesystem superblock
|
||||
@@ -96,24 +120,13 @@ static int apfs_cpm_lookup_oid(struct super_block *sb,
|
||||
*/
|
||||
int apfs_create_cpoint_map(struct super_block *sb, u64 oid, u64 bno)
|
||||
{
|
||||
struct apfs_nx_superblock *raw_sb = APFS_NXI(sb)->nx_raw;
|
||||
u64 desc_base = le64_to_cpu(raw_sb->nx_xp_desc_base);
|
||||
u32 desc_index = le32_to_cpu(raw_sb->nx_xp_desc_index);
|
||||
u32 desc_blks = le32_to_cpu(raw_sb->nx_xp_desc_blocks);
|
||||
u32 desc_len = le32_to_cpu(raw_sb->nx_xp_desc_len);
|
||||
struct buffer_head *bh;
|
||||
struct apfs_checkpoint_map_phys *cpm;
|
||||
struct apfs_checkpoint_mapping *map;
|
||||
u64 cpm_bno;
|
||||
u32 cpm_count;
|
||||
int err = 0;
|
||||
|
||||
if (!desc_blks || desc_len < 2)
|
||||
return -EFSCORRUPTED;
|
||||
|
||||
/* Last block in area is superblock; we want the last mapping block */
|
||||
cpm_bno = desc_base + (desc_index + desc_len - 2) % desc_blks;
|
||||
bh = apfs_sb_bread(sb, cpm_bno);
|
||||
bh = apfs_read_cpm_block(sb);
|
||||
if (!bh)
|
||||
return -EIO;
|
||||
cpm = (struct apfs_checkpoint_map_phys *)bh->b_data;
|
||||
@@ -142,6 +155,96 @@ fail:
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_index_in_data_area - Get position of block in current checkpoint's data
|
||||
* @sb: superblock structure
|
||||
* @bno: block number
|
||||
*
|
||||
* TODO: reuse this function and apfs_data_index_to_bno(), and do the same for
|
||||
* the descriptor area.
|
||||
*/
|
||||
static inline u32 apfs_index_in_data_area(struct super_block *sb, u64 bno)
|
||||
{
|
||||
struct apfs_nx_superblock *raw_sb = APFS_NXI(sb)->nx_raw;
|
||||
u64 data_base = le64_to_cpu(raw_sb->nx_xp_data_base);
|
||||
u32 data_index = le32_to_cpu(raw_sb->nx_xp_data_index);
|
||||
u32 data_blks = le32_to_cpu(raw_sb->nx_xp_data_blocks);
|
||||
|
||||
return (bno - data_base + data_blks - data_index) % data_blks;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_data_index_to_bno - Convert index in data area to block number
|
||||
* @sb: superblock structure
|
||||
* @index: index of the block in the current checkpoint's data area
|
||||
*/
|
||||
static inline u64 apfs_data_index_to_bno(struct super_block *sb, u32 index)
|
||||
{
|
||||
struct apfs_nx_superblock *raw_sb = APFS_NXI(sb)->nx_raw;
|
||||
u64 data_base = le64_to_cpu(raw_sb->nx_xp_data_base);
|
||||
u32 data_index = le32_to_cpu(raw_sb->nx_xp_data_index);
|
||||
u32 data_blks = le32_to_cpu(raw_sb->nx_xp_data_blocks);
|
||||
|
||||
return data_base + (index + data_index) % data_blks;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_remove_cpoint_map - Remove a checkpoint mapping
|
||||
* @sb: filesystem superblock
|
||||
* @bno: block number to delete
|
||||
*
|
||||
* Only mappings for free queue nodes are supported for now. Blocks that come
|
||||
* after the deleted one are assumed to shift back one place. Returns 0 on
|
||||
* success or a negative error code in case of failure.
|
||||
*/
|
||||
int apfs_remove_cpoint_map(struct super_block *sb, u64 bno)
|
||||
{
|
||||
struct buffer_head *bh;
|
||||
struct apfs_checkpoint_map_phys *cpm;
|
||||
struct apfs_checkpoint_mapping *map, *maps_start, *maps_end;
|
||||
struct apfs_checkpoint_mapping *bno_map = NULL;
|
||||
u32 cpm_count;
|
||||
u32 bno_off;
|
||||
int err = 0;
|
||||
|
||||
bh = apfs_read_cpm_block(sb);
|
||||
if (!bh)
|
||||
return -EIO;
|
||||
cpm = (struct apfs_checkpoint_map_phys *)bh->b_data;
|
||||
apfs_assert_in_transaction(sb, &cpm->cpm_o);
|
||||
|
||||
/* TODO: multiple cpm blocks? */
|
||||
cpm_count = le32_to_cpu(cpm->cpm_count);
|
||||
if (cpm_count > apfs_max_maps_per_block(sb)) {
|
||||
err = -EFSCORRUPTED;
|
||||
goto fail;
|
||||
}
|
||||
maps_start = &cpm->cpm_map[0];
|
||||
maps_end = &cpm->cpm_map[cpm_count];
|
||||
|
||||
bno_off = apfs_index_in_data_area(sb, bno);
|
||||
for (map = maps_start; map < maps_end; ++map) {
|
||||
u32 curr_off;
|
||||
|
||||
if (le64_to_cpu(map->cpm_paddr) == bno)
|
||||
bno_map = map;
|
||||
|
||||
curr_off = apfs_index_in_data_area(sb, le64_to_cpu(map->cpm_paddr));
|
||||
if (curr_off > bno_off)
|
||||
map->cpm_paddr = cpu_to_le64(apfs_data_index_to_bno(sb, curr_off - 1));
|
||||
}
|
||||
if (!bno_map) {
|
||||
err = -EFSCORRUPTED;
|
||||
goto fail;
|
||||
}
|
||||
memmove(bno_map, bno_map + 1, (maps_end - bno_map - 1) * sizeof(*bno_map));
|
||||
le32_add_cpu(&cpm->cpm_count, -1);
|
||||
|
||||
fail:
|
||||
brelse(bh);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_read_ephemeral_object - Find and map an ephemeral object
|
||||
* @sb: superblock structure
|
||||
|
||||
@@ -254,6 +254,59 @@ void apfs_cpoint_data_allocate(struct super_block *sb, u64 *bno)
|
||||
raw_sb->nx_xp_data_len = cpu_to_le32(data_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_cpoint_data_free - Free a block in the checkpoint data area
|
||||
* @sb: superblock structure
|
||||
* @bno: block number to free
|
||||
*
|
||||
* Returns 0 on sucess, or a negative error code in case of failure.
|
||||
*/
|
||||
int apfs_cpoint_data_free(struct super_block *sb, u64 bno)
|
||||
{
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_nx_superblock *raw_sb = nxi->nx_raw;
|
||||
u64 data_base = le64_to_cpu(raw_sb->nx_xp_data_base);
|
||||
u32 data_next = le32_to_cpu(raw_sb->nx_xp_data_next);
|
||||
u32 data_blks = le32_to_cpu(raw_sb->nx_xp_data_blocks);
|
||||
u32 data_len = le32_to_cpu(raw_sb->nx_xp_data_len);
|
||||
u32 data_index = le32_to_cpu(raw_sb->nx_xp_data_index);
|
||||
u32 i, bno_i;
|
||||
|
||||
/*
|
||||
* We can't leave a hole in the data area, so we need to shift all
|
||||
* blocks that come after @bno one position back.
|
||||
*/
|
||||
bno_i = (bno - data_base + data_blks - data_index) % data_blks;
|
||||
for (i = bno_i; i < data_len - 1; ++i) {
|
||||
struct buffer_head *old_bh, *new_bh;
|
||||
int err;
|
||||
|
||||
new_bh = apfs_sb_bread(sb, data_base + (data_index + i) % data_blks);
|
||||
old_bh = apfs_sb_bread(sb, data_base + (data_index + i + 1) % data_blks);
|
||||
if (!new_bh || !old_bh) {
|
||||
brelse(new_bh);
|
||||
brelse(old_bh);
|
||||
return -EIO;
|
||||
}
|
||||
/* I could also just remap the buffer heads... */
|
||||
memcpy(new_bh->b_data, old_bh->b_data, sb->s_blocksize);
|
||||
|
||||
brelse(old_bh);
|
||||
err = apfs_transaction_join(sb, new_bh); /* Not really needed */
|
||||
brelse(new_bh);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
data_next = (data_blks + data_next - 1) % data_blks;
|
||||
data_len--;
|
||||
|
||||
apfs_assert_in_transaction(sb, &raw_sb->nx_o);
|
||||
raw_sb->nx_xp_data_next = cpu_to_le32(data_next);
|
||||
raw_sb->nx_xp_data_len = cpu_to_le32(data_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_checkpoint_start - Start the checkpoint for a new transaction
|
||||
* @sb: superblock structure
|
||||
|
||||
Reference in New Issue
Block a user