Files
linux-apfs-rw/object.c
T
Ernesto A. Fernández c54e3a7423 Silence b_blocknr width warnings on old kernels
While fixing the build for old kernels, I noticed that in some of them
you get a warning when you prink a buffer head's b_blocknr as 0x%llx.
I haven't checked but I'm guessing that, at some point, sector_t on
64-bit archs went from being 'unsigned long' to 'unsigned long long',
so these warnings went away. Anyway, just cast b_blocknr before printing
it in all kernels.

At some point I do need to attempt a 32-bit build though. It's been a
long time, and I'm sure there will be much worse problems than this one.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-05-19 18:49:38 -03:00

415 lines
12 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)) {
apfs_err(sb, "block has too many maps (%d)", map_count);
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) {
apfs_err(sb, "failed to read cpm block");
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)) {
apfs_err(sb, "block has too many maps (%d)", cpm_count);
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) {
apfs_err(sb, "no mapping for bno 0x%llx", bno);
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) {
apfs_err(sb, "no blocks in checkpoint descriptor area");
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) {
apfs_err(sb, "failed to read cpm block");
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) {
apfs_err(sb, "cpm lookup failed for oid 0x%llx", oid);
return ERR_PTR(err);
}
bh = apfs_sb_bread(sb, obj_bno);
if (!bh) {
apfs_err(sb, "failed to read ephemeral block");
return ERR_PTR(-EIO);
}
return bh;
}
apfs_err(sb, "no mapping for oid 0x%llx", oid);
return ERR_PTR(-EFSCORRUPTED);
}
/**
* 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 apfs_superblock *vsb_raw = NULL;
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) {
apfs_err(sb, "failed to read object block 0x%llx", bno);
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)) {
apfs_err(sb, "bad checksum for object in block 0x%llx", bno);
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) {
apfs_err(sb, "block allocation failed");
goto fail;
}
new_bh = apfs_getblk(sb, new_bno);
if (!new_bh) {
apfs_err(sb, "failed to map block for CoW (0x%llx)", new_bno);
err = -EIO;
goto fail;
}
memcpy(new_bh->b_data, bh->b_data, sb->s_blocksize);
/*
* Don't free the old copy of the object if it's part of a snapshot.
* Also increase the allocation count, except for the volume superblock
* which is never counted there.
*/
if (!preserve) {
err = apfs_free_queue_insert(sb, bh->b_blocknr, 1);
if (err)
apfs_err(sb, "free queue insertion failed for 0x%llx", (unsigned long long)bh->b_blocknr);
} else if ((type & APFS_OBJECT_TYPE_MASK) != APFS_OBJECT_TYPE_FS) {
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);
le64_add_cpu(&vsb_raw->apfs_total_blocks_alloced, 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);
}