mirror of
https://github.com/linux-apfs/linux-apfs-rw.git
synced 2026-08-18 02:19:59 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f9d529bcb | ||
|
|
60ef1e9991 | ||
|
|
0313a0fee1 | ||
|
|
784dd43146 | ||
|
|
7df84d2173 | ||
|
|
fbf052e1e9 | ||
|
|
41af006115 | ||
|
|
cba2045422 | ||
|
|
5e133a2ca5 | ||
|
|
9ae07db606 |
No files matched your search
@@ -6,7 +6,6 @@
|
||||
KERNELRELEASE ?= $(shell uname -r)
|
||||
KERNEL_DIR ?= /lib/modules/$(KERNELRELEASE)/build
|
||||
PWD := $(shell pwd)
|
||||
GIT_COMMIT = $(shell git describe HEAD | tail -c 9)
|
||||
|
||||
obj-m = apfs.o
|
||||
apfs-y := btree.o compress.o dir.o extents.o file.o inode.o key.o libzbitmap.o \
|
||||
@@ -15,7 +14,7 @@ apfs-y := btree.o compress.o dir.o extents.o file.o inode.o key.o libzbitmap.o \
|
||||
spaceman.o super.o symlink.o transaction.o unicode.o xattr.o xfield.o
|
||||
|
||||
default:
|
||||
@printf '#define GIT_COMMIT\t"%s"\n' $(GIT_COMMIT) > version.h
|
||||
./genver.sh
|
||||
make -C $(KERNEL_DIR) M=$(PWD)
|
||||
install:
|
||||
make -C $(KERNEL_DIR) M=$(PWD) modules_install
|
||||
|
||||
+2
-1
@@ -22,7 +22,8 @@ This module is the result of reverse engineering and testing has been limited.
|
||||
If you make use of the write support, expect data corruption. Please report any
|
||||
issues that you find, but I can't promise a quick resolution at this stage.
|
||||
|
||||
Encryption is not yet implemented even in read-only mode.
|
||||
Encryption is not yet implemented even in read-only mode, and neither are
|
||||
fusion drives.
|
||||
|
||||
Build
|
||||
=====
|
||||
|
||||
@@ -64,6 +64,7 @@ struct apfs_object {
|
||||
*/
|
||||
struct buffer_head *o_bh;
|
||||
char *data; /* The raw object */
|
||||
bool ephemeral; /* Is this an ephemeral object? */
|
||||
};
|
||||
|
||||
/* Constants used in managing the size of a node's table of contents */
|
||||
@@ -121,8 +122,8 @@ static inline bool apfs_node_has_fixed_kv_size(struct apfs_node *node)
|
||||
*/
|
||||
struct apfs_spaceman {
|
||||
struct apfs_spaceman_phys *sm_raw; /* On-disk spaceman structure */
|
||||
struct buffer_head *sm_bh; /* Buffer head for @sm_raw */
|
||||
struct apfs_nxsb_info *sm_nxi; /* Container superblock */
|
||||
u32 sm_size; /* Size of @sm_raw in bytes */
|
||||
|
||||
u32 sm_blocks_per_chunk; /* Blocks covered by a bitmap block */
|
||||
u32 sm_chunks_per_cib; /* Chunk count in a chunk-info block */
|
||||
@@ -163,7 +164,6 @@ struct apfs_spaceman {
|
||||
* Structure that keeps track of a container transaction.
|
||||
*/
|
||||
struct apfs_nx_transaction {
|
||||
struct buffer_head *t_old_msb; /* Main superblock being replaced */
|
||||
unsigned int t_state;
|
||||
|
||||
struct list_head t_inodes; /* List of inodes in the transaction */
|
||||
@@ -204,6 +204,23 @@ struct apfs_max_ops {
|
||||
int blks; /* Maximum extent blocks that may need changing */
|
||||
};
|
||||
|
||||
/*
|
||||
* List entry for an in-memory ephemeral object
|
||||
*/
|
||||
struct apfs_ephemeral_object_info {
|
||||
u64 oid; /* Ephemeral object id */
|
||||
u32 size; /* Size of the object in bytes */
|
||||
void *object; /* In-memory address of the object */
|
||||
};
|
||||
|
||||
/*
|
||||
* We allocate a fixed space for the list of ephemeral objects. I don't
|
||||
* actually know how big this should be allowed to get, but all the objects
|
||||
* must be written down with each transaction commit, so probably not too big.
|
||||
*/
|
||||
#define APFS_EPHEMERAL_LIST_SIZE 32768
|
||||
#define APFS_EPHEMERAL_LIST_LIMIT (APFS_EPHEMERAL_LIST_SIZE / sizeof(struct apfs_ephemeral_object_info))
|
||||
|
||||
/* Mount option flags for a container */
|
||||
#define APFS_CHECK_NODES 1
|
||||
#define APFS_READWRITE 2
|
||||
@@ -213,10 +230,17 @@ struct apfs_max_ops {
|
||||
*/
|
||||
struct apfs_nxsb_info {
|
||||
struct block_device *nx_bdev; /* Device for the container */
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
struct bdev_handle *nx_bdev_handle;
|
||||
#endif
|
||||
struct apfs_nx_superblock *nx_raw; /* On-disk main sb */
|
||||
struct apfs_object nx_object; /* Main superblock object */
|
||||
u64 nx_bno; /* Current block number for the checkpoint superblock */
|
||||
u64 nx_xid; /* Latest transaction id */
|
||||
|
||||
/* List of ephemeral objects in memory (except the superblock) */
|
||||
struct apfs_ephemeral_object_info *nx_eph_list;
|
||||
int nx_eph_count;
|
||||
|
||||
struct list_head vol_list; /* List of mounted volumes in container */
|
||||
|
||||
unsigned int nx_flags; /* Mount options shared by all volumes */
|
||||
@@ -1017,12 +1041,12 @@ extern int apfs_make_empty_btree_root(struct super_block *sb, u32 subtype, u64 *
|
||||
|
||||
/* object.c */
|
||||
extern int apfs_obj_verify_csum(struct super_block *sb, struct buffer_head *bh);
|
||||
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 void apfs_obj_set_csum(struct super_block *sb, struct apfs_obj_phys *obj);
|
||||
extern int apfs_multiblock_verify_csum(char *object, u32 size);
|
||||
extern void apfs_multiblock_set_csum(char *object, u32 size);
|
||||
extern int apfs_create_cpm_block(struct super_block *sb, u64 bno, struct buffer_head **bh_p);
|
||||
extern int apfs_create_cpoint_map(struct super_block *sb, struct apfs_checkpoint_map_phys *cpm, struct apfs_obj_phys *obj, u64 bno, u32 size);
|
||||
extern struct apfs_ephemeral_object_info *apfs_ephemeral_object_lookup(struct super_block *sb, u64 oid);
|
||||
extern struct buffer_head *apfs_read_object_block(struct super_block *sb, u64 bno, bool write, bool preserve);
|
||||
extern u32 apfs_index_in_data_area(struct super_block *sb, u64 bno);
|
||||
extern u64 apfs_data_index_to_bno(struct super_block *sb, u32 index);
|
||||
@@ -1046,7 +1070,6 @@ extern int apfs_read_catalog(struct super_block *sb, bool write);
|
||||
extern int apfs_sync_fs(struct super_block *sb, int wait);
|
||||
|
||||
/* 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, struct apfs_max_ops maxops);
|
||||
extern int apfs_transaction_commit(struct super_block *sb);
|
||||
|
||||
@@ -589,11 +589,22 @@ int apfs_query_join_transaction(struct apfs_query *query)
|
||||
struct super_block *sb = node->object.sb;
|
||||
u64 oid = node->object.oid;
|
||||
u32 storage = apfs_query_storage(query);
|
||||
struct apfs_obj_phys *raw = NULL;
|
||||
|
||||
/*
|
||||
* Ephemeral objects are checkpoint data, and all of their xids get
|
||||
* updated on commit. There is no real need to do it here as well, but
|
||||
* it's better for consistency with the other object types.
|
||||
*/
|
||||
if (storage == APFS_OBJ_EPHEMERAL) {
|
||||
ASSERT(node->object.ephemeral);
|
||||
raw = (void *)node->object.data;
|
||||
raw->o_xid = cpu_to_le64(APFS_NXI(sb)->nx_xid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (buffer_trans(node->object.o_bh)) /* Already in the transaction */
|
||||
return 0;
|
||||
/* Ephemeral objects are always checkpoint data */
|
||||
ASSERT(storage != APFS_OBJ_EPHEMERAL);
|
||||
/* Root nodes should join the transaction before the query is created */
|
||||
ASSERT(!apfs_node_is_root(node));
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
PACKAGE_NAME="linux-apfs-rw"
|
||||
PACKAGE_VERSION="0.3.6"
|
||||
PACKAGE_VERSION="0.3.8"
|
||||
|
||||
BUILT_MODULE_NAME[0]="apfs"
|
||||
DEST_MODULE_LOCATION[0]="/extra"
|
||||
|
||||
AUTOINSTALL="yes"
|
||||
PRE_BUILD="genver.sh"
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
#include "apfs.h"
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
#include <linux/splice.h>
|
||||
#endif
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 17, 0)
|
||||
typedef int vm_fault_t;
|
||||
#endif
|
||||
@@ -152,6 +155,16 @@ int apfs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
|
||||
return apfs_sync_fs(sb, true /* wait */);
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
static ssize_t apfs_copy_file_range(struct file *src_file, loff_t src_off,
|
||||
struct file *dst_file, loff_t dst_off,
|
||||
size_t len, unsigned int flags)
|
||||
{
|
||||
return (splice_copy_file_range(src_file, src_off,
|
||||
dst_file, dst_off, len));
|
||||
}
|
||||
#endif
|
||||
|
||||
const struct file_operations apfs_file_operations = {
|
||||
.llseek = generic_file_llseek,
|
||||
.read_iter = generic_file_read_iter,
|
||||
@@ -161,7 +174,9 @@ const struct file_operations apfs_file_operations = {
|
||||
.fsync = apfs_fsync,
|
||||
.unlocked_ioctl = apfs_file_ioctl,
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
.copy_file_range = apfs_copy_file_range,
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
|
||||
.copy_file_range = generic_copy_file_range,
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Script to generate the module version header
|
||||
#
|
||||
|
||||
if command -v git >/dev/null 2>&1 && [ -d .git ]; then
|
||||
GIT_COMMIT=$(git describe HEAD | tail -c 9)
|
||||
else
|
||||
GIT_COMMIT="$(grep PACKAGE_VERSION dkms.conf | cut -d '"' -f2)?"
|
||||
fi
|
||||
|
||||
printf '#define GIT_COMMIT\t"%s"\n' "$GIT_COMMIT" > version.h
|
||||
@@ -46,13 +46,13 @@ void apfs_node_free(struct apfs_node *node)
|
||||
obj = &node->object;
|
||||
|
||||
if (obj->o_bh) {
|
||||
obj->data = NULL;
|
||||
brelse(obj->o_bh);
|
||||
obj->o_bh = NULL;
|
||||
} else {
|
||||
} else if (!obj->ephemeral) {
|
||||
/* Ephemeral data always remains in memory */
|
||||
kfree(obj->data);
|
||||
obj->data = NULL;
|
||||
}
|
||||
obj->data = NULL;
|
||||
|
||||
kfree(node);
|
||||
}
|
||||
@@ -75,9 +75,10 @@ struct apfs_node *apfs_read_node(struct super_block *sb, u64 oid, u32 storage,
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct buffer_head *bh = NULL;
|
||||
struct apfs_btree_node_phys *raw;
|
||||
struct apfs_node *node;
|
||||
struct apfs_nloc *free_head;
|
||||
struct apfs_ephemeral_object_info *eph_info = NULL;
|
||||
struct apfs_btree_node_phys *raw = NULL;
|
||||
struct apfs_node *node = NULL;
|
||||
struct apfs_nloc *free_head = NULL;
|
||||
u64 bno;
|
||||
int err;
|
||||
|
||||
@@ -95,6 +96,8 @@ struct apfs_node *apfs_read_node(struct super_block *sb, u64 oid, u32 storage,
|
||||
apfs_err(sb, "object read failed for bno 0x%llx", bno);
|
||||
return (void *)bh;
|
||||
}
|
||||
bno = bh->b_blocknr;
|
||||
raw = (struct apfs_btree_node_phys *)bh->b_data;
|
||||
break;
|
||||
case APFS_OBJ_PHYSICAL:
|
||||
bh = apfs_read_object_block(sb, oid, write, false /* preserve */);
|
||||
@@ -102,21 +105,30 @@ struct apfs_node *apfs_read_node(struct super_block *sb, u64 oid, u32 storage,
|
||||
apfs_err(sb, "object read failed for bno 0x%llx", oid);
|
||||
return (void *)bh;
|
||||
}
|
||||
oid = bh->b_blocknr;
|
||||
bno = oid = bh->b_blocknr;
|
||||
raw = (struct apfs_btree_node_phys *)bh->b_data;
|
||||
break;
|
||||
case APFS_OBJ_EPHEMERAL:
|
||||
/* Ephemeral objects are checkpoint data, so ignore 'write' */
|
||||
bh = apfs_read_ephemeral_object(sb, oid);
|
||||
if (IS_ERR(bh)) {
|
||||
apfs_err(sb, "ephemeral read failed for oid 0x%llx", oid);
|
||||
return (void *)bh;
|
||||
/* Ephemeral objects are already in memory */
|
||||
eph_info = apfs_ephemeral_object_lookup(sb, oid);
|
||||
if (IS_ERR(eph_info)) {
|
||||
apfs_err(sb, "no ephemeral node for oid 0x%llx", oid);
|
||||
return (void *)eph_info;
|
||||
}
|
||||
if (eph_info->size != sb->s_blocksize) {
|
||||
apfs_err(sb, "unsupported size for ephemeral node (%u)", eph_info->size);
|
||||
return ERR_PTR(-EOPNOTSUPP);
|
||||
}
|
||||
bno = 0; /* In memory, so meaningless */
|
||||
raw = eph_info->object;
|
||||
/* Only for consistency, will happen again on commit */
|
||||
if (write)
|
||||
raw->btn_o.o_xid = cpu_to_le64(nxi->nx_xid);
|
||||
break;
|
||||
default:
|
||||
apfs_alert(sb, "invalid storage type %u - bug!", storage);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
raw = (struct apfs_btree_node_phys *) bh->b_data;
|
||||
|
||||
node = kmalloc(sizeof(*node), GFP_KERNEL);
|
||||
if (!node) {
|
||||
@@ -138,19 +150,21 @@ struct apfs_node *apfs_read_node(struct super_block *sb, u64 oid, u32 storage,
|
||||
node->val_free_list_len = le16_to_cpu(free_head->len);
|
||||
|
||||
node->object.sb = sb;
|
||||
node->object.block_nr = bh->b_blocknr;
|
||||
node->object.block_nr = bno;
|
||||
node->object.oid = oid;
|
||||
node->object.o_bh = bh;
|
||||
node->object.data = bh->b_data;
|
||||
node->object.data = (char *)raw;
|
||||
node->object.ephemeral = !bh;
|
||||
|
||||
if (nxi->nx_flags & APFS_CHECK_NODES && !apfs_obj_verify_csum(sb, bh)) {
|
||||
/* Ephemeral objects already got checked on mount */
|
||||
if (!node->object.ephemeral && nxi->nx_flags & APFS_CHECK_NODES && !apfs_obj_verify_csum(sb, bh)) {
|
||||
/* TODO: don't check this twice for virtual/physical objects */
|
||||
apfs_err(sb, "bad checksum for node in block 0x%llx", (unsigned long long)bh->b_blocknr);
|
||||
apfs_err(sb, "bad checksum for node in block 0x%llx", (unsigned long long)bno);
|
||||
apfs_node_free(node);
|
||||
return ERR_PTR(-EFSBADCRC);
|
||||
}
|
||||
if (!apfs_node_is_valid(sb, node)) {
|
||||
apfs_err(sb, "bad node in block 0x%llx", (unsigned long long)bh->b_blocknr);
|
||||
apfs_err(sb, "bad node in block 0x%llx", (unsigned long long)bno);
|
||||
apfs_node_free(node);
|
||||
return ERR_PTR(-EFSCORRUPTED);
|
||||
}
|
||||
@@ -332,9 +346,10 @@ static struct apfs_node *apfs_create_node(struct super_block *sb, u32 storage)
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_nx_superblock *msb_raw = nxi->nx_raw;
|
||||
struct apfs_superblock *vsb_raw = sbi->s_vsb_raw;
|
||||
struct apfs_node *node;
|
||||
struct buffer_head *bh;
|
||||
struct apfs_btree_node_phys *raw;
|
||||
struct apfs_ephemeral_object_info *eph_info = NULL;
|
||||
struct apfs_node *node = NULL;
|
||||
struct buffer_head *bh = NULL;
|
||||
struct apfs_btree_node_phys *raw = NULL;
|
||||
u64 bno, oid;
|
||||
int err;
|
||||
|
||||
@@ -370,29 +385,38 @@ static struct apfs_node *apfs_create_node(struct super_block *sb, u32 storage)
|
||||
oid = bno;
|
||||
break;
|
||||
case APFS_OBJ_EPHEMERAL:
|
||||
apfs_cpoint_data_allocate(sb, &bno);
|
||||
oid = le64_to_cpu(msb_raw->nx_next_oid);
|
||||
le64_add_cpu(&msb_raw->nx_next_oid, 1);
|
||||
|
||||
err = apfs_create_cpoint_map(sb, oid, bno);
|
||||
if (err) {
|
||||
apfs_err(sb, "checkpoint map creation failed (0x%llx-0x%llx)", oid, bno);
|
||||
return ERR_PTR(err);
|
||||
if (nxi->nx_eph_count >= APFS_EPHEMERAL_LIST_LIMIT) {
|
||||
apfs_err(sb, "creating too many ephemeral objects?");
|
||||
return ERR_PTR(-EOPNOTSUPP);
|
||||
}
|
||||
eph_info = &nxi->nx_eph_list[nxi->nx_eph_count++];
|
||||
eph_info->object = kzalloc(sb->s_blocksize, GFP_KERNEL);
|
||||
if (!eph_info->object)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
eph_info->size = sb->s_blocksize;
|
||||
oid = eph_info->oid = le64_to_cpu(msb_raw->nx_next_oid);
|
||||
le64_add_cpu(&msb_raw->nx_next_oid, 1);
|
||||
break;
|
||||
default:
|
||||
apfs_alert(sb, "invalid storage type %u - bug!", storage);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
bh = apfs_getblk(sb, bno);
|
||||
if (!bh)
|
||||
return ERR_PTR(-EIO);
|
||||
raw = (void *)bh->b_data;
|
||||
err = apfs_transaction_join(sb, bh);
|
||||
if (err)
|
||||
goto fail;
|
||||
set_buffer_csum(bh);
|
||||
if (storage == APFS_OBJ_EPHEMERAL) {
|
||||
bh = NULL;
|
||||
bno = 0;
|
||||
raw = eph_info->object;
|
||||
} else {
|
||||
bh = apfs_getblk(sb, bno);
|
||||
if (!bh)
|
||||
return ERR_PTR(-EIO);
|
||||
bno = bh->b_blocknr;
|
||||
raw = (void *)bh->b_data;
|
||||
err = apfs_transaction_join(sb, bh);
|
||||
if (err)
|
||||
goto fail;
|
||||
set_buffer_csum(bh);
|
||||
}
|
||||
|
||||
/* Set most of the object header, but the subtype is up to the caller */
|
||||
raw->btn_o.o_oid = cpu_to_le64(oid);
|
||||
@@ -420,14 +444,20 @@ static struct apfs_node *apfs_create_node(struct super_block *sb, u32 storage)
|
||||
}
|
||||
|
||||
node->object.sb = sb;
|
||||
node->object.block_nr = bh->b_blocknr;
|
||||
node->object.block_nr = bno;
|
||||
node->object.oid = oid;
|
||||
node->object.o_bh = bh;
|
||||
node->object.data = bh->b_data;
|
||||
node->object.data = (char *)raw;
|
||||
node->object.ephemeral = !bh;
|
||||
return node;
|
||||
|
||||
fail:
|
||||
brelse(bh);
|
||||
if (storage == APFS_OBJ_EPHEMERAL)
|
||||
kfree(raw);
|
||||
else
|
||||
brelse(bh);
|
||||
raw = NULL;
|
||||
bh = NULL;
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
@@ -442,9 +472,11 @@ fail:
|
||||
int apfs_delete_node(struct apfs_node *node, int type)
|
||||
{
|
||||
struct super_block *sb = node->object.sb;
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_superblock *vsb_raw;
|
||||
u64 oid = node->object.oid;
|
||||
u64 bno = node->object.block_nr;
|
||||
struct apfs_ephemeral_object_info *eph_info = NULL, *eph_info_end = NULL;
|
||||
int err;
|
||||
|
||||
switch (type) {
|
||||
@@ -479,16 +511,17 @@ int apfs_delete_node(struct apfs_node *node, int type)
|
||||
le64_add_cpu(&vsb_raw->apfs_total_blocks_freed, 1);
|
||||
return 0;
|
||||
case APFS_QUERY_FREE_QUEUE:
|
||||
err = apfs_cpoint_data_free(sb, bno);
|
||||
if (err) {
|
||||
apfs_err(sb, "failed to free checkpoint block 0x%llx", bno);
|
||||
return err;
|
||||
}
|
||||
err = apfs_remove_cpoint_map(sb, bno);
|
||||
if (err) {
|
||||
apfs_err(sb, "checkpoint map removal failed (0x%llx)", bno);
|
||||
return err;
|
||||
eph_info_end = &nxi->nx_eph_list[nxi->nx_eph_count];
|
||||
eph_info = apfs_ephemeral_object_lookup(sb, node->object.oid);
|
||||
if (IS_ERR(eph_info)) {
|
||||
apfs_alert(sb, "can't find ephemeral object to delete");
|
||||
return PTR_ERR(eph_info);
|
||||
}
|
||||
kfree(eph_info->object);
|
||||
eph_info->object = NULL;
|
||||
memmove(eph_info, eph_info + 1, (char *)eph_info_end - (char *)(eph_info + 1));
|
||||
eph_info_end->object = NULL;
|
||||
--nxi->nx_eph_count;
|
||||
return 0;
|
||||
default:
|
||||
apfs_alert(sb, "new query type must implement node deletion (%d)", type);
|
||||
@@ -538,9 +571,10 @@ void apfs_update_node(struct apfs_node *node)
|
||||
if (!free_head->len)
|
||||
free_head->off = cpu_to_le16(APFS_BTOFF_INVALID);
|
||||
|
||||
(void)bh;
|
||||
ASSERT(buffer_trans(bh));
|
||||
ASSERT(buffer_csum(bh));
|
||||
if (bh) {
|
||||
ASSERT(buffer_trans(bh));
|
||||
ASSERT(buffer_csum(bh));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1193,6 +1227,7 @@ static int apfs_node_temp_dup(const struct apfs_node *original, struct apfs_node
|
||||
*dup = *original;
|
||||
dup->object.o_bh = NULL;
|
||||
dup->object.data = NULL;
|
||||
dup->object.ephemeral = false;
|
||||
|
||||
buffer = kmalloc(sb->s_blocksize, GFP_KERNEL);
|
||||
if (!buffer) {
|
||||
|
||||
@@ -37,131 +37,129 @@ static u64 apfs_fletcher64(void *addr, size_t len)
|
||||
|
||||
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 apfs_multiblock_verify_csum(bh->b_data, sb->s_blocksize);
|
||||
}
|
||||
|
||||
return (le64_to_cpu(obj->o_cksum) ==
|
||||
apfs_fletcher64((char *) obj + APFS_MAX_CKSUM_SIZE,
|
||||
sb->s_blocksize - APFS_MAX_CKSUM_SIZE));
|
||||
/**
|
||||
* apfs_multiblock_verify_csum - Verify an object's checksum
|
||||
* @object: the object to verify
|
||||
* @size: size of the object in bytes (may be multiple blocks)
|
||||
*
|
||||
* Returns 1 on success, 0 on failure.
|
||||
*/
|
||||
int apfs_multiblock_verify_csum(char *object, u32 size)
|
||||
{
|
||||
struct apfs_obj_phys *obj = (struct apfs_obj_phys *)object;
|
||||
u64 actual_csum, header_csum;
|
||||
|
||||
header_csum = le64_to_cpu(obj->o_cksum);
|
||||
actual_csum = apfs_fletcher64(object + APFS_MAX_CKSUM_SIZE, size - APFS_MAX_CKSUM_SIZE);
|
||||
return header_csum == actual_csum;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_obj_set_csum - Set the fletcher checksum in an object header
|
||||
* @sb: superblock structure
|
||||
* @obj: the object header
|
||||
*
|
||||
* The object must have a length of a single block.
|
||||
*/
|
||||
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);
|
||||
apfs_multiblock_set_csum((char *)obj, sb->s_blocksize);
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_multiblock_set_csum - Set an object's checksum
|
||||
* @object: the object to checksum
|
||||
* @size: size of the object in bytes (may be multiple blocks)
|
||||
*/
|
||||
void apfs_multiblock_set_csum(char *object, u32 size)
|
||||
{
|
||||
struct apfs_obj_phys *obj = (struct apfs_obj_phys *)object;
|
||||
u64 cksum;
|
||||
|
||||
cksum = apfs_fletcher64(object + APFS_MAX_CKSUM_SIZE, size - 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
|
||||
* apfs_create_cpm_block - Create a new checkpoint-mapping block
|
||||
* @sb: filesystem superblock
|
||||
* @oid: ephemeral object id
|
||||
* @bno: block number
|
||||
* @bno: block number to use
|
||||
* @bh_p: on return, the buffer head for the block
|
||||
*
|
||||
* Only mappings for free queue nodes are supported for now. Returns 0 on
|
||||
* success or a negative error code in case of failure.
|
||||
* 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)
|
||||
int apfs_create_cpm_block(struct super_block *sb, u64 bno, struct buffer_head **bh_p)
|
||||
{
|
||||
struct buffer_head *bh;
|
||||
struct apfs_checkpoint_map_phys *cpm;
|
||||
struct apfs_checkpoint_mapping *map;
|
||||
u32 cpm_count;
|
||||
int err = 0;
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_checkpoint_map_phys *cpm = NULL;
|
||||
struct buffer_head *bh = NULL;
|
||||
int err;
|
||||
|
||||
bh = apfs_read_cpm_block(sb);
|
||||
if (!bh)
|
||||
bh = apfs_getblk(sb, bno);
|
||||
if (!bh) {
|
||||
apfs_err(sb, "failed to map cpm block");
|
||||
return -EIO;
|
||||
cpm = (struct apfs_checkpoint_map_phys *)bh->b_data;
|
||||
}
|
||||
err = apfs_transaction_join(sb, bh);
|
||||
if (err) {
|
||||
brelse(bh);
|
||||
return err;
|
||||
}
|
||||
set_buffer_csum(bh);
|
||||
|
||||
cpm = (void *)bh->b_data;
|
||||
memset(cpm, 0, sb->s_blocksize);
|
||||
cpm->cpm_o.o_oid = cpu_to_le64(bno);
|
||||
cpm->cpm_o.o_xid = cpu_to_le64(nxi->nx_xid);
|
||||
cpm->cpm_o.o_type = cpu_to_le32(APFS_OBJ_PHYSICAL | APFS_OBJECT_TYPE_CHECKPOINT_MAP);
|
||||
cpm->cpm_o.o_subtype = cpu_to_le32(APFS_OBJECT_TYPE_INVALID);
|
||||
|
||||
/* For now: the caller will have to update these fields */
|
||||
cpm->cpm_flags = cpu_to_le32(APFS_CHECKPOINT_MAP_LAST);
|
||||
cpm->cpm_count = 0;
|
||||
|
||||
*bh_p = bh;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_create_cpoint_map - Create a checkpoint mapping for an object
|
||||
* @sb: filesystem superblock
|
||||
* @cpm: checkpoint mapping block to use
|
||||
* @obj: header for the ephemeral object
|
||||
* @bno: block number for the ephemeral object
|
||||
* @size: size of the ephemeral object in bytes
|
||||
*
|
||||
* Returns 0 on success or a negative error code in case of failure, which may
|
||||
* be -ENOSPC if @cpm is full.
|
||||
*/
|
||||
int apfs_create_cpoint_map(struct super_block *sb, struct apfs_checkpoint_map_phys *cpm, struct apfs_obj_phys *obj, u64 bno, u32 size)
|
||||
{
|
||||
struct apfs_checkpoint_mapping *map = NULL;
|
||||
u32 cpm_count;
|
||||
|
||||
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;
|
||||
}
|
||||
if (cpm_count >= apfs_max_maps_per_block(sb))
|
||||
return -ENOSPC;
|
||||
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_type = obj->o_type;
|
||||
map->cpm_subtype = obj->o_subtype;
|
||||
map->cpm_size = cpu_to_le32(size);
|
||||
map->cpm_pad = 0;
|
||||
map->cpm_fs_oid = 0;
|
||||
map->cpm_oid = cpu_to_le64(oid);
|
||||
map->cpm_oid = obj->o_oid;
|
||||
map->cpm_paddr = cpu_to_le64(bno);
|
||||
|
||||
fail:
|
||||
brelse(bh);
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,120 +195,23 @@ u64 apfs_data_index_to_bno(struct super_block *sb, u32 index)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* apfs_ephemeral_object_lookup - Find an ephemeral object info in memory
|
||||
* @sb: superblock structure
|
||||
* @oid: ephemeral object id
|
||||
*
|
||||
* Returns the mapped buffer head for the object, or an error pointer in case
|
||||
* Returns a pointer to the object info on success, or an error pointer in case
|
||||
* of failure.
|
||||
*/
|
||||
struct buffer_head *apfs_read_ephemeral_object(struct super_block *sb, u64 oid)
|
||||
struct apfs_ephemeral_object_info *apfs_ephemeral_object_lookup(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;
|
||||
struct apfs_ephemeral_object_info *list = NULL;
|
||||
int 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;
|
||||
list = nxi->nx_eph_list;
|
||||
for (i = 0; i < nxi->nx_eph_count; ++i) {
|
||||
if (list[i].oid == oid)
|
||||
return &list[i];
|
||||
}
|
||||
apfs_err(sb, "no mapping for oid 0x%llx", oid);
|
||||
return ERR_PTR(-EFSCORRUPTED);
|
||||
|
||||
+18
-24
@@ -86,9 +86,9 @@ static int apfs_read_spaceman_dev(struct super_block *sb,
|
||||
spaceman->sm_free_count = le64_to_cpu(dev->sm_free_count);
|
||||
spaceman->sm_addr_offset = le32_to_cpu(dev->sm_addr_offset);
|
||||
|
||||
/* Check that all the cib addresses fit in the spaceman block */
|
||||
/* Check that all the cib addresses fit in the spaceman object */
|
||||
if ((long long)spaceman->sm_addr_offset +
|
||||
(long long)spaceman->sm_cib_count * sizeof(u64) > sb->s_blocksize) {
|
||||
(long long)spaceman->sm_cib_count * sizeof(u64) > spaceman->sm_size) {
|
||||
apfs_err(sb, "too many cibs (%u)", spaceman->sm_cib_count);
|
||||
return -EFSCORRUPTED;
|
||||
}
|
||||
@@ -108,9 +108,9 @@ static __le16 *apfs_spaceman_get_16(struct super_block *sb, size_t off)
|
||||
struct apfs_spaceman *spaceman = APFS_SM(sb);
|
||||
struct apfs_spaceman_phys *sm_raw = spaceman->sm_raw;
|
||||
|
||||
if (off > sb->s_blocksize)
|
||||
if (off > spaceman->sm_size)
|
||||
return NULL;
|
||||
if (off + sizeof(__le16) > sb->s_blocksize)
|
||||
if (off + sizeof(__le16) > spaceman->sm_size)
|
||||
return NULL;
|
||||
return (void *)sm_raw + off;
|
||||
}
|
||||
@@ -127,9 +127,9 @@ static __le64 *apfs_spaceman_get_64(struct super_block *sb, size_t off)
|
||||
struct apfs_spaceman *spaceman = APFS_SM(sb);
|
||||
struct apfs_spaceman_phys *sm_raw = spaceman->sm_raw;
|
||||
|
||||
if (off > sb->s_blocksize)
|
||||
if (off > spaceman->sm_size)
|
||||
return NULL;
|
||||
if (off + sizeof(__le64) > sb->s_blocksize)
|
||||
if (off + sizeof(__le64) > spaceman->sm_size)
|
||||
return NULL;
|
||||
return (void *)sm_raw + off;
|
||||
}
|
||||
@@ -164,15 +164,15 @@ static int apfs_update_ip_bm_free_next(struct super_block *sb)
|
||||
struct apfs_spaceman *spaceman = APFS_SM(sb);
|
||||
struct apfs_spaceman_phys *raw = spaceman->sm_raw;
|
||||
u32 free_next_off = le32_to_cpu(raw->sm_ip_bm_free_next_offset);
|
||||
int bmap_count = le32_to_cpu(raw->sm_ip_bm_block_count);
|
||||
int bmap_count = le32_to_cpu(raw->sm_ip_bm_block_count); /* TODO: this can overflow! */
|
||||
__le16 *free_next;
|
||||
int i;
|
||||
|
||||
if (free_next_off > sb->s_blocksize) {
|
||||
if (free_next_off > spaceman->sm_size) {
|
||||
apfs_err(sb, "offset out of bounds (%u)", free_next_off);
|
||||
return -EFSCORRUPTED;
|
||||
}
|
||||
if (free_next_off + bmap_count * sizeof(*free_next) > sb->s_blocksize) {
|
||||
if (free_next_off + bmap_count * sizeof(*free_next) > spaceman->sm_size) {
|
||||
apfs_err(sb, "free next out of bounds (%u-%u)", free_next_off, bmap_count * (u32)sizeof(*free_next));
|
||||
return -EFSCORRUPTED;
|
||||
}
|
||||
@@ -532,8 +532,6 @@ static int apfs_flush_free_queue(struct super_block *sb, unsigned int qid)
|
||||
break;
|
||||
}
|
||||
|
||||
set_buffer_csum(sm->sm_bh);
|
||||
|
||||
fail:
|
||||
apfs_node_free(fq_root);
|
||||
return err;
|
||||
@@ -588,7 +586,7 @@ int apfs_read_spaceman(struct super_block *sb)
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_nx_superblock *raw_sb = nxi->nx_raw;
|
||||
struct apfs_spaceman *spaceman = NULL;
|
||||
struct buffer_head *sm_bh;
|
||||
struct apfs_ephemeral_object_info *sm_eph_info = NULL;
|
||||
struct apfs_spaceman_phys *sm_raw;
|
||||
u32 sm_flags;
|
||||
u64 oid = le64_to_cpu(raw_sb->nx_spaceman_oid);
|
||||
@@ -597,12 +595,13 @@ int apfs_read_spaceman(struct super_block *sb)
|
||||
if (sb->s_flags & SB_RDONLY) /* The space manager won't be needed */
|
||||
return 0;
|
||||
|
||||
sm_bh = apfs_read_ephemeral_object(sb, oid);
|
||||
if (IS_ERR(sm_bh)) {
|
||||
apfs_err(sb, "ephemeral read failed for oid 0x%llx", oid);
|
||||
return PTR_ERR(sm_bh);
|
||||
sm_eph_info = apfs_ephemeral_object_lookup(sb, oid);
|
||||
if (IS_ERR(sm_eph_info)) {
|
||||
apfs_err(sb, "no spaceman object for oid 0x%llx", oid);
|
||||
return PTR_ERR(sm_eph_info);
|
||||
}
|
||||
sm_raw = (struct apfs_spaceman_phys *)sm_bh->b_data;
|
||||
sm_raw = (struct apfs_spaceman_phys *)sm_eph_info->object;
|
||||
sm_raw->sm_o.o_xid = cpu_to_le64(nxi->nx_xid);
|
||||
|
||||
spaceman = apfs_allocate_spaceman(sb, le32_to_cpu(sm_raw->sm_ip_bm_size_in_blocks));
|
||||
if (IS_ERR(spaceman)) {
|
||||
@@ -610,6 +609,8 @@ int apfs_read_spaceman(struct super_block *sb)
|
||||
err = PTR_ERR(spaceman);
|
||||
goto fail;
|
||||
}
|
||||
spaceman->sm_raw = sm_raw;
|
||||
spaceman->sm_size = sm_eph_info->size;
|
||||
|
||||
spaceman->sm_free_cache_base = spaceman->sm_free_cache_blkcnt = 0;
|
||||
|
||||
@@ -634,8 +635,6 @@ int apfs_read_spaceman(struct super_block *sb)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
spaceman->sm_bh = sm_bh;
|
||||
spaceman->sm_raw = sm_raw;
|
||||
err = apfs_rotate_ip_bitmaps(sb);
|
||||
if (err) {
|
||||
apfs_err(sb, "failed to rotate ip bitmaps");
|
||||
@@ -654,8 +653,6 @@ int apfs_read_spaceman(struct super_block *sb)
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
brelse(sm_bh);
|
||||
spaceman->sm_bh = sm_bh = NULL;
|
||||
spaceman->sm_raw = NULL;
|
||||
return err;
|
||||
}
|
||||
@@ -857,7 +854,6 @@ int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
|
||||
if (!fq->sfq_oldest_xid)
|
||||
fq->sfq_oldest_xid = cpu_to_le64(nxi->nx_xid);
|
||||
le64_add_cpu(&fq->sfq_count, count);
|
||||
set_buffer_csum(sm->sm_bh);
|
||||
|
||||
fail:
|
||||
apfs_free_query(query);
|
||||
@@ -1182,7 +1178,6 @@ int apfs_spaceman_allocate_block(struct super_block *sb, u64 *bno, bool backward
|
||||
apfs_spaceman_write_cib_addr(sb, index, cib_bh->b_blocknr);
|
||||
/* The free block count has changed */
|
||||
apfs_write_spaceman(sm);
|
||||
set_buffer_csum(sm->sm_bh);
|
||||
}
|
||||
brelse(cib_bh);
|
||||
if (err == -ENOSPC) /* This cib is full */
|
||||
@@ -1245,7 +1240,6 @@ static int apfs_main_free(struct super_block *sb, u64 bno)
|
||||
apfs_spaceman_write_cib_addr(sb, cib_idx, cib_bh->b_blocknr);
|
||||
/* The free block count has changed */
|
||||
apfs_write_spaceman(sm);
|
||||
set_buffer_csum(sm->sm_bh);
|
||||
}
|
||||
brelse(cib_bh);
|
||||
if (err)
|
||||
|
||||
@@ -113,11 +113,8 @@ static struct buffer_head *apfs_read_super_copy(struct super_block *sb)
|
||||
apfs_err(sb, "not an apfs filesystem");
|
||||
goto fail;
|
||||
}
|
||||
if (!apfs_obj_verify_csum(sb, bh)) {
|
||||
apfs_err(sb, "inconsistent container superblock");
|
||||
err = -EFSBADCRC;
|
||||
goto fail;
|
||||
}
|
||||
if (!apfs_obj_verify_csum(sb, bh))
|
||||
apfs_notice(sb, "backup superblock seems corrupted");
|
||||
return bh;
|
||||
|
||||
fail:
|
||||
@@ -155,14 +152,16 @@ out_unlock:
|
||||
mutex_unlock(&nxs_mutex);
|
||||
}
|
||||
|
||||
static int apfs_check_nx_features(struct super_block *sb);
|
||||
|
||||
/**
|
||||
* apfs_map_main_super - Find the container superblock and map it into memory
|
||||
* apfs_read_main_super - Find the container superblock and read it into memory
|
||||
* @sb: superblock structure
|
||||
*
|
||||
* Returns a negative error code in case of failure. On success, returns 0
|
||||
* and sets the nx_raw, nx_object and nx_xid fields of APFS_NXI(@sb).
|
||||
* and sets the nx_raw and nx_xid fields of APFS_NXI(@sb).
|
||||
*/
|
||||
static int apfs_map_main_super(struct super_block *sb)
|
||||
static int apfs_read_main_super(struct super_block *sb)
|
||||
{
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct buffer_head *bh;
|
||||
@@ -231,20 +230,20 @@ static int apfs_map_main_super(struct super_block *sb)
|
||||
desc_bh = NULL;
|
||||
}
|
||||
|
||||
nxi->nx_raw = kmalloc(sb->s_blocksize, GFP_KERNEL);
|
||||
if (!nxi->nx_raw) {
|
||||
err = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
memcpy(nxi->nx_raw, bh->b_data, sb->s_blocksize);
|
||||
nxi->nx_bno = bno;
|
||||
nxi->nx_xid = xid;
|
||||
nxi->nx_raw = msb_raw;
|
||||
nxi->nx_object.sb = sb; /* XXX: these "objects" never made any sense */
|
||||
nxi->nx_object.block_nr = bno;
|
||||
nxi->nx_object.oid = le64_to_cpu(msb_raw->nx_o.o_oid);
|
||||
nxi->nx_object.o_bh = bh;
|
||||
nxi->nx_object.data = bh->b_data;
|
||||
|
||||
/* For now we only support blocksize < PAGE_SIZE */
|
||||
nxi->nx_blocksize = sb->s_blocksize;
|
||||
nxi->nx_blocksize_bits = sb->s_blocksize_bits;
|
||||
|
||||
return 0;
|
||||
|
||||
err = apfs_check_nx_features(sb);
|
||||
fail:
|
||||
brelse(bh);
|
||||
return err;
|
||||
@@ -283,18 +282,19 @@ static void apfs_update_software_info(struct super_block *sb)
|
||||
static struct file_system_type apfs_fs_type;
|
||||
|
||||
/**
|
||||
* apfs_unmap_main_super - Clean up apfs_map_main_super()
|
||||
* apfs_free_main_super - Clean up apfs_read_main_super()
|
||||
* @sbi: in-memory superblock info
|
||||
*
|
||||
* It also cleans up after apfs_attach_nxi(), so the name is no longer accurate.
|
||||
*/
|
||||
static inline void apfs_unmap_main_super(struct apfs_sb_info *sbi)
|
||||
static inline void apfs_free_main_super(struct apfs_sb_info *sbi)
|
||||
{
|
||||
struct apfs_nxsb_info *nxi = sbi->s_nxi;
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0)
|
||||
fmode_t mode = FMODE_READ | FMODE_EXCL;
|
||||
#endif
|
||||
struct apfs_object *obj = NULL;
|
||||
struct apfs_ephemeral_object_info *eph_list = NULL;
|
||||
int i;
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0)
|
||||
if (nxi->nx_flags & APFS_READWRITE)
|
||||
@@ -307,13 +307,24 @@ static inline void apfs_unmap_main_super(struct apfs_sb_info *sbi)
|
||||
if (--nxi->nx_refcnt)
|
||||
goto out;
|
||||
|
||||
obj = &nxi->nx_object;
|
||||
obj->data = NULL;
|
||||
brelse(obj->o_bh);
|
||||
obj->o_bh = NULL;
|
||||
obj = NULL;
|
||||
/* Clean up all the ephemeral objects in memory */
|
||||
eph_list = nxi->nx_eph_list;
|
||||
if (eph_list) {
|
||||
for (i = 0; i < nxi->nx_eph_count; ++i) {
|
||||
kfree(eph_list[i].object);
|
||||
eph_list[i].object = NULL;
|
||||
}
|
||||
kfree(eph_list);
|
||||
eph_list = nxi->nx_eph_list = NULL;
|
||||
nxi->nx_eph_count = 0;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)
|
||||
kfree(nxi->nx_raw);
|
||||
nxi->nx_raw = NULL;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
bdev_release(nxi->nx_bdev_handle);
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)
|
||||
blkdev_put(nxi->nx_bdev, &apfs_fs_type);
|
||||
#else
|
||||
blkdev_put(nxi->nx_bdev, mode);
|
||||
@@ -716,7 +727,7 @@ fail:
|
||||
mutex_lock(&nxs_mutex);
|
||||
apfs_put_omap(sbi->s_omap);
|
||||
sbi->s_omap = NULL;
|
||||
apfs_unmap_main_super(sbi);
|
||||
apfs_free_main_super(sbi);
|
||||
mutex_unlock(&nxs_mutex);
|
||||
|
||||
sb->s_fs_info = NULL;
|
||||
@@ -1206,26 +1217,26 @@ out:
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_check_features - Check for unsupported features in the filesystem
|
||||
* apfs_check_nx_features - Check for unsupported features in the container
|
||||
* @sb: superblock structure
|
||||
*
|
||||
* Returns -EINVAL if unsupported incompatible features are found, otherwise
|
||||
* returns 0.
|
||||
*/
|
||||
static int apfs_check_features(struct super_block *sb)
|
||||
static int apfs_check_nx_features(struct super_block *sb)
|
||||
{
|
||||
struct apfs_nx_superblock *msb_raw = APFS_NXI(sb)->nx_raw;
|
||||
struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
|
||||
struct apfs_nx_superblock *msb_raw = NULL;
|
||||
u64 features;
|
||||
|
||||
ASSERT(msb_raw);
|
||||
ASSERT(vsb_raw);
|
||||
msb_raw = APFS_NXI(sb)->nx_raw;
|
||||
if (!msb_raw) {
|
||||
apfs_alert(sb, "feature checks are misplaced");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
features = le64_to_cpu(msb_raw->nx_incompatible_features);
|
||||
if (features & ~APFS_NX_SUPPORTED_INCOMPAT_MASK) {
|
||||
apfs_warn(sb,
|
||||
"unknown incompatible container features (0x%llx)",
|
||||
features);
|
||||
apfs_warn(sb, "unknown incompatible container features (0x%llx)", features);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (features & APFS_NX_INCOMPAT_FUSION) {
|
||||
@@ -1233,10 +1244,38 @@ static int apfs_check_features(struct super_block *sb)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
features = le64_to_cpu(msb_raw->nx_readonly_compatible_features);
|
||||
if (features & ~APFS_NX_SUPPORTED_ROCOMPAT_MASK) {
|
||||
apfs_warn(sb, "unknown read-only compatible container features (0x%llx)", features);
|
||||
if (!sb_rdonly(sb)) {
|
||||
apfs_warn(sb, "container can't be mounted read-write");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_check_vol_features - Check for unsupported features in the volume
|
||||
* @sb: superblock structure
|
||||
*
|
||||
* Returns -EINVAL if unsupported incompatible features are found, otherwise
|
||||
* returns 0.
|
||||
*/
|
||||
static int apfs_check_vol_features(struct super_block *sb)
|
||||
{
|
||||
struct apfs_superblock *vsb_raw = NULL;
|
||||
u64 features;
|
||||
|
||||
vsb_raw = APFS_SB(sb)->s_vsb_raw;
|
||||
if (!vsb_raw) {
|
||||
apfs_alert(sb, "feature checks are misplaced");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
features = le64_to_cpu(vsb_raw->apfs_incompatible_features);
|
||||
if (features & ~APFS_SUPPORTED_INCOMPAT_MASK) {
|
||||
apfs_warn(sb, "unknown incompatible volume features (0x%llx)",
|
||||
features);
|
||||
apfs_warn(sb, "unknown incompatible volume features (0x%llx)", features);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (features & APFS_INCOMPAT_DATALESS_SNAPS) {
|
||||
@@ -1268,29 +1307,14 @@ static int apfs_check_features(struct super_block *sb)
|
||||
if (!(features & APFS_FS_UNENCRYPTED))
|
||||
apfs_warn(sb, "volume is encrypted, may not be read correctly");
|
||||
|
||||
features = le64_to_cpu(msb_raw->nx_readonly_compatible_features);
|
||||
if (features & ~APFS_NX_SUPPORTED_ROCOMPAT_MASK) {
|
||||
apfs_warn(sb,
|
||||
"unknown read-only compatible container features (0x%llx)",
|
||||
features);
|
||||
if (!sb_rdonly(sb)) {
|
||||
apfs_warn(sb, "container can't be mounted read-write");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
features = le64_to_cpu(vsb_raw->apfs_readonly_compatible_features);
|
||||
if (features & ~APFS_SUPPORTED_ROCOMPAT_MASK) {
|
||||
apfs_warn(sb,
|
||||
"unknown read-only compatible volume features (0x%llx)",
|
||||
features);
|
||||
apfs_warn(sb, "unknown read-only compatible volume features (0x%llx)", features);
|
||||
if (!sb_rdonly(sb)) {
|
||||
apfs_warn(sb, "volume can't be mounted read-write");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: add checks for encryption, snapshots? */
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1388,7 +1412,7 @@ static int apfs_fill_super(struct super_block *sb, void *data, int silent)
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = apfs_check_features(sb);
|
||||
err = apfs_check_vol_features(sb);
|
||||
if (err)
|
||||
goto failed_omap;
|
||||
|
||||
@@ -1559,13 +1583,24 @@ static int apfs_attach_nxi(struct apfs_sb_info *sbi, const char *dev_name, fmode
|
||||
|
||||
nxi = apfs_nx_find_by_dev(dev);
|
||||
if (!nxi) {
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
struct bdev_handle *handle;
|
||||
#endif
|
||||
struct block_device *bdev;
|
||||
|
||||
nxi = kzalloc(sizeof(*nxi), GFP_KERNEL);
|
||||
if (!nxi)
|
||||
return -ENOMEM;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
handle = bdev_open_by_path(dev_name, mode, &apfs_fs_type, NULL);
|
||||
if (IS_ERR(handle)) {
|
||||
kfree(nxi);
|
||||
return PTR_ERR(handle);
|
||||
}
|
||||
nxi->nx_bdev_handle = handle;
|
||||
bdev = handle->bdev;
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)
|
||||
bdev = blkdev_get_by_path(dev_name, mode, &apfs_fs_type, NULL);
|
||||
#else
|
||||
bdev = blkdev_get_by_path(dev_name, mode, &apfs_fs_type);
|
||||
@@ -1638,13 +1673,13 @@ static struct dentry *apfs_mount(struct file_system_type *fs_type, int flags,
|
||||
goto out_deactivate_super;
|
||||
}
|
||||
/* Only one superblock per volume */
|
||||
apfs_unmap_main_super(sbi);
|
||||
apfs_free_main_super(sbi);
|
||||
kfree(sbi->s_snap_name);
|
||||
sbi->s_snap_name = NULL;
|
||||
kfree(sbi);
|
||||
sbi = NULL;
|
||||
} else {
|
||||
error = apfs_map_main_super(sb);
|
||||
error = apfs_read_main_super(sb);
|
||||
if (error)
|
||||
goto out_deactivate_super;
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0)
|
||||
@@ -1663,7 +1698,7 @@ static struct dentry *apfs_mount(struct file_system_type *fs_type, int flags,
|
||||
out_deactivate_super:
|
||||
deactivate_locked_super(sb);
|
||||
out_unmap_super:
|
||||
apfs_unmap_main_super(sbi);
|
||||
apfs_free_main_super(sbi);
|
||||
out_free_sbi:
|
||||
kfree(sbi->s_snap_name);
|
||||
kfree(sbi);
|
||||
|
||||
+389
-389
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user