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
5cce4b3818
Luflosi has reported a long time ago that the cknodes mount option does not work well on readwrite mounts: https://github.com/linux-apfs/linux-apfs-rw/issues/15 I don't care much for the cknodes option, and I don't know if anybody is actually using it, but I shouldn't ignore a simple bug like this for such a long time. The problem here is that there are several places in the code where an object will get its checksum verified after getting changed, but before the transaction is committed and the checksum updated. As a solution, refuse to verify buffers that are already part of a transaction. Try to make sure that a check happens before the join. Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
380 lines
10 KiB
C
380 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Checksum routines for an APFS object
|
|
*/
|
|
|
|
#include <linux/buffer_head.h>
|
|
#include <linux/fs.h>
|
|
#include "apfs.h"
|
|
|
|
/*
|
|
* Note that this is not a generic implementation of fletcher64, as it assumes
|
|
* a message length that doesn't overflow sum1 and sum2. This constraint is ok
|
|
* for apfs, though, since the block size is limited to 2^16. For a more
|
|
* generic optimized implementation, see Nakassis (1988).
|
|
*/
|
|
static u64 apfs_fletcher64(void *addr, size_t len)
|
|
{
|
|
__le32 *buff = addr;
|
|
u64 sum1 = 0;
|
|
u64 sum2 = 0;
|
|
u64 c1, c2;
|
|
int i, count_32;
|
|
|
|
count_32 = len >> 2;
|
|
for (i = 0; i < count_32; i++) {
|
|
sum1 += le32_to_cpu(buff[i]);
|
|
sum2 += sum1;
|
|
}
|
|
|
|
c1 = sum1 + sum2;
|
|
c1 = 0xFFFFFFFF - do_div(c1, 0xFFFFFFFF);
|
|
c2 = sum1 + c1;
|
|
c2 = 0xFFFFFFFF - do_div(c2, 0xFFFFFFFF);
|
|
|
|
return (c2 << 32) | c1;
|
|
}
|
|
|
|
int apfs_obj_verify_csum(struct super_block *sb, struct buffer_head *bh)
|
|
{
|
|
struct apfs_obj_phys *obj = (struct apfs_obj_phys *)bh->b_data;
|
|
|
|
/* The checksum may be stale until the transaction is committed */
|
|
if (buffer_trans(bh))
|
|
return 1;
|
|
|
|
return (le64_to_cpu(obj->o_cksum) ==
|
|
apfs_fletcher64((char *) obj + APFS_MAX_CKSUM_SIZE,
|
|
sb->s_blocksize - APFS_MAX_CKSUM_SIZE));
|
|
}
|
|
|
|
/**
|
|
* apfs_obj_set_csum - Set the fletcher checksum in an object header
|
|
* @sb: superblock structure
|
|
* @obj: the object header
|
|
*/
|
|
void apfs_obj_set_csum(struct super_block *sb, struct apfs_obj_phys *obj)
|
|
{
|
|
u64 cksum = apfs_fletcher64((char *)obj + APFS_MAX_CKSUM_SIZE,
|
|
sb->s_blocksize - APFS_MAX_CKSUM_SIZE);
|
|
|
|
obj->o_cksum = cpu_to_le64(cksum);
|
|
}
|
|
|
|
/**
|
|
* apfs_cpm_lookup_oid - Search a checkpoint-mapping block for a given oid
|
|
* @sb: superblock structure
|
|
* @cpm: checkpoint-mapping block (on disk)
|
|
* @oid: the ephemeral object id to look up
|
|
* @bno: on return, the block number for the object
|
|
*
|
|
* Returns -EFSCORRUPTED in case of corruption, or -EAGAIN if @oid is not
|
|
* listed in @cpm; returns 0 on success.
|
|
*/
|
|
static int apfs_cpm_lookup_oid(struct super_block *sb,
|
|
struct apfs_checkpoint_map_phys *cpm,
|
|
u64 oid, u64 *bno)
|
|
{
|
|
u32 map_count = le32_to_cpu(cpm->cpm_count);
|
|
int i;
|
|
|
|
if (map_count > apfs_max_maps_per_block(sb))
|
|
return -EFSCORRUPTED;
|
|
|
|
for (i = 0; i < map_count; ++i) {
|
|
struct apfs_checkpoint_mapping *map = &cpm->cpm_map[i];
|
|
|
|
if (le64_to_cpu(map->cpm_oid) == oid) {
|
|
*bno = le64_to_cpu(map->cpm_paddr);
|
|
return 0;
|
|
}
|
|
}
|
|
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
|
|
* @oid: ephemeral object id
|
|
* @bno: block number
|
|
*
|
|
* Only mappings for free queue nodes are supported for now. Returns 0 on
|
|
* success or a negative error code in case of failure.
|
|
*/
|
|
int apfs_create_cpoint_map(struct super_block *sb, u64 oid, u64 bno)
|
|
{
|
|
struct buffer_head *bh;
|
|
struct apfs_checkpoint_map_phys *cpm;
|
|
struct apfs_checkpoint_mapping *map;
|
|
u32 cpm_count;
|
|
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);
|
|
|
|
cpm_count = le32_to_cpu(cpm->cpm_count);
|
|
if (cpm_count >= apfs_max_maps_per_block(sb)) { /* TODO */
|
|
apfs_warn(sb, "creation of cpm blocks not yet supported");
|
|
err = -EOPNOTSUPP;
|
|
goto fail;
|
|
}
|
|
map = &cpm->cpm_map[cpm_count];
|
|
le32_add_cpu(&cpm->cpm_count, 1);
|
|
|
|
map->cpm_type = cpu_to_le32(APFS_OBJ_EPHEMERAL |
|
|
APFS_OBJECT_TYPE_BTREE_NODE);
|
|
map->cpm_subtype = cpu_to_le32(APFS_OBJECT_TYPE_SPACEMAN_FREE_QUEUE);
|
|
map->cpm_size = cpu_to_le32(sb->s_blocksize);
|
|
map->cpm_pad = 0;
|
|
map->cpm_fs_oid = 0;
|
|
map->cpm_oid = cpu_to_le64(oid);
|
|
map->cpm_paddr = cpu_to_le64(bno);
|
|
|
|
fail:
|
|
brelse(bh);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* apfs_index_in_data_area - Get position of block in current checkpoint's data
|
|
* @sb: superblock structure
|
|
* @bno: block number
|
|
*/
|
|
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);
|
|
u64 tmp;
|
|
|
|
tmp = bno - data_base + data_blks - data_index;
|
|
return do_div(tmp, 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
|
|
*/
|
|
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
|
|
* @oid: ephemeral object id
|
|
*
|
|
* Returns the mapped buffer head for the object, or an error pointer in case
|
|
* of failure.
|
|
*/
|
|
struct buffer_head *apfs_read_ephemeral_object(struct super_block *sb, u64 oid)
|
|
{
|
|
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
|
struct apfs_nx_superblock *raw_sb = nxi->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);
|
|
u32 i;
|
|
|
|
if (!desc_blks || !desc_len)
|
|
return ERR_PTR(-EFSCORRUPTED);
|
|
|
|
/* Last block in the area is superblock; the rest are mapping blocks */
|
|
for (i = 0; i < desc_len - 1; ++i) {
|
|
struct buffer_head *bh;
|
|
struct apfs_checkpoint_map_phys *cpm;
|
|
u64 cpm_bno = desc_base + (desc_index + i) % desc_blks;
|
|
u64 obj_bno;
|
|
int err;
|
|
|
|
bh = apfs_sb_bread(sb, cpm_bno);
|
|
if (!bh)
|
|
return ERR_PTR(-EIO);
|
|
cpm = (struct apfs_checkpoint_map_phys *)bh->b_data;
|
|
|
|
err = apfs_cpm_lookup_oid(sb, cpm, oid, &obj_bno);
|
|
brelse(bh);
|
|
cpm = NULL;
|
|
if (err == -EAGAIN) /* Search the next mapping block */
|
|
continue;
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
|
|
bh = apfs_sb_bread(sb, obj_bno);
|
|
if (!bh)
|
|
return ERR_PTR(-EIO);
|
|
return bh;
|
|
}
|
|
return ERR_PTR(-EFSCORRUPTED); /* The mapping is missing */
|
|
}
|
|
|
|
/**
|
|
* apfs_read_object_block - Map a non-ephemeral object block
|
|
* @sb: superblock structure
|
|
* @bno: block number for the object
|
|
* @write: request write access?
|
|
* @preserve: preserve the old block?
|
|
*
|
|
* On success returns the mapped buffer head for the object, which may now be
|
|
* in a new location if write access was requested. Returns an error pointer
|
|
* in case of failure.
|
|
*/
|
|
struct buffer_head *apfs_read_object_block(struct super_block *sb, u64 bno, bool write, bool preserve)
|
|
{
|
|
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
|
struct buffer_head *bh, *new_bh;
|
|
struct apfs_obj_phys *obj;
|
|
u32 type;
|
|
u64 new_bno;
|
|
int err;
|
|
|
|
ASSERT(write || !preserve);
|
|
|
|
bh = apfs_sb_bread(sb, bno);
|
|
if (!bh)
|
|
return ERR_PTR(-EIO);
|
|
|
|
obj = (struct apfs_obj_phys *)bh->b_data;
|
|
type = le32_to_cpu(obj->o_type);
|
|
ASSERT(!(type & APFS_OBJ_EPHEMERAL));
|
|
if (nxi->nx_flags & APFS_CHECK_NODES && !apfs_obj_verify_csum(sb, bh)) {
|
|
err = -EFSBADCRC;
|
|
goto fail;
|
|
}
|
|
|
|
if (!write)
|
|
return bh;
|
|
ASSERT(!(sb->s_flags & SB_RDONLY));
|
|
|
|
/* Is the object already part of the current transaction? */
|
|
if (obj->o_xid == cpu_to_le64(nxi->nx_xid))
|
|
return bh;
|
|
|
|
err = apfs_spaceman_allocate_block(sb, &new_bno, true /* backwards */);
|
|
if (err)
|
|
goto fail;
|
|
new_bh = apfs_getblk(sb, new_bno);
|
|
if (!new_bh) {
|
|
err = -EIO;
|
|
goto fail;
|
|
}
|
|
memcpy(new_bh->b_data, bh->b_data, sb->s_blocksize);
|
|
|
|
/* Don't free the old copy if it's part of a snapshot */
|
|
if (!preserve)
|
|
err = apfs_free_queue_insert(sb, bh->b_blocknr, 1);
|
|
brelse(bh);
|
|
bh = new_bh;
|
|
new_bh = NULL;
|
|
if (err)
|
|
goto fail;
|
|
obj = (struct apfs_obj_phys *)bh->b_data;
|
|
|
|
if (type & APFS_OBJ_PHYSICAL)
|
|
obj->o_oid = cpu_to_le64(new_bno);
|
|
obj->o_xid = cpu_to_le64(nxi->nx_xid);
|
|
err = apfs_transaction_join(sb, bh);
|
|
if (err)
|
|
goto fail;
|
|
|
|
set_buffer_csum(bh);
|
|
return bh;
|
|
|
|
fail:
|
|
brelse(bh);
|
|
return ERR_PTR(err);
|
|
}
|