mirror of
https://github.com/linux-apfs/linux-apfs-rw.git
synced 2026-08-18 02:19:59 -07:00
Compare commits
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 */
|
||||
@@ -149,9 +150,9 @@ struct apfs_spaceman {
|
||||
struct buffer_head *sm_ip_bmaps[];
|
||||
};
|
||||
|
||||
#define TRANSACTION_MAIN_QUEUE_MAX 4096
|
||||
#define TRANSACTION_BUFFERS_MAX 65536
|
||||
#define TRANSACTION_STARTS_MAX 65536
|
||||
#define APFS_TRANS_MAIN_QUEUE_MAX 10000
|
||||
#define APFS_TRANS_BUFFERS_MAX 65536
|
||||
#define APFS_TRANS_STARTS_MAX 65536
|
||||
|
||||
/* Possible states for the container transaction structure */
|
||||
#define APFS_NX_TRANS_FORCE_COMMIT 1 /* Commit guaranteed */
|
||||
@@ -163,25 +164,17 @@ 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 delayed_work t_work; /* Work task for transaction commits */
|
||||
struct super_block *t_work_sb; /* sb that last queued a commit */
|
||||
|
||||
struct list_head t_inodes; /* List of inodes in the transaction */
|
||||
struct list_head t_buffers; /* List of buffers in the transaction */
|
||||
size_t t_buffers_count; /* Count of items on the list */
|
||||
int t_starts_count; /* Count of starts for transaction */
|
||||
};
|
||||
|
||||
/*
|
||||
* Structure that keeps track of a volume transaction.
|
||||
*/
|
||||
struct apfs_vol_transaction {
|
||||
struct buffer_head *t_old_vsb; /* Volume superblock being replaced */
|
||||
|
||||
struct apfs_node t_old_omap_root; /* Omap root node being replaced */
|
||||
struct apfs_node t_old_cat_root; /* Catalog root node being replaced */
|
||||
};
|
||||
|
||||
/* State bits for buffer heads in a transaction */
|
||||
#define BH_TRANS BH_PrivateStart /* Attached to a transaction */
|
||||
#define BH_CSUM (BH_PrivateStart + 1) /* Requires checksum update */
|
||||
@@ -204,19 +197,50 @@ 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
|
||||
/*
|
||||
* Mount options for a container are decided on its first mount. This flag lets
|
||||
* future mounts know that has happened already.
|
||||
*/
|
||||
#define APFS_FLAGS_SET 4
|
||||
|
||||
/*
|
||||
* Container superblock data in memory
|
||||
*/
|
||||
struct apfs_nxsb_info {
|
||||
struct block_device *nx_bdev; /* Device for the container */
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0)
|
||||
struct file *nx_bdev_file;
|
||||
#elif 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 */
|
||||
@@ -228,6 +252,7 @@ struct apfs_nxsb_info {
|
||||
|
||||
struct apfs_spaceman *nx_spaceman;
|
||||
struct apfs_nx_transaction nx_transaction;
|
||||
int nx_trans_buffers_max;
|
||||
|
||||
/* For now, a single semaphore for every operation */
|
||||
struct rw_semaphore nx_big_sem;
|
||||
@@ -307,10 +332,8 @@ struct apfs_sb_info {
|
||||
|
||||
struct apfs_crypto_state_val *s_dflt_pfk; /* default per-file key */
|
||||
|
||||
struct apfs_vol_transaction s_transaction;
|
||||
int s_trans_buffers_max;
|
||||
|
||||
struct inode *s_private_dir; /* Inode for the private directory */
|
||||
struct work_struct s_orphan_cleanup_work;
|
||||
};
|
||||
|
||||
static inline struct apfs_sb_info *APFS_SB(struct super_block *sb)
|
||||
@@ -602,20 +625,21 @@ static inline u64 apfs_cat_cnid(struct apfs_key_header *key)
|
||||
}
|
||||
|
||||
/* Flags for the query structure */
|
||||
#define APFS_QUERY_TREE_MASK 00177 /* Which b-tree we query */
|
||||
#define APFS_QUERY_OMAP 00001 /* This is a b-tree object map query */
|
||||
#define APFS_QUERY_CAT 00002 /* This is a catalog tree query */
|
||||
#define APFS_QUERY_FREE_QUEUE 00004 /* This is a free queue query */
|
||||
#define APFS_QUERY_EXTENTREF 00010 /* This is an extent reference query */
|
||||
#define APFS_QUERY_FEXT 00020 /* This is a fext tree query */
|
||||
#define APFS_QUERY_SNAP_META 00040 /* This is a snapshot meta query */
|
||||
#define APFS_QUERY_OMAP_SNAP 00100 /* This is an omap snapshots query */
|
||||
#define APFS_QUERY_NEXT 00200 /* Find next of multiple matches */
|
||||
#define APFS_QUERY_EXACT 00400 /* Search for an exact match */
|
||||
#define APFS_QUERY_DONE 01000 /* The search at this level is over */
|
||||
#define APFS_QUERY_ANY_NAME 02000 /* Multiple search for any name */
|
||||
#define APFS_QUERY_ANY_NUMBER 04000 /* Multiple search for any number */
|
||||
#define APFS_QUERY_TREE_MASK 000177 /* Which b-tree we query */
|
||||
#define APFS_QUERY_OMAP 000001 /* This is a b-tree object map query */
|
||||
#define APFS_QUERY_CAT 000002 /* This is a catalog tree query */
|
||||
#define APFS_QUERY_FREE_QUEUE 000004 /* This is a free queue query */
|
||||
#define APFS_QUERY_EXTENTREF 000010 /* This is an extent reference query */
|
||||
#define APFS_QUERY_FEXT 000020 /* This is a fext tree query */
|
||||
#define APFS_QUERY_SNAP_META 000040 /* This is a snapshot meta query */
|
||||
#define APFS_QUERY_OMAP_SNAP 000100 /* This is an omap snapshots query */
|
||||
#define APFS_QUERY_NEXT 000200 /* Find next of multiple matches */
|
||||
#define APFS_QUERY_EXACT 000400 /* Search for an exact match */
|
||||
#define APFS_QUERY_DONE 001000 /* The search at this level is over */
|
||||
#define APFS_QUERY_ANY_NAME 002000 /* Multiple search for any name */
|
||||
#define APFS_QUERY_ANY_NUMBER 004000 /* Multiple search for any number */
|
||||
#define APFS_QUERY_MULTIPLE (APFS_QUERY_ANY_NAME | APFS_QUERY_ANY_NUMBER)
|
||||
#define APFS_QUERY_PREV 010000 /* Find previous record */
|
||||
|
||||
/*
|
||||
* Structure used to retrieve data from an APFS B-Tree. For now only used
|
||||
@@ -731,6 +755,8 @@ struct apfs_inode_info {
|
||||
bool i_has_dstream; /* Is there a dstream record? */
|
||||
struct apfs_dstream_info i_dstream; /* Dstream data, if any */
|
||||
|
||||
bool i_cleaned; /* Orphan data already deleted */
|
||||
|
||||
struct inode vfs_inode;
|
||||
};
|
||||
|
||||
@@ -848,6 +874,7 @@ extern int apfs_btree_remove(struct apfs_query *query);
|
||||
extern void apfs_btree_change_node_count(struct apfs_query *query, int change);
|
||||
extern int apfs_btree_replace(struct apfs_query *query, void *key, int key_len,
|
||||
void *val, int val_len);
|
||||
extern void apfs_query_direct_forward(struct apfs_query *query);
|
||||
|
||||
/* compress.c */
|
||||
extern int apfs_compress_get_size(struct inode *inode, loff_t *size);
|
||||
@@ -895,6 +922,7 @@ extern int apfs_unlink(struct inode *dir, struct dentry *dentry);
|
||||
extern int apfs_rmdir(struct inode *dir, struct dentry *dentry);
|
||||
extern int apfs_delete_orphan_link(struct inode *inode);
|
||||
extern int APFS_DELETE_ORPHAN_LINK_MAXOPS(void);
|
||||
extern u64 apfs_any_orphan_ino(struct super_block *sb, u64 *ino_p);
|
||||
|
||||
/* extents.c */
|
||||
extern int apfs_extent_from_query(struct apfs_query *query,
|
||||
@@ -910,6 +938,7 @@ extern int apfs_get_new_block(struct inode *inode, sector_t iblock,
|
||||
struct buffer_head *bh_result, int create);
|
||||
extern int APFS_GET_NEW_BLOCK_MAXOPS(void);
|
||||
extern int apfs_truncate(struct apfs_dstream_info *dstream, loff_t new_size);
|
||||
extern int apfs_inode_delete_front(struct inode *inode);
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
|
||||
extern loff_t apfs_remap_file_range(struct file *src_file, loff_t off, struct file *dst_file, loff_t destoff, loff_t len, unsigned int remap_flags);
|
||||
#else
|
||||
@@ -927,6 +956,7 @@ extern int apfs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
|
||||
extern struct inode *apfs_iget(struct super_block *sb, u64 cnid);
|
||||
extern int apfs_update_inode(struct inode *inode, char *new_name);
|
||||
extern int APFS_UPDATE_INODE_MAXOPS(void);
|
||||
extern void apfs_orphan_cleanup_work(struct work_struct *work);
|
||||
extern void apfs_evict_inode(struct inode *inode);
|
||||
extern struct inode *apfs_new_inode(struct inode *dir, umode_t mode,
|
||||
dev_t rdev);
|
||||
@@ -1017,12 +1047,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,8 +1076,8 @@ 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 void apfs_transaction_init(struct apfs_nx_transaction *trans);
|
||||
extern int apfs_transaction_start(struct super_block *sb, struct apfs_max_ops maxops);
|
||||
extern int apfs_transaction_commit(struct super_block *sb);
|
||||
extern void apfs_inode_join_transaction(struct super_block *sb, struct inode *inode);
|
||||
|
||||
+48
-12
@@ -268,6 +268,10 @@ struct apfs_phys_ext_val {
|
||||
#define APFS_FILE_EXTENT_LEN_MASK 0x00ffffffffffffffULL
|
||||
#define APFS_FILE_EXTENT_FLAG_MASK 0xff00000000000000ULL
|
||||
#define APFS_FILE_EXTENT_FLAG_SHIFT 56
|
||||
#define APFS_FILE_EXTENT_CRYPTO_FLAG 0x01 /* Made-up name */
|
||||
#define APFS_FILE_EXTENT_PREALLOCATED 0x02 /* Made-up name */
|
||||
#define APFS_VALID_FILE_EXTENT_FLAGS (APFS_FILE_EXTENT_CRYPTO_FLAG \
|
||||
| APFS_FILE_EXTENT_PREALLOCATED)
|
||||
|
||||
/*
|
||||
* Put a bound on maximum file size so that a growing truncation will always
|
||||
@@ -1051,13 +1055,19 @@ struct apfs_checkpoint_map_phys {
|
||||
#define APFS_FS_SPILLEDOVER 0x00000010LL
|
||||
#define APFS_FS_RUN_SPILLOVER_CLEANER 0x00000020LL
|
||||
#define APFS_FS_ALWAYS_CHECK_EXTENTREF 0x00000040LL
|
||||
#define APFS_FS_PREVIOUSLY_SEALED 0x00000080LL /* Made-up name */
|
||||
#define APFS_FS_PFK 0x00000100LL /* Made-up name */
|
||||
#define APFS_FS_UNKNOWN_200 0x00000200LL
|
||||
#define APFS_FS_FLAGS_VALID_MASK (APFS_FS_UNENCRYPTED \
|
||||
| APFS_FS_EFFACEABLE \
|
||||
| APFS_FS_RESERVED_4 \
|
||||
| APFS_FS_ONEKEY \
|
||||
| APFS_FS_SPILLEDOVER \
|
||||
| APFS_FS_RUN_SPILLOVER_CLEANER \
|
||||
| APFS_FS_ALWAYS_CHECK_EXTENTREF)
|
||||
| APFS_FS_EFFACEABLE \
|
||||
| APFS_FS_RESERVED_4 \
|
||||
| APFS_FS_ONEKEY \
|
||||
| APFS_FS_SPILLEDOVER \
|
||||
| APFS_FS_RUN_SPILLOVER_CLEANER \
|
||||
| APFS_FS_ALWAYS_CHECK_EXTENTREF \
|
||||
| APFS_FS_PREVIOUSLY_SEALED \
|
||||
| APFS_FS_PFK \
|
||||
| APFS_FS_UNKNOWN_200)
|
||||
|
||||
#define APFS_FS_CRYPTOFLAGS (APFS_FS_UNENCRYPTED \
|
||||
| APFS_FS_EFFACEABLE \
|
||||
@@ -1124,7 +1134,9 @@ struct apfs_checkpoint_map_phys {
|
||||
#define APFS_INCOMPAT_NORMALIZATION_INSENSITIVE 0x00000008LL
|
||||
#define APFS_INCOMPAT_INCOMPLETE_RESTORE 0x00000010LL
|
||||
#define APFS_INCOMPAT_SEALED_VOLUME 0x00000020LL
|
||||
#define APFS_INCOMPAT_RESERVED_40 0x00000040LL
|
||||
#define APFS_INCOMPAT_PFK 0x00000040LL /* Made-up name */
|
||||
#define APFS_INCOMPAT_EXTENT_PREALLOC_FLAG 0x00000080LL /* Made-up name */
|
||||
#define APFS_INCOMPAT_SECONDARY_FSROOT 0x00000100LL /* Made-up name */
|
||||
|
||||
#define APFS_SUPPORTED_INCOMPAT_MASK (APFS_INCOMPAT_CASE_INSENSITIVE \
|
||||
| APFS_INCOMPAT_DATALESS_SNAPS \
|
||||
@@ -1132,7 +1144,9 @@ struct apfs_checkpoint_map_phys {
|
||||
| APFS_INCOMPAT_NORMALIZATION_INSENSITIVE \
|
||||
| APFS_INCOMPAT_INCOMPLETE_RESTORE \
|
||||
| APFS_INCOMPAT_SEALED_VOLUME \
|
||||
| APFS_INCOMPAT_RESERVED_40)
|
||||
| APFS_INCOMPAT_PFK \
|
||||
| APFS_INCOMPAT_EXTENT_PREALLOC_FLAG \
|
||||
| APFS_INCOMPAT_SECONDARY_FSROOT)
|
||||
|
||||
#define APFS_MODIFIED_NAMELEN 32
|
||||
|
||||
@@ -1162,6 +1176,18 @@ struct apfs_modified_by {
|
||||
#define APFS_CRYPTO_RESERVED_5 5
|
||||
#define APFS_UNASSIGNED_CRYPTO_ID (~0ULL)
|
||||
|
||||
/* Doc id index flags. I'm making up the names for now. */
|
||||
#define APFS_DOC_ID_HAS_PREV_TREE 0x00000001
|
||||
#define APFS_DOC_ID_UNKNOWN_02 0x00000002
|
||||
#define APFS_DOC_ID_UNKNOWN_04 0x00000004
|
||||
#define APFS_DOC_ID_UNKNOWN_08 0x00000008
|
||||
#define APFS_DOC_ID_UNKNOWN_10 0x00000010
|
||||
#define APFS_DOC_ID_VALID_FLAGS (APFS_DOC_ID_HAS_PREV_TREE \
|
||||
| APFS_DOC_ID_UNKNOWN_02 \
|
||||
| APFS_DOC_ID_UNKNOWN_04 \
|
||||
| APFS_DOC_ID_UNKNOWN_08 \
|
||||
| APFS_DOC_ID_UNKNOWN_10)
|
||||
|
||||
/*
|
||||
* Structure used to store the encryption state
|
||||
*/
|
||||
@@ -1241,15 +1267,24 @@ struct apfs_superblock {
|
||||
|
||||
__le64 apfs_snap_meta_ext_oid;
|
||||
|
||||
char apfs_volume_group_id[UUID_SIZE];
|
||||
/*3F0*/ char apfs_volume_group_id[UUID_SIZE];
|
||||
|
||||
__le64 apfs_integrity_meta_oid;
|
||||
/*400*/ __le64 apfs_integrity_meta_oid;
|
||||
|
||||
__le64 apfs_fext_tree_oid;
|
||||
__le32 apfs_fext_tree_type;
|
||||
/*410*/ __le32 apfs_fext_tree_type;
|
||||
|
||||
__le32 reserved_type;
|
||||
__le64 reserved_oid;
|
||||
|
||||
/*420*/ __le64 apfs_doc_id_index_xid;
|
||||
__le32 apfs_doc_id_index_flags;
|
||||
__le32 apfs_doc_id_tree_type;
|
||||
/*430*/ __le64 apfs_doc_id_tree_oid; /* Made-up name */
|
||||
__le64 apfs_prev_doc_id_tree_oid;
|
||||
__le64 apfs_doc_id_fixup_cursor;
|
||||
__le64 apfs_sec_root_tree_oid;
|
||||
/*450*/ __le32 apfs_sec_root_tree_type;
|
||||
} __packed;
|
||||
|
||||
/* Extended attributes constants */
|
||||
@@ -1268,9 +1303,10 @@ enum {
|
||||
APFS_XATTR_DATA_EMBEDDED = 0x00000002,
|
||||
APFS_XATTR_FILE_SYSTEM_OWNED = 0x00000004,
|
||||
APFS_XATTR_RESERVED_8 = 0x00000008,
|
||||
APFS_XATTR_UNKNOWN_10 = 0x00000010,
|
||||
};
|
||||
|
||||
#define APFS_XATTR_VALID_FLAGS 0x0000000f
|
||||
#define APFS_XATTR_VALID_FLAGS 0x0000001f
|
||||
|
||||
/*
|
||||
* Structure of the value of an extended attributes record
|
||||
|
||||
@@ -398,8 +398,6 @@ struct apfs_query *apfs_alloc_query(struct apfs_node *node,
|
||||
|
||||
/* To be released by free_query. */
|
||||
query->node = node;
|
||||
/* Start the search with the last record and go backwards */
|
||||
query->index = node->records;
|
||||
|
||||
if (parent) {
|
||||
query->key = parent->key;
|
||||
@@ -408,6 +406,15 @@ struct apfs_query *apfs_alloc_query(struct apfs_node *node,
|
||||
query->depth = parent->depth + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* We start the search with the last record and go backwards, but
|
||||
* some queries later use the PREV flag later to list them in order.
|
||||
*/
|
||||
if (query->flags & APFS_QUERY_PREV)
|
||||
query->index = -1;
|
||||
else
|
||||
query->index = node->records;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
@@ -514,7 +521,8 @@ next_node:
|
||||
* than enough to map every block.
|
||||
*/
|
||||
apfs_err(sb, "btree is too high");
|
||||
return -EFSCORRUPTED;
|
||||
err = -EFSCORRUPTED;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = apfs_node_query(sb, *query);
|
||||
@@ -526,12 +534,16 @@ next_node:
|
||||
err = apfs_query_set_before_first(sb, query);
|
||||
if (err) {
|
||||
apfs_err(sb, "failed to set before the first record");
|
||||
return err;
|
||||
goto fail;
|
||||
}
|
||||
return -ENODATA;
|
||||
err = -ENODATA;
|
||||
goto fail;
|
||||
} else if (err == -EAGAIN) {
|
||||
if (!(*query)->parent) /* We are at the root of the tree */
|
||||
return -ENODATA;
|
||||
if (!(*query)->parent) {
|
||||
/* We are at the root of the tree */
|
||||
err = -ENODATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Move back up one level and continue the query */
|
||||
parent = (*query)->parent;
|
||||
@@ -540,7 +552,7 @@ next_node:
|
||||
*query = parent;
|
||||
goto next_node;
|
||||
} else if (err) {
|
||||
return err;
|
||||
goto fail;
|
||||
}
|
||||
if (apfs_node_is_leaf((*query)->node)) /* All done */
|
||||
return 0;
|
||||
@@ -549,14 +561,15 @@ next_node:
|
||||
if (err) {
|
||||
apfs_alert(sb, "bad index block: 0x%llx",
|
||||
(*query)->node->object.block_nr);
|
||||
return err;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Now go a level deeper and search the child */
|
||||
node = apfs_read_node(sb, child_id, storage, false /* write */);
|
||||
if (IS_ERR(node)) {
|
||||
apfs_err(sb, "failed to read node 0x%llx", child_id);
|
||||
return PTR_ERR(node);
|
||||
err = PTR_ERR(node);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (node->object.oid != child_id)
|
||||
@@ -571,10 +584,16 @@ next_node:
|
||||
if (!*query) {
|
||||
apfs_node_free(node);
|
||||
*query = parent;
|
||||
return -ENOMEM;
|
||||
err = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
node = NULL;
|
||||
goto next_node;
|
||||
|
||||
fail:
|
||||
/* Don't leave stale record info here or some callers will use it */
|
||||
(*query)->key_len = (*query)->len = 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __apfs_btree_replace(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
|
||||
@@ -589,11 +608,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));
|
||||
|
||||
@@ -1121,3 +1151,24 @@ int apfs_btree_replace(struct apfs_query *query, void *key, int key_len, void *v
|
||||
apfs_btree_change_rec_count(query, 0 /* change */, key_len, val_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_query_direct_forward - Set a query to start listing records forwards
|
||||
* @query: a successfully executed query
|
||||
*
|
||||
* Multiple queries list records backwards, but queries marked with this
|
||||
* function after execution will go in the opposite direction.
|
||||
*/
|
||||
void apfs_query_direct_forward(struct apfs_query *query)
|
||||
{
|
||||
if (query->flags & APFS_QUERY_PREV)
|
||||
return;
|
||||
|
||||
apfs_assert_query_is_valid(query);
|
||||
ASSERT(apfs_node_is_leaf(query->node));
|
||||
|
||||
while (query) {
|
||||
query->flags |= APFS_QUERY_PREV;
|
||||
query = query->parent;
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -417,16 +417,16 @@ static int apfs_compress_read_folio(struct file *filp, struct folio *folio)
|
||||
static int apfs_compress_readpage(struct file *filp, struct page *page)
|
||||
{
|
||||
#endif
|
||||
void *addr = NULL;
|
||||
char *addr = NULL;
|
||||
ssize_t ret;
|
||||
loff_t off;
|
||||
|
||||
/* Mostly copied from ext4_read_inline_page() */
|
||||
off = page->index << PAGE_SHIFT;
|
||||
addr = kmap_atomic(page);
|
||||
addr = kmap(page);
|
||||
ret = apfs_compress_file_read_page(filp, addr, off);
|
||||
flush_dcache_page(page);
|
||||
kunmap_atomic(addr);
|
||||
kunmap(page);
|
||||
if (ret >= 0) {
|
||||
zero_user_segment(page, ret, PAGE_SIZE);
|
||||
SetPageUptodate(page);
|
||||
|
||||
@@ -1158,13 +1158,13 @@ fail:
|
||||
|
||||
/**
|
||||
* apfs_orphan_name - Get the name for an orphan inode's invisible link
|
||||
* @inode: the vfs inode
|
||||
* @qname: on return, the name assigned to the link
|
||||
* @ino: the inode number
|
||||
* @qname: on return, the name assigned to the link
|
||||
*
|
||||
* Returns 0 on success; the caller must remember to free @qname->name after
|
||||
* use. Returns a negative error code in case of failure.
|
||||
*/
|
||||
static int apfs_orphan_name(struct inode *inode, struct qstr *qname)
|
||||
static int apfs_orphan_name(u64 ino, struct qstr *qname)
|
||||
{
|
||||
int max_len;
|
||||
char *name;
|
||||
@@ -1174,7 +1174,7 @@ static int apfs_orphan_name(struct inode *inode, struct qstr *qname)
|
||||
name = kmalloc(max_len, GFP_KERNEL);
|
||||
if (!name)
|
||||
return -ENOMEM;
|
||||
qname->len = snprintf(name, max_len, "0x%llx-dead", apfs_ino(inode));
|
||||
qname->len = snprintf(name, max_len, "0x%llx-dead", ino);
|
||||
qname->name = name;
|
||||
return 0;
|
||||
}
|
||||
@@ -1193,7 +1193,7 @@ static int apfs_create_orphan_link(struct inode *inode)
|
||||
struct qstr qname;
|
||||
int err = 0;
|
||||
|
||||
err = apfs_orphan_name(inode, &qname);
|
||||
err = apfs_orphan_name(apfs_ino(inode), &qname);
|
||||
if (err)
|
||||
return err;
|
||||
err = apfs_create_dentry_rec(inode, &qname, apfs_ino(priv_dir), 0 /* sibling_id */);
|
||||
@@ -1236,7 +1236,7 @@ int apfs_delete_orphan_link(struct inode *inode)
|
||||
struct apfs_drec drec;
|
||||
int err;
|
||||
|
||||
err = apfs_orphan_name(inode, &qname);
|
||||
err = apfs_orphan_name(apfs_ino(inode), &qname);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@@ -1481,3 +1481,64 @@ out_abort:
|
||||
apfs_transaction_abort(sb);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_any_orphan_ino - Find the inode number for any orphaned regular file
|
||||
* @sb: filesytem superblock
|
||||
* @ino_p: on return, the inode number found
|
||||
*
|
||||
* Returns 0 on success, or a negative error code in case of failure, which may
|
||||
* be -ENODATA if there are no orphan files.
|
||||
*/
|
||||
u64 apfs_any_orphan_ino(struct super_block *sb, u64 *ino_p)
|
||||
{
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
struct apfs_query *query = NULL;
|
||||
struct apfs_drec drec = {0};
|
||||
struct qstr qname = {0};
|
||||
bool hashed = apfs_is_normalization_insensitive(sb);
|
||||
bool found = false;
|
||||
int err;
|
||||
|
||||
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
||||
if (!query)
|
||||
return -ENOMEM;
|
||||
apfs_init_drec_key(sb, APFS_PRIV_DIR_INO_NUM, NULL /* name */, 0 /* name_len */, &query->key);
|
||||
query->flags = APFS_QUERY_CAT | APFS_QUERY_MULTIPLE | APFS_QUERY_EXACT;
|
||||
|
||||
while (!found) {
|
||||
err = apfs_btree_query(sb, &query);
|
||||
if (err) {
|
||||
if (err == -ENODATA)
|
||||
goto out;
|
||||
apfs_err(sb, "drec query failed for private dir");
|
||||
goto out;
|
||||
}
|
||||
err = apfs_drec_from_query(query, &drec, hashed);
|
||||
if (err) {
|
||||
apfs_alert(sb, "bad dentry record in private dir");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* These files are deleted immediately by ->evict_inode() */
|
||||
if (drec.type != DT_REG)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Confirm that this is an orphan file, because the official
|
||||
* reference allows other uses for the private directory.
|
||||
*/
|
||||
err = apfs_orphan_name(drec.ino, &qname);
|
||||
if (err)
|
||||
goto out;
|
||||
found = strcmp(drec.name, qname.name) == 0;
|
||||
kfree(qname.name);
|
||||
qname.name = NULL;
|
||||
}
|
||||
*ino_p = drec.ino;
|
||||
|
||||
out:
|
||||
apfs_free_query(query);
|
||||
query = NULL;
|
||||
return err;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
PACKAGE_NAME="linux-apfs-rw"
|
||||
PACKAGE_VERSION="0.3.6"
|
||||
PACKAGE_VERSION="0.3.11"
|
||||
|
||||
BUILT_MODULE_NAME[0]="apfs"
|
||||
DEST_MODULE_LOCATION[0]="/extra"
|
||||
|
||||
AUTOINSTALL="yes"
|
||||
PRE_BUILD="genver.sh"
|
||||
@@ -8,7 +8,9 @@
|
||||
#include <linux/blk_types.h>
|
||||
#include "apfs.h"
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0)
|
||||
#define MAX(X, Y) ((X) <= (Y) ? (Y) : (X))
|
||||
#endif
|
||||
|
||||
/**
|
||||
* apfs_ext_is_hole - Does this extent represent a hole in a sparse file?
|
||||
@@ -387,7 +389,7 @@ static int apfs_shrink_extent_tail(struct apfs_query *query, struct apfs_dstream
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_query_found_extent - Is this query pointing to an extent record?
|
||||
* apfs_query_found_extent - Did this query find an extent with the right id?
|
||||
* @query: the (successful) query that found the record
|
||||
*/
|
||||
static inline bool apfs_query_found_extent(struct apfs_query *query)
|
||||
@@ -398,7 +400,12 @@ static inline bool apfs_query_found_extent(struct apfs_query *query)
|
||||
if (query->key_len < sizeof(*hdr))
|
||||
return false;
|
||||
hdr = raw + query->key_off;
|
||||
return apfs_cat_type(hdr) == APFS_TYPE_FILE_EXTENT;
|
||||
|
||||
if (apfs_cat_type(hdr) != APFS_TYPE_FILE_EXTENT)
|
||||
return false;
|
||||
if (apfs_cat_cnid(hdr) != query->key.id)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1625,6 +1632,125 @@ int apfs_truncate(struct apfs_dstream_info *dstream, loff_t new_size)
|
||||
return apfs_create_hole(dstream, old_blks, new_blks);
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_dstream_delete_front - Deletes as many leading extents as possible
|
||||
* @sb: filesystem superblock
|
||||
* @ds_id: id for the dstream to delete
|
||||
*
|
||||
* Returns 0 on success, or a negative error code in case of failure, which may
|
||||
* be -ENODATA if there are no more extents, or -EAGAIN if the free queue is
|
||||
* getting too full.
|
||||
*/
|
||||
static int apfs_dstream_delete_front(struct super_block *sb, u64 ds_id)
|
||||
{
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
struct apfs_spaceman *sm = APFS_SM(sb);
|
||||
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
|
||||
struct apfs_spaceman_free_queue *fq = NULL;
|
||||
struct apfs_query *query = NULL;
|
||||
struct apfs_file_extent head;
|
||||
bool first_match = true;
|
||||
int ret;
|
||||
|
||||
fq = &sm_raw->sm_fq[APFS_SFQ_MAIN];
|
||||
if (le64_to_cpu(fq->sfq_count) > APFS_TRANS_MAIN_QUEUE_MAX)
|
||||
return -EAGAIN;
|
||||
|
||||
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
||||
if (!query)
|
||||
return -ENOMEM;
|
||||
apfs_init_file_extent_key(ds_id, 0, &query->key);
|
||||
query->flags = APFS_QUERY_CAT;
|
||||
|
||||
next_extent:
|
||||
ret = apfs_btree_query(sb, &query);
|
||||
if (ret && ret != -ENODATA) {
|
||||
apfs_err(sb, "query failed for first extent of id 0x%llx", ds_id);
|
||||
goto out;
|
||||
}
|
||||
apfs_query_direct_forward(query);
|
||||
if (!apfs_query_found_extent(query)) {
|
||||
/*
|
||||
* After the original lookup, the query may not be set to the
|
||||
* first extent, but instead to the record that comes right
|
||||
* before.
|
||||
*/
|
||||
if (first_match) {
|
||||
first_match = false;
|
||||
goto next_extent;
|
||||
}
|
||||
ret = -ENODATA;
|
||||
goto out;
|
||||
}
|
||||
first_match = false;
|
||||
|
||||
ret = apfs_extent_from_query(query, &head);
|
||||
if (ret) {
|
||||
apfs_err(sb, "bad head extent record on dstream 0x%llx", ds_id);
|
||||
goto out;
|
||||
}
|
||||
ret = apfs_btree_remove(query);
|
||||
if (ret) {
|
||||
apfs_err(sb, "removal failed for id 0x%llx, addr 0x%llx", ds_id, head.logical_addr);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* The official fsck doesn't complain about wrong sparse byte counts
|
||||
* for orphans, so I guess we don't need to update them here
|
||||
*/
|
||||
if (!apfs_ext_is_hole(&head)) {
|
||||
ret = apfs_range_put_reference(sb, head.phys_block_num, head.len);
|
||||
if (ret) {
|
||||
apfs_err(sb, "failed to put range 0x%llx-0x%llx", head.phys_block_num, head.len);
|
||||
goto out;
|
||||
}
|
||||
ret = apfs_crypto_adj_refcnt(sb, head.crypto_id, -1);
|
||||
if (ret) {
|
||||
apfs_err(sb, "failed to take crypto id 0x%llx", head.crypto_id);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (le64_to_cpu(fq->sfq_count) <= APFS_TRANS_MAIN_QUEUE_MAX)
|
||||
goto next_extent;
|
||||
ret = -EAGAIN;
|
||||
out:
|
||||
apfs_free_query(query);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_inode_delete_front - Deletes as many leading extents as possible
|
||||
* @inode: inode to delete
|
||||
*
|
||||
* Tries to delete all extents for @inode, in which case it returns 0. If the
|
||||
* free queue is getting too full, deletes as much as is reasonable and returns
|
||||
* -EAGAIN. May return other negative error codes as well.
|
||||
*/
|
||||
int apfs_inode_delete_front(struct inode *inode)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_dstream_info *dstream = NULL;
|
||||
struct apfs_inode_info *ai = APFS_I(inode);
|
||||
int ret;
|
||||
|
||||
if (!ai->i_has_dstream)
|
||||
return 0;
|
||||
|
||||
dstream = &ai->i_dstream;
|
||||
ret = apfs_flush_extent_cache(dstream);
|
||||
if (ret) {
|
||||
apfs_err(sb, "extent cache flush failed for dstream 0x%llx", dstream->ds_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = apfs_dstream_delete_front(sb, dstream->ds_id);
|
||||
if (ret == -ENODATA)
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
|
||||
loff_t apfs_remap_file_range(struct file *src_file, loff_t off, struct file *dst_file, loff_t destoff, loff_t len, unsigned int remap_flags)
|
||||
#else
|
||||
|
||||
@@ -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
|
||||
@@ -886,7 +886,9 @@ static struct apfs_query *apfs_inode_lookup(const struct inode *inode)
|
||||
if (!ret)
|
||||
return query;
|
||||
|
||||
apfs_err(sb, "query failed for id 0x%llx", apfs_ino(inode));
|
||||
/* Don't complain if an orphan is already gone */
|
||||
if (!current_work() || ret != -ENODATA)
|
||||
apfs_err(sb, "query failed for id 0x%llx", apfs_ino(inode));
|
||||
apfs_free_query(query);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
@@ -1005,8 +1007,10 @@ struct inode *apfs_iget(struct super_block *sb, u64 cnid)
|
||||
down_read(&nxi->nx_big_sem);
|
||||
query = apfs_inode_lookup(inode);
|
||||
if (IS_ERR(query)) {
|
||||
apfs_err(sb, "lookup failed for ino 0x%llx", cnid);
|
||||
err = PTR_ERR(query);
|
||||
/* Don't complain if an orphan is already gone */
|
||||
if (!current_work() || err != -ENODATA)
|
||||
apfs_err(sb, "lookup failed for ino 0x%llx", cnid);
|
||||
goto fail;
|
||||
}
|
||||
err = apfs_inode_from_query(query, inode);
|
||||
@@ -1540,13 +1544,16 @@ int APFS_UPDATE_INODE_MAXOPS(void)
|
||||
* apfs_delete_inode - Delete an inode record
|
||||
* @inode: the vfs inode to delete
|
||||
*
|
||||
* 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, which may
|
||||
* be -EAGAIN if the inode was not deleted in full.
|
||||
*/
|
||||
static int apfs_delete_inode(struct inode *inode)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_inode_info *ai = APFS_I(inode);
|
||||
struct apfs_dstream_info *dstream = NULL;
|
||||
struct apfs_query *query;
|
||||
u64 old_dstream_id;
|
||||
int ret;
|
||||
|
||||
ret = apfs_delete_all_xattrs(inode);
|
||||
@@ -1555,6 +1562,9 @@ static int apfs_delete_inode(struct inode *inode)
|
||||
return ret;
|
||||
}
|
||||
|
||||
dstream = &ai->i_dstream;
|
||||
old_dstream_id = dstream->ds_id;
|
||||
|
||||
/*
|
||||
* This is very wasteful since all the new extents and references will
|
||||
* get deleted right away, but it only affects clones, so I don't see a
|
||||
@@ -1566,12 +1576,27 @@ static int apfs_delete_inode(struct inode *inode)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* TODO: truncate an orphan inode in multiple transactions */
|
||||
dstream = &APFS_I(inode)->i_dstream;
|
||||
ret = apfs_truncate(dstream, 0 /* new_size */);
|
||||
/* TODO: what about partial deletion of xattrs? Is that allowed? */
|
||||
ret = apfs_inode_delete_front(inode);
|
||||
if (ret) {
|
||||
apfs_err(sb, "truncation failed for ino 0x%llx", apfs_ino(inode));
|
||||
return ret;
|
||||
/*
|
||||
* If the inode had too many extents, only the first few get
|
||||
* deleted and the inode remains in the orphan list for now.
|
||||
* I don't know why the deletion starts at the front, but it
|
||||
* seems to be what the official driver does.
|
||||
*/
|
||||
if (ret != -EAGAIN) {
|
||||
apfs_err(sb, "head deletion failed for ino 0x%llx", apfs_ino(inode));
|
||||
return ret;
|
||||
}
|
||||
if (dstream->ds_id != old_dstream_id) {
|
||||
ret = apfs_update_inode(inode, NULL /* new_name */);
|
||||
if (ret) {
|
||||
apfs_err(sb, "dstream id update failed for orphan 0x%llx", apfs_ino(inode));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
ret = apfs_put_dstream_rec(dstream);
|
||||
@@ -1579,6 +1604,8 @@ static int apfs_delete_inode(struct inode *inode)
|
||||
apfs_err(sb, "failed to put dstream for ino 0x%llx", apfs_ino(inode));
|
||||
return ret;
|
||||
}
|
||||
dstream = NULL;
|
||||
ai->i_has_dstream = false;
|
||||
|
||||
query = apfs_inode_lookup(inode);
|
||||
if (IS_ERR(query)) {
|
||||
@@ -1587,42 +1614,191 @@ static int apfs_delete_inode(struct inode *inode)
|
||||
}
|
||||
ret = apfs_btree_remove(query);
|
||||
apfs_free_query(query);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
apfs_err(sb, "removal failed for ino 0x%llx", apfs_ino(inode));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ai->i_cleaned = true;
|
||||
return ret;
|
||||
}
|
||||
#define APFS_DELETE_INODE_MAXOPS 1
|
||||
|
||||
/**
|
||||
* apfs_clean_single_orphan - Clean the given orphan file
|
||||
* @inode: inode for the file to clean
|
||||
*
|
||||
* Returns 0 on success or a negative error code in case of failure, which may
|
||||
* be -EAGAIN if the file could not be deleted in full.
|
||||
*/
|
||||
static int apfs_clean_single_orphan(struct inode *inode)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_max_ops maxops = {0}; /* TODO: rethink this stuff */
|
||||
u64 ino = apfs_ino(inode);
|
||||
bool eagain = false;
|
||||
int err;
|
||||
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
if (err)
|
||||
return err;
|
||||
err = apfs_delete_inode(inode);
|
||||
if (err) {
|
||||
if (err != -EAGAIN) {
|
||||
apfs_err(sb, "failed to delete orphan 0x%llx", ino);
|
||||
goto fail;
|
||||
}
|
||||
eagain = true;
|
||||
} else {
|
||||
err = apfs_delete_orphan_link(inode);
|
||||
if (err) {
|
||||
apfs_err(sb, "failed to unlink orphan 0x%llx", ino);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
err = apfs_transaction_commit(sb);
|
||||
if (err)
|
||||
goto fail;
|
||||
return eagain ? -EAGAIN : 0;
|
||||
|
||||
fail:
|
||||
apfs_transaction_abort(sb);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_clean_any_orphan - Pick an orphan and delete as much as reasonable
|
||||
* @sb: filesystem superblock
|
||||
*
|
||||
* Returns 0 on success, or a negative error code in case of failure, which may
|
||||
* be -ENODATA if there are no more orphan files or -EAGAIN if a file could not
|
||||
* be deleted in full.
|
||||
*/
|
||||
static int apfs_clean_any_orphan(struct super_block *sb)
|
||||
{
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct inode *inode = NULL;
|
||||
int err;
|
||||
u64 ino;
|
||||
|
||||
down_read(&nxi->nx_big_sem);
|
||||
err = apfs_any_orphan_ino(sb, &ino);
|
||||
up_read(&nxi->nx_big_sem);
|
||||
if (err) {
|
||||
if (err == -ENODATA)
|
||||
return -ENODATA;
|
||||
apfs_err(sb, "failed to find orphan inode numbers");
|
||||
return err;
|
||||
}
|
||||
|
||||
inode = apfs_iget(sb, ino);
|
||||
if (IS_ERR(inode)) {
|
||||
err = PTR_ERR(inode);
|
||||
if (err != -ENODATA) {
|
||||
apfs_err(sb, "iget failed for orphan 0x%llx", ino);
|
||||
return err;
|
||||
}
|
||||
/*
|
||||
* This happens rarely for files with no extents, if we hit a
|
||||
* race with ->evict_inode(). Not a problem: the file is gone.
|
||||
*/
|
||||
apfs_notice(sb, "orphan 0x%llx not found", ino);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (atomic_read(&inode->i_count) > 1)
|
||||
goto out;
|
||||
err = apfs_clean_single_orphan(inode);
|
||||
if (err && err != -EAGAIN) {
|
||||
apfs_err(sb, "failed to clean orphan 0x%llx", ino);
|
||||
goto out;
|
||||
}
|
||||
out:
|
||||
iput(inode);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_clean_orphans - Delete as many orphan files as is reasonable
|
||||
* @sb: filesystem superblock
|
||||
*
|
||||
* Returns 0 on success or a negative error code in case of failure.
|
||||
*/
|
||||
static int apfs_clean_orphans(struct super_block *sb)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
for (i = 0; i < 100; ++i) {
|
||||
ret = apfs_clean_any_orphan(sb);
|
||||
if (ret) {
|
||||
if (ret == -ENODATA)
|
||||
return 0;
|
||||
if (ret == -EAGAIN)
|
||||
break;
|
||||
apfs_err(sb, "failed to delete an orphan file");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If a file is too big, or if there are too many files, take a break
|
||||
* and continue later.
|
||||
*/
|
||||
if (atomic_read(&sb->s_active) != 0)
|
||||
schedule_work(&APFS_SB(sb)->s_orphan_cleanup_work);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void apfs_evict_inode(struct inode *inode)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_max_ops maxops;
|
||||
struct apfs_inode_info *ai = APFS_I(inode);
|
||||
int err;
|
||||
|
||||
if (is_bad_inode(inode) || inode->i_nlink)
|
||||
goto out_clear;
|
||||
if (is_bad_inode(inode) || inode->i_nlink || ai->i_cleaned)
|
||||
goto out;
|
||||
|
||||
maxops.cat = APFS_DELETE_INODE_MAXOPS + APFS_DELETE_ORPHAN_LINK_MAXOPS();
|
||||
maxops.blks = 0;
|
||||
if (!ai->i_has_dstream || ai->i_dstream.ds_size == 0) {
|
||||
/* For files with no extents, scheduled cleanup wastes time */
|
||||
err = apfs_clean_single_orphan(inode);
|
||||
if (err)
|
||||
apfs_err(sb, "failed to clean orphan 0x%llx (err:%d)", apfs_ino(inode), err);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (apfs_transaction_start(sb, maxops))
|
||||
goto out_report;
|
||||
if (apfs_delete_inode(inode))
|
||||
goto out_abort;
|
||||
if (apfs_delete_orphan_link(inode))
|
||||
goto out_abort;
|
||||
if (apfs_transaction_commit(sb))
|
||||
goto out_abort;
|
||||
goto out_clear;
|
||||
|
||||
out_abort:
|
||||
apfs_transaction_abort(sb);
|
||||
out_report:
|
||||
apfs_err(sb, "failed to delete orphan inode 0x%llx", apfs_ino(inode));
|
||||
out_clear:
|
||||
/*
|
||||
* If the inode still has extents then schedule cleanup for the rest
|
||||
* of it. Not during unmount though: completing all cleanup could take
|
||||
* a while so just leave future mounts to handle the orphans.
|
||||
*/
|
||||
if (atomic_read(&sb->s_active))
|
||||
schedule_work(&APFS_SB(sb)->s_orphan_cleanup_work);
|
||||
out:
|
||||
truncate_inode_pages_final(&inode->i_data);
|
||||
clear_inode(inode);
|
||||
}
|
||||
|
||||
void apfs_orphan_cleanup_work(struct work_struct *work)
|
||||
{
|
||||
struct super_block *sb = NULL;
|
||||
struct apfs_sb_info *sbi = NULL;
|
||||
struct inode *priv = NULL;
|
||||
int err;
|
||||
|
||||
sbi = container_of(work, struct apfs_sb_info, s_orphan_cleanup_work);
|
||||
priv = sbi->s_private_dir;
|
||||
sb = priv->i_sb;
|
||||
|
||||
if (sb->s_flags & SB_RDONLY) {
|
||||
apfs_alert(sb, "attempt to flush orphans in read-only mount");
|
||||
return;
|
||||
}
|
||||
|
||||
err = apfs_clean_orphans(sb);
|
||||
if (err)
|
||||
apfs_err(sb, "orphan cleanup failed (err:%d)", err);
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_insert_inode_locked - Wrapper for insert_inode_locked4()
|
||||
* @inode: vfs inode to insert in cache
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -769,6 +803,42 @@ static int apfs_key_from_query(struct apfs_query *query, struct apfs_key *key)
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_node_prev - Find the previous record in the current node
|
||||
* @sb: filesystem superblock
|
||||
* @query: query in execution
|
||||
*
|
||||
* Returns 0 on success, -EAGAIN if the previous record is in another node,
|
||||
* -ENODATA if no more records exist, or another negative error code in case
|
||||
* of failure.
|
||||
*
|
||||
* The meaning of "next" and "previous" is reverted here, because regular
|
||||
* multiple always start with the final record, and then they go backwards.
|
||||
* TODO: consider renaming this for clarity.
|
||||
*/
|
||||
static int apfs_node_prev(struct super_block *sb, struct apfs_query *query)
|
||||
{
|
||||
struct apfs_node *node = query->node;
|
||||
|
||||
if (query->index + 1 == node->records) {
|
||||
/* The next record may be in another node */
|
||||
return -EAGAIN;
|
||||
}
|
||||
++query->index;
|
||||
|
||||
query->key_len = apfs_node_locate_key(node, query->index, &query->key_off);
|
||||
if (query->key_len == 0) {
|
||||
apfs_err(sb, "bad key for index %d", query->index);
|
||||
return -EFSCORRUPTED;
|
||||
}
|
||||
query->len = apfs_node_locate_data(node, query->index, &query->off);
|
||||
if (query->len == 0) {
|
||||
apfs_err(sb, "bad value for index %d", query->index);
|
||||
return -EFSCORRUPTED;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_node_next - Find the next matching record in the current node
|
||||
* @sb: filesystem superblock
|
||||
@@ -858,6 +928,8 @@ int apfs_node_query(struct super_block *sb, struct apfs_query *query)
|
||||
int cmp;
|
||||
int err;
|
||||
|
||||
if (query->flags & APFS_QUERY_PREV)
|
||||
return apfs_node_prev(sb, query);
|
||||
if (query->flags & APFS_QUERY_NEXT)
|
||||
return apfs_node_next(sb, query);
|
||||
|
||||
@@ -1193,6 +1265,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);
|
||||
|
||||
+72
-3
@@ -149,9 +149,9 @@ static int apfs_create_snap_name_rec(struct apfs_node *snap_root, const char *na
|
||||
|
||||
err = apfs_btree_query(sb, &query);
|
||||
if (err == 0) {
|
||||
/* TODO: avoid transaction abort here */
|
||||
apfs_info(sb, "a snapshot with that name already exists");
|
||||
err = -EEXIST;
|
||||
/* This should never happen here, the caller has checked */
|
||||
apfs_alert(sb, "snapshot name record already exists");
|
||||
err = -EFSCORRUPTED;
|
||||
goto fail;
|
||||
}
|
||||
if (err != -ENODATA) {
|
||||
@@ -332,6 +332,56 @@ static int apfs_update_omap_snapshots(struct super_block *sb)
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_snapshot_name_check - Check if a snapshot with the given name exists
|
||||
* @sb: filesystem superblock
|
||||
* @name: name to check
|
||||
* @name_len: length of @name
|
||||
* @eexist: on return, whether the name exists or not
|
||||
*
|
||||
* Returns 0 on success, or a negative error code in case of failure.
|
||||
*/
|
||||
static int apfs_snapshot_name_check(struct super_block *sb, const char *name, int name_len, bool *eexist)
|
||||
{
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
struct apfs_superblock *vsb_raw = sbi->s_vsb_raw;
|
||||
struct apfs_node *snap_root = NULL;
|
||||
struct apfs_query *query = NULL;
|
||||
int err;
|
||||
|
||||
snap_root = apfs_read_node(sb, le64_to_cpu(vsb_raw->apfs_snap_meta_tree_oid), APFS_OBJ_PHYSICAL, false /* write */);
|
||||
if (IS_ERR(snap_root)) {
|
||||
apfs_err(sb, "failed to read snap meta root 0x%llx", le64_to_cpu(vsb_raw->apfs_snap_meta_tree_oid));
|
||||
return PTR_ERR(snap_root);
|
||||
}
|
||||
vsb_raw = NULL;
|
||||
|
||||
query = apfs_alloc_query(snap_root, NULL /* parent */);
|
||||
if (!query) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
apfs_init_snap_name_key(name, &query->key);
|
||||
query->flags |= APFS_QUERY_SNAP_META | APFS_QUERY_EXACT;
|
||||
|
||||
err = apfs_btree_query(sb, &query);
|
||||
if (err == 0) {
|
||||
*eexist = true;
|
||||
} else if (err == -ENODATA) {
|
||||
*eexist = false;
|
||||
err = 0;
|
||||
} else {
|
||||
apfs_err(sb, "query failed (%s)", name);
|
||||
}
|
||||
|
||||
out:
|
||||
apfs_free_query(query);
|
||||
query = NULL;
|
||||
apfs_node_free(snap_root);
|
||||
snap_root = NULL;
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_do_ioc_takesnapshot - Actual work for apfs_ioc_take_snapshot()
|
||||
* @mntpoint: inode of the mount point to snapshot
|
||||
@@ -348,12 +398,31 @@ static int apfs_do_ioc_take_snapshot(struct inode *mntpoint, const char *name, i
|
||||
/* TODO: remember to update the maxops in the future */
|
||||
struct apfs_max_ops maxops = {0};
|
||||
u64 sblock_oid;
|
||||
bool eexist = false;
|
||||
int err;
|
||||
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* This check can fail in normal operation, so run it before making any
|
||||
* changes and exit the transaction cleanly if necessary. The name
|
||||
* lookup will have to be repeated later, but it's ok because I don't
|
||||
* expect snapshot creation to be a bottleneck for anyone.
|
||||
*/
|
||||
err = apfs_snapshot_name_check(sb, name, name_len, &eexist);
|
||||
if (err) {
|
||||
apfs_err(sb, "snapshot name existence check failed");
|
||||
goto fail;
|
||||
}
|
||||
if (eexist) {
|
||||
err = apfs_transaction_commit(sb);
|
||||
if (err)
|
||||
goto fail;
|
||||
return -EEXIST;
|
||||
}
|
||||
|
||||
/*
|
||||
* Flush the extent caches to the extenref tree before it gets moved to
|
||||
* the snapshot. It seems safer in general to avoid big unpredictable
|
||||
|
||||
+103
-53
@@ -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;
|
||||
}
|
||||
@@ -465,9 +465,14 @@ static u64 apfs_free_queue_oldest_xid(struct apfs_node *root)
|
||||
char *raw = root->object.data;
|
||||
int len, off;
|
||||
|
||||
len = apfs_node_locate_key(root, 0, &off);
|
||||
if (len != sizeof(*key)) /* No records in queue (or corruption) */
|
||||
if (root->records == 0)
|
||||
return 0;
|
||||
len = apfs_node_locate_key(root, 0, &off);
|
||||
if (len != sizeof(*key)) {
|
||||
/* TODO: abort transaction */
|
||||
apfs_err(root->object.sb, "bad key length (%d)", len);
|
||||
return 0;
|
||||
}
|
||||
key = (struct apfs_spaceman_free_queue_key *)(raw + off);
|
||||
return le64_to_cpu(key->sfqk_xid);
|
||||
}
|
||||
@@ -476,10 +481,11 @@ static u64 apfs_free_queue_oldest_xid(struct apfs_node *root)
|
||||
* apfs_flush_free_queue - Free ip blocks queued by old transactions
|
||||
* @sb: superblock structure
|
||||
* @qid: queue to be freed
|
||||
* @force: flush as much as possible
|
||||
*
|
||||
* Returns 0 on success or a negative error code in case of failure.
|
||||
*/
|
||||
static int apfs_flush_free_queue(struct super_block *sb, unsigned int qid)
|
||||
static int apfs_flush_free_queue(struct super_block *sb, unsigned int qid, bool force)
|
||||
{
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_spaceman *sm = APFS_SM(sb);
|
||||
@@ -496,10 +502,21 @@ static int apfs_flush_free_queue(struct super_block *sb, unsigned int qid)
|
||||
return PTR_ERR(fq_root);
|
||||
}
|
||||
|
||||
/* Preserve a few transactions */
|
||||
while (oldest + 4 < nxi->nx_xid) {
|
||||
while (oldest) {
|
||||
u64 sfq_count;
|
||||
|
||||
/*
|
||||
* Try to preserve one transaction here. I don't really know
|
||||
* what free queues are for so this is probably silly.
|
||||
*/
|
||||
if (force) {
|
||||
if (oldest == nxi->nx_xid)
|
||||
break;
|
||||
} else {
|
||||
if (oldest + 1 >= nxi->nx_xid)
|
||||
break;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
u64 count = 0;
|
||||
|
||||
@@ -518,6 +535,9 @@ static int apfs_flush_free_queue(struct super_block *sb, unsigned int qid)
|
||||
oldest = apfs_free_queue_oldest_xid(fq_root);
|
||||
fq->sfq_oldest_xid = cpu_to_le64(oldest);
|
||||
|
||||
if (force)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Flushing a single transaction may not be enough to avoid
|
||||
* running out of space in the ip, but it's probably best not
|
||||
@@ -528,12 +548,10 @@ static int apfs_flush_free_queue(struct super_block *sb, unsigned int qid)
|
||||
sfq_count = le64_to_cpu(fq->sfq_count);
|
||||
if (qid == APFS_SFQ_IP && sfq_count * 6 <= le64_to_cpu(sm_raw->sm_ip_block_count))
|
||||
break;
|
||||
if (qid == APFS_SFQ_MAIN && sfq_count <= TRANSACTION_MAIN_QUEUE_MAX - 200)
|
||||
if (qid == APFS_SFQ_MAIN && sfq_count <= APFS_TRANS_MAIN_QUEUE_MAX - 200)
|
||||
break;
|
||||
}
|
||||
|
||||
set_buffer_csum(sm->sm_bh);
|
||||
|
||||
fail:
|
||||
apfs_node_free(fq_root);
|
||||
return err;
|
||||
@@ -588,7 +606,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 +615,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 +629,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,19 +655,17 @@ 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");
|
||||
goto fail;
|
||||
}
|
||||
err = apfs_flush_free_queue(sb, APFS_SFQ_IP);
|
||||
err = apfs_flush_free_queue(sb, APFS_SFQ_IP, false /* force */);
|
||||
if (err) {
|
||||
apfs_err(sb, "failed to flush ip fq");
|
||||
goto fail;
|
||||
}
|
||||
err = apfs_flush_free_queue(sb, APFS_SFQ_MAIN);
|
||||
err = apfs_flush_free_queue(sb, APFS_SFQ_MAIN, false /* force */);
|
||||
if (err) {
|
||||
apfs_err(sb, "failed to flush main fq");
|
||||
goto fail;
|
||||
@@ -654,8 +673,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;
|
||||
}
|
||||
@@ -775,17 +792,16 @@ static inline int apfs_chunk_mark_free(struct super_block *sb, char *bitmap,
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_free_queue_insert_nocache - Add a block range to its free queue
|
||||
* apfs_free_queue_try_insert - Try to add a block range to its free queue
|
||||
* @sb: superblock structure
|
||||
* @bno: first block number to free
|
||||
* @count: number of consecutive blocks to free
|
||||
*
|
||||
* Same as apfs_free_queue_insert(), but writes to the free queue directly,
|
||||
* bypassing the cache of the latest freed block range.
|
||||
*
|
||||
* Returns 0 on success or a negative error code in case of failure.
|
||||
* Same as apfs_free_queue_insert_nocache(), except that this one can also fail
|
||||
* with -EAGAIN if there is no room for the new record, so that the caller can
|
||||
* flush the queue and retry.
|
||||
*/
|
||||
int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
|
||||
static int apfs_free_queue_try_insert(struct super_block *sb, u64 bno, u64 count)
|
||||
{
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_spaceman *sm = APFS_SM(sb);
|
||||
@@ -795,6 +811,8 @@ int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
|
||||
struct apfs_btree_info *fq_info = NULL;
|
||||
struct apfs_query *query = NULL;
|
||||
struct apfs_spaceman_free_queue_key raw_key;
|
||||
bool ghost = count == 1;
|
||||
int needed_room;
|
||||
__le64 raw_val;
|
||||
u64 node_count;
|
||||
u16 node_limit;
|
||||
@@ -826,9 +844,20 @@ int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fq_info = (void *)fq_root->object.data + sb->s_blocksize - sizeof(*fq_info);
|
||||
node_count = le64_to_cpu(fq_info->bt_node_count);
|
||||
node_limit = le16_to_cpu(fq->sfq_tree_node_limit);
|
||||
if (node_count == node_limit) {
|
||||
needed_room = sizeof(raw_key) + (ghost ? 0 : sizeof(raw_val));
|
||||
if (!apfs_node_has_room(query->node, needed_room, false /* replace */)) {
|
||||
err = -EAGAIN;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
raw_key.sfqk_xid = cpu_to_le64(nxi->nx_xid);
|
||||
raw_key.sfqk_paddr = cpu_to_le64(bno);
|
||||
if (count == 1) {
|
||||
if (ghost) {
|
||||
/* A lack of value (ghost record) means single-block extent */
|
||||
err = apfs_btree_insert(query, &raw_key, sizeof(raw_key), NULL /* val */, 0 /* val_len */);
|
||||
} else {
|
||||
@@ -840,24 +869,9 @@ int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fq_info = (void *)fq_root->object.data + sb->s_blocksize - sizeof(*fq_info);
|
||||
node_count = le64_to_cpu(fq_info->bt_node_count);
|
||||
node_limit = le16_to_cpu(fq->sfq_tree_node_limit);
|
||||
if (node_count > node_limit) {
|
||||
/*
|
||||
* Ideally this should never happen, but at this point I can't
|
||||
* be certain of that (TODO). If it does happen, it's best to
|
||||
* abort and avoid corruption.
|
||||
*/
|
||||
apfs_alert(sb, "free queue has too many nodes (%llu > %u)", node_count, node_limit);
|
||||
err = -EFSCORRUPTED;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -865,6 +879,44 @@ fail:
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_free_queue_insert_nocache - Add a block range to its free queue
|
||||
* @sb: superblock structure
|
||||
* @bno: first block number to free
|
||||
* @count: number of consecutive blocks to free
|
||||
*
|
||||
* Same as apfs_free_queue_insert(), but writes to the free queue directly,
|
||||
* bypassing the cache of the latest freed block range.
|
||||
*
|
||||
* Returns 0 on success or a negative error code in case of failure.
|
||||
*/
|
||||
int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
|
||||
{
|
||||
struct apfs_spaceman *sm = APFS_SM(sb);
|
||||
unsigned int qid;
|
||||
int err;
|
||||
|
||||
err = apfs_free_queue_try_insert(sb, bno, count);
|
||||
if (err == -EAGAIN) {
|
||||
qid = apfs_block_in_ip(sm, bno) ? APFS_SFQ_IP : APFS_SFQ_MAIN;
|
||||
err = apfs_flush_free_queue(sb, qid, true /* force */);
|
||||
if (err) {
|
||||
apfs_err(sb, "failed to flush fq to make room");
|
||||
return err;
|
||||
}
|
||||
err = apfs_free_queue_try_insert(sb, bno, count);
|
||||
}
|
||||
if (err) {
|
||||
if (err == -EAGAIN) {
|
||||
apfs_alert(sb, "failed to make room in fq - bug!");
|
||||
err = -EFSCORRUPTED;
|
||||
}
|
||||
apfs_err(sb, "fq insert failed (0x%llx-0x%llx)", bno, count);
|
||||
return err;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_free_queue_insert - Add a block range to its free queue
|
||||
* @sb: superblock structure
|
||||
@@ -1182,7 +1234,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 +1296,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)
|
||||
|
||||
+472
-464
File diff suppressed because it is too large.
Load diff
@@ -12,9 +12,12 @@
|
||||
#include <linux/types.h>
|
||||
#include <linux/nls.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/version.h>
|
||||
#include "unicode.h"
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0)
|
||||
#define MIN(X, Y) ((X) <= (Y) ? (X) : (Y))
|
||||
#endif
|
||||
|
||||
/* The arrays of unicode data are defined at the bottom of the file */
|
||||
/* TODO: would a single trie with all the data be more efficient? */
|
||||
|
||||
Reference in new issue
Block a user