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
@@ -150,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 */
|
||||
@@ -166,22 +166,15 @@ struct apfs_spaceman {
|
||||
struct apfs_nx_transaction {
|
||||
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 */
|
||||
@@ -230,7 +223,9 @@ struct apfs_ephemeral_object_info {
|
||||
*/
|
||||
struct apfs_nxsb_info {
|
||||
struct block_device *nx_bdev; /* Device for the container */
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
#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 */
|
||||
@@ -252,6 +247,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;
|
||||
@@ -331,10 +327,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)
|
||||
@@ -626,20 +620,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
|
||||
@@ -755,6 +750,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;
|
||||
};
|
||||
|
||||
@@ -872,6 +869,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);
|
||||
@@ -919,6 +917,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,
|
||||
@@ -934,6 +933,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
|
||||
@@ -951,6 +951,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);
|
||||
@@ -1071,6 +1072,7 @@ extern int apfs_sync_fs(struct super_block *sb, int wait);
|
||||
|
||||
/* transaction.c */
|
||||
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);
|
||||
@@ -1132,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,5 +1,5 @@
|
||||
PACKAGE_NAME="linux-apfs-rw"
|
||||
PACKAGE_VERSION="0.3.8"
|
||||
PACKAGE_VERSION="0.3.10"
|
||||
|
||||
BUILT_MODULE_NAME[0]="apfs"
|
||||
DEST_MODULE_LOCATION[0]="/extra"
|
||||
|
||||
@@ -387,7 +387,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 +398,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 +1630,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -803,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
|
||||
@@ -892,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);
|
||||
|
||||
|
||||
+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
|
||||
|
||||
+85
-29
@@ -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,7 +548,7 @@ 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;
|
||||
}
|
||||
|
||||
@@ -640,12 +660,12 @@ int apfs_read_spaceman(struct super_block *sb)
|
||||
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;
|
||||
@@ -772,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);
|
||||
@@ -792,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;
|
||||
@@ -823,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 {
|
||||
@@ -837,20 +869,6 @@ 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);
|
||||
@@ -861,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
|
||||
|
||||
@@ -57,7 +57,11 @@ static struct apfs_nxsb_info *apfs_nx_find_by_dev(dev_t dev)
|
||||
*/
|
||||
static int apfs_sb_set_blocksize(struct super_block *sb, int size)
|
||||
{
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 10, 0)
|
||||
if (set_blocksize(APFS_NXI(sb)->nx_bdev_file, size))
|
||||
#else
|
||||
if (set_blocksize(APFS_NXI(sb)->nx_bdev, size))
|
||||
#endif
|
||||
return 0;
|
||||
sb->s_blocksize = size;
|
||||
sb->s_blocksize_bits = blksize_bits(size);
|
||||
@@ -253,9 +257,8 @@ fail:
|
||||
* apfs_update_software_info - Write the module info to a modified volume
|
||||
* @sb: superblock structure
|
||||
*
|
||||
* Does nothing if the module information is already present at index zero of
|
||||
* the apfs_modified_by array. Otherwise, writes it there after shifting the
|
||||
* rest of the entries to the right.
|
||||
* Writes this module's information to index zero of the apfs_modified_by
|
||||
* array, shifting the rest of the entries to the right.
|
||||
*/
|
||||
static void apfs_update_software_info(struct super_block *sb)
|
||||
{
|
||||
@@ -268,11 +271,7 @@ static void apfs_update_software_info(struct super_block *sb)
|
||||
ASSERT(strlen(APFS_MODULE_ID_STRING) < APFS_MODIFIED_NAMELEN);
|
||||
mod_by = raw->apfs_modified_by;
|
||||
|
||||
/* This check could be optimized away, but does it matter? */
|
||||
if (!strcmp(mod_by->id, APFS_MODULE_ID_STRING))
|
||||
return;
|
||||
memmove(mod_by + 1, mod_by, (APFS_MAX_HIST - 1) * sizeof(*mod_by));
|
||||
|
||||
memset(mod_by->id, 0, sizeof(mod_by->id));
|
||||
strscpy(mod_by->id, APFS_MODULE_ID_STRING, sizeof(mod_by->id));
|
||||
mod_by->timestamp = cpu_to_le64(ktime_get_real_ns());
|
||||
@@ -322,7 +321,9 @@ static inline void apfs_free_main_super(struct apfs_sb_info *sbi)
|
||||
kfree(nxi->nx_raw);
|
||||
nxi->nx_raw = NULL;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0)
|
||||
fput(nxi->nx_bdev_file);
|
||||
#elif 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);
|
||||
@@ -405,14 +406,11 @@ int apfs_map_volume_super(struct super_block *sb, bool write)
|
||||
struct apfs_omap *omap = NULL;
|
||||
struct apfs_node *vnode;
|
||||
struct buffer_head *bh;
|
||||
struct apfs_vol_transaction *trans = &sbi->s_transaction;
|
||||
u64 vol_id;
|
||||
u64 vsb;
|
||||
int err;
|
||||
|
||||
ASSERT(msb_raw);
|
||||
ASSERT(trans->t_old_vsb == sbi->s_vobject.o_bh);
|
||||
(void)trans;
|
||||
|
||||
/* Get the id for the requested volume number */
|
||||
if (sbi->s_vol_nr >= APFS_NX_MAX_FILE_SYSTEMS) {
|
||||
@@ -475,13 +473,7 @@ int apfs_map_volume_super(struct super_block *sb, bool write)
|
||||
* Snapshots could get mounted during a transaction, so the fletcher
|
||||
* checksum doesn't have to be valid.
|
||||
*/
|
||||
err = apfs_map_volume_super_bno(sb, vsb, !write && !sbi->s_snap_name);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (write)
|
||||
apfs_update_software_info(sb);
|
||||
return 0;
|
||||
return apfs_map_volume_super_bno(sb, vsb, !write && !sbi->s_snap_name);
|
||||
|
||||
fail:
|
||||
brelse(bh);
|
||||
@@ -685,8 +677,16 @@ int apfs_read_catalog(struct super_block *sb, bool write)
|
||||
static void apfs_put_super(struct super_block *sb)
|
||||
{
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
struct apfs_nx_transaction *trans = NULL;
|
||||
|
||||
/* Update the volume's unmount time */
|
||||
/* Cleanups won't reschedule themselves during unmount */
|
||||
flush_work(&sbi->s_orphan_cleanup_work);
|
||||
|
||||
/* We are about to commit anyway */
|
||||
trans = &APFS_NXI(sb)->nx_transaction;
|
||||
cancel_delayed_work_sync(&trans->t_work);
|
||||
|
||||
/* Stop flushing orphans and update the volume as needed */
|
||||
if (!(sb->s_flags & SB_RDONLY)) {
|
||||
struct apfs_superblock *vsb_raw;
|
||||
struct buffer_head *vsb_bh;
|
||||
@@ -700,6 +700,7 @@ static void apfs_put_super(struct super_block *sb)
|
||||
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
||||
ASSERT(buffer_trans(vsb_bh));
|
||||
|
||||
apfs_update_software_info(sb);
|
||||
vsb_raw->apfs_unmount_time = cpu_to_le64(ktime_get_real_ns());
|
||||
set_buffer_csum(vsb_bh);
|
||||
|
||||
@@ -765,6 +766,7 @@ static struct inode *apfs_alloc_inode(struct super_block *sb)
|
||||
dstream->ds_ext_dirty = false;
|
||||
ai->i_nchildren = 0;
|
||||
INIT_LIST_HEAD(&ai->i_list);
|
||||
ai->i_cleaned = false;
|
||||
return &ai->vfs_inode;
|
||||
}
|
||||
|
||||
@@ -789,6 +791,10 @@ static void init_once(void *p)
|
||||
inode_init_once(&ai->vfs_inode);
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0)
|
||||
#define SLAB_MEM_SPREAD 0
|
||||
#endif
|
||||
|
||||
static int __init init_inodecache(void)
|
||||
{
|
||||
apfs_inode_cachep = kmem_cache_create("apfs_inode_cache",
|
||||
@@ -1209,10 +1215,15 @@ static int parse_options(struct super_block *sb, char *options)
|
||||
|
||||
out:
|
||||
apfs_set_nx_flags(sb, nx_flags);
|
||||
if ((nxi->nx_flags & APFS_READWRITE) && !(sb->s_flags & SB_RDONLY))
|
||||
apfs_notice(sb, "experimental write support is enabled");
|
||||
else
|
||||
sb->s_flags |= SB_RDONLY;
|
||||
if (!(sb->s_flags & SB_RDONLY)) {
|
||||
if (nxi->nx_flags & APFS_READWRITE) {
|
||||
apfs_notice(sb, "experimental write support is enabled");
|
||||
} else {
|
||||
apfs_warn(sb, "experimental writes disabled to avoid data loss");
|
||||
apfs_warn(sb, "if you really want them, check the README");
|
||||
sb->s_flags |= SB_RDONLY;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1290,8 +1301,12 @@ static int apfs_check_vol_features(struct super_block *sb)
|
||||
apfs_warn(sb, "incomplete restore is not supported");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (features & APFS_INCOMPAT_RESERVED_40) {
|
||||
apfs_warn(sb, "reserved incompatible feature flag is set");
|
||||
if (features & APFS_INCOMPAT_PFK) {
|
||||
apfs_warn(sb, "PFK is not supported");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (features & APFS_INCOMPAT_SECONDARY_FSROOT) {
|
||||
apfs_warn(sb, "secondary fsroot is not supported");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (features & APFS_INCOMPAT_SEALED_VOLUME) {
|
||||
@@ -1301,6 +1316,16 @@ static int apfs_check_vol_features(struct super_block *sb)
|
||||
}
|
||||
apfs_info(sb, "volume is sealed");
|
||||
}
|
||||
/*
|
||||
* As far as I can see, all this feature seems to do is define a new
|
||||
* flag (which I call APFS_FILE_EXTENT_PREALLOCATED) for extents that
|
||||
* are fully after the end of their file. I don't get why this change
|
||||
* is incompatible instead of read-only compatible, so I fear I might
|
||||
* be missing something. I will never be certain though, so for now
|
||||
* allow the mount and hope for the best.
|
||||
*/
|
||||
if (features & APFS_INCOMPAT_EXTENT_PREALLOC_FLAG)
|
||||
apfs_warn(sb, "extent prealloc flag is set");
|
||||
|
||||
features = le64_to_cpu(vsb_raw->apfs_fs_flags);
|
||||
/* Some encrypted volumes are readable anyway */
|
||||
@@ -1368,7 +1393,7 @@ static int apfs_setup_bdi(struct super_block *sb)
|
||||
|
||||
static void apfs_set_trans_buffer_limit(struct super_block *sb)
|
||||
{
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
unsigned long memsize_in_blocks;
|
||||
struct sysinfo info = {0};
|
||||
|
||||
@@ -1381,16 +1406,16 @@ static void apfs_set_trans_buffer_limit(struct super_block *sb)
|
||||
* commit often enough. This hack should make that happen in general,
|
||||
* but I still need to get the reclaim to work eventually (TODO).
|
||||
*/
|
||||
if (memsize_in_blocks >= 16 * TRANSACTION_BUFFERS_MAX)
|
||||
sbi->s_trans_buffers_max = TRANSACTION_BUFFERS_MAX;
|
||||
if (memsize_in_blocks >= 16 * APFS_TRANS_BUFFERS_MAX)
|
||||
nxi->nx_trans_buffers_max = APFS_TRANS_BUFFERS_MAX;
|
||||
else
|
||||
sbi->s_trans_buffers_max = memsize_in_blocks / 16;
|
||||
nxi->nx_trans_buffers_max = memsize_in_blocks / 16;
|
||||
}
|
||||
|
||||
static int apfs_fill_super(struct super_block *sb, void *data, int silent)
|
||||
{
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
struct inode *root;
|
||||
struct inode *root = NULL, *priv = NULL;
|
||||
int err;
|
||||
|
||||
ASSERT(sbi);
|
||||
@@ -1454,12 +1479,20 @@ static int apfs_fill_super(struct super_block *sb, void *data, int silent)
|
||||
err = PTR_ERR(root);
|
||||
goto failed_mount;
|
||||
}
|
||||
|
||||
sb->s_root = d_make_root(root);
|
||||
if (!sb->s_root) {
|
||||
apfs_err(sb, "unable to get root dentry");
|
||||
err = -ENOMEM;
|
||||
goto failed_mount;
|
||||
}
|
||||
|
||||
INIT_WORK(&sbi->s_orphan_cleanup_work, apfs_orphan_cleanup_work);
|
||||
if (!(sb->s_flags & SB_RDONLY)) {
|
||||
priv = sbi->s_private_dir;
|
||||
if (APFS_I(priv)->i_nchildren)
|
||||
schedule_work(&sbi->s_orphan_cleanup_work);
|
||||
}
|
||||
return 0;
|
||||
|
||||
failed_mount:
|
||||
@@ -1583,16 +1616,26 @@ 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;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0)
|
||||
struct file *file = NULL;
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
struct bdev_handle *handle = NULL;
|
||||
#endif
|
||||
struct block_device *bdev;
|
||||
struct block_device *bdev = NULL;
|
||||
|
||||
nxi = kzalloc(sizeof(*nxi), GFP_KERNEL);
|
||||
if (!nxi)
|
||||
return -ENOMEM;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0)
|
||||
file = bdev_file_open_by_path(dev_name, mode, &apfs_fs_type, NULL);
|
||||
if (IS_ERR(file)) {
|
||||
kfree(nxi);
|
||||
return PTR_ERR(file);
|
||||
}
|
||||
nxi->nx_bdev_file = file;
|
||||
bdev = file_bdev(file);
|
||||
#elif 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);
|
||||
@@ -1614,6 +1657,7 @@ static int apfs_attach_nxi(struct apfs_sb_info *sbi, const char *dev_name, fmode
|
||||
init_rwsem(&nxi->nx_big_sem);
|
||||
list_add(&nxi->nx_list, &nxs);
|
||||
INIT_LIST_HEAD(&nxi->vol_list);
|
||||
apfs_transaction_init(&nxi->nx_transaction);
|
||||
}
|
||||
|
||||
list_add(&sbi->list, &nxi->vol_list);
|
||||
|
||||
+69
-72
@@ -20,8 +20,12 @@ static int apfs_checkpoint_end(struct super_block *sb)
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_obj_phys *obj = &nxi->nx_raw->nx_o;
|
||||
struct buffer_head *bh = NULL;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 10, 0)
|
||||
struct address_space *bdev_map = nxi->nx_bdev->bd_mapping;
|
||||
#else
|
||||
struct inode *bdev_inode = nxi->nx_bdev->bd_inode;
|
||||
struct address_space *bdev_map = bdev_inode->i_mapping;
|
||||
#endif
|
||||
int err;
|
||||
|
||||
ASSERT(!(sb->s_flags & SB_RDONLY));
|
||||
@@ -262,6 +266,55 @@ static int apfs_read_ephemeral_objects(struct super_block *sb)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void apfs_trans_commit_work(struct work_struct *work)
|
||||
{
|
||||
struct super_block *sb = NULL;
|
||||
struct apfs_nxsb_info *nxi = NULL;
|
||||
struct apfs_nx_transaction *trans = NULL;
|
||||
int err;
|
||||
|
||||
trans = container_of(to_delayed_work(work), struct apfs_nx_transaction, t_work);
|
||||
nxi = container_of(trans, struct apfs_nxsb_info, nx_transaction);
|
||||
sb = trans->t_work_sb;
|
||||
|
||||
/*
|
||||
* If sb is set then the transaction already started, there is no need
|
||||
* for apfs_transaction_start() here. It would be cleaner to call it
|
||||
* anyway (and check in there if sb is set), but maxops is a problem
|
||||
* because we don't need any space. I really need to rethink that stuff
|
||||
* (TODO).
|
||||
*/
|
||||
down_write(&nxi->nx_big_sem);
|
||||
mutex_lock(&nxs_mutex);
|
||||
if (!sb || sb->s_flags & SB_RDONLY) {
|
||||
/* The commit already took place, or there was an abort */
|
||||
mutex_unlock(&nxs_mutex);
|
||||
up_write(&nxi->nx_big_sem);
|
||||
return;
|
||||
}
|
||||
|
||||
trans->t_state |= APFS_NX_TRANS_FORCE_COMMIT;
|
||||
err = apfs_transaction_commit(sb);
|
||||
if (err) {
|
||||
apfs_err(sb, "queued commit failed (err:%d)", err);
|
||||
apfs_transaction_abort(sb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_transaction_init - Initialize the transaction struct for the container
|
||||
* @trans: the transaction structure
|
||||
*/
|
||||
void apfs_transaction_init(struct apfs_nx_transaction *trans)
|
||||
{
|
||||
trans->t_state = 0;
|
||||
INIT_DELAYED_WORK(&trans->t_work, apfs_trans_commit_work);
|
||||
INIT_LIST_HEAD(&trans->t_inodes);
|
||||
INIT_LIST_HEAD(&trans->t_buffers);
|
||||
trans->t_buffers_count = 0;
|
||||
trans->t_starts_count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_transaction_start - Begin a new transaction
|
||||
* @sb: superblock structure
|
||||
@@ -272,9 +325,7 @@ static int apfs_read_ephemeral_objects(struct super_block *sb)
|
||||
*/
|
||||
int apfs_transaction_start(struct super_block *sb, struct apfs_max_ops maxops)
|
||||
{
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_vol_transaction *vol_trans = &sbi->s_transaction;
|
||||
struct apfs_nx_transaction *nx_trans = &nxi->nx_transaction;
|
||||
int err;
|
||||
|
||||
@@ -306,9 +357,6 @@ int apfs_transaction_start(struct super_block *sb, struct apfs_max_ops maxops)
|
||||
++nxi->nx_xid;
|
||||
nxi->nx_raw->nx_next_xid = cpu_to_le64(nxi->nx_xid + 1);
|
||||
|
||||
INIT_LIST_HEAD(&nx_trans->t_inodes);
|
||||
INIT_LIST_HEAD(&nx_trans->t_buffers);
|
||||
|
||||
err = apfs_read_spaceman(sb);
|
||||
if (err) {
|
||||
apfs_err(sb, "failed to read the spaceman");
|
||||
@@ -328,16 +376,7 @@ int apfs_transaction_start(struct super_block *sb, struct apfs_max_ops maxops)
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
if (!vol_trans->t_old_vsb) {
|
||||
vol_trans->t_old_vsb = sbi->s_vobject.o_bh;
|
||||
get_bh(vol_trans->t_old_vsb);
|
||||
|
||||
/* Backup the old tree roots; the node struct issues make this ugly */
|
||||
vol_trans->t_old_cat_root = *sbi->s_cat_root;
|
||||
get_bh(vol_trans->t_old_cat_root.object.o_bh);
|
||||
vol_trans->t_old_omap_root = *sbi->s_omap->omap_root;
|
||||
get_bh(vol_trans->t_old_omap_root.object.o_bh);
|
||||
|
||||
if (nx_trans->t_starts_count == 0) {
|
||||
err = apfs_map_volume_super(sb, true /* write */);
|
||||
if (err) {
|
||||
apfs_err(sb, "CoW failed for volume super");
|
||||
@@ -404,16 +443,16 @@ int apfs_transaction_flush_all_inodes(struct super_block *sb)
|
||||
mutex_unlock(&nxs_mutex);
|
||||
up_write(&nxi->nx_big_sem);
|
||||
|
||||
/* Unlocked, so it may call ->evict_inode() */
|
||||
/* Unlocked, so it may call evict() and wait for writeback */
|
||||
iput(inode);
|
||||
|
||||
down_write(&nxi->nx_big_sem);
|
||||
mutex_lock(&nxs_mutex);
|
||||
nx_trans->t_state = 0;
|
||||
|
||||
/* Transaction aborted by ->evict_inode(), error code is lost */
|
||||
/* Transaction aborted during writeback, error code is lost */
|
||||
if (sb->s_flags & SB_RDONLY) {
|
||||
apfs_err(sb, "abort during inode eviction");
|
||||
apfs_err(sb, "abort during inode writeback");
|
||||
return -EROFS;
|
||||
}
|
||||
}
|
||||
@@ -596,7 +635,6 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
|
||||
{
|
||||
struct apfs_spaceman *sm = APFS_SM(sb);
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_sb_info *sbi;
|
||||
struct apfs_nx_transaction *nx_trans = &nxi->nx_transaction;
|
||||
struct apfs_bh_info *bhi, *tmp;
|
||||
int err = 0;
|
||||
@@ -692,24 +730,6 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
|
||||
return err;
|
||||
}
|
||||
|
||||
list_for_each_entry(sbi, &nxi->vol_list, list) {
|
||||
struct apfs_vol_transaction *vol_trans = &sbi->s_transaction;
|
||||
|
||||
if (!vol_trans->t_old_vsb)
|
||||
continue;
|
||||
|
||||
brelse(vol_trans->t_old_vsb);
|
||||
vol_trans->t_old_vsb = NULL;
|
||||
|
||||
/* XXX: forget the buffers for the b-tree roots */
|
||||
vol_trans->t_old_omap_root.object.data = NULL;
|
||||
brelse(vol_trans->t_old_omap_root.object.o_bh);
|
||||
vol_trans->t_old_omap_root.object.o_bh = NULL;
|
||||
vol_trans->t_old_cat_root.object.data = NULL;
|
||||
brelse(vol_trans->t_old_cat_root.object.o_bh);
|
||||
vol_trans->t_old_cat_root.object.o_bh = NULL;
|
||||
}
|
||||
|
||||
for (bmap_idx = 0; bmap_idx < APFS_SM(sb)->sm_ip_bmaps_count; ++bmap_idx) {
|
||||
brelse(APFS_SM(sb)->sm_ip_bmaps[bmap_idx]);
|
||||
APFS_SM(sb)->sm_ip_bmaps[bmap_idx] = NULL;
|
||||
@@ -736,7 +756,7 @@ static bool apfs_transaction_need_commit(struct super_block *sb)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Avoid nested commits on ->evict_inode() */
|
||||
/* Avoid nested commits on inode writeback */
|
||||
if (nx_trans->t_state & APFS_NX_TRANS_COMMITTING)
|
||||
return false;
|
||||
|
||||
@@ -749,9 +769,9 @@ static bool apfs_transaction_need_commit(struct super_block *sb)
|
||||
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
|
||||
struct apfs_spaceman_free_queue *fq_ip = &sm_raw->sm_fq[APFS_SFQ_IP];
|
||||
struct apfs_spaceman_free_queue *fq_main = &sm_raw->sm_fq[APFS_SFQ_MAIN];
|
||||
int buffers_max = APFS_SB(sb)->s_trans_buffers_max;
|
||||
int starts_max = TRANSACTION_STARTS_MAX;
|
||||
int mq_max = TRANSACTION_MAIN_QUEUE_MAX;
|
||||
int buffers_max = nxi->nx_trans_buffers_max;
|
||||
int starts_max = APFS_TRANS_STARTS_MAX;
|
||||
int mq_max = APFS_TRANS_MAIN_QUEUE_MAX;
|
||||
|
||||
/*
|
||||
* Try to avoid committing halfway through a data block write,
|
||||
@@ -796,14 +816,22 @@ static bool apfs_transaction_need_commit(struct super_block *sb)
|
||||
int apfs_transaction_commit(struct super_block *sb)
|
||||
{
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_nx_transaction *trans = NULL;
|
||||
int err = 0;
|
||||
|
||||
trans = &nxi->nx_transaction;
|
||||
|
||||
if (apfs_transaction_need_commit(sb)) {
|
||||
err = apfs_transaction_commit_nx(sb);
|
||||
if (err) {
|
||||
apfs_err(sb, "transaction commit failed");
|
||||
return err;
|
||||
}
|
||||
trans->t_work_sb = NULL;
|
||||
cancel_delayed_work(&trans->t_work);
|
||||
} else {
|
||||
trans->t_work_sb = sb;
|
||||
mod_delayed_work(system_wq, &trans->t_work, msecs_to_jiffies(100));
|
||||
}
|
||||
|
||||
mutex_unlock(&nxs_mutex);
|
||||
@@ -889,7 +917,6 @@ static void apfs_force_readonly(struct apfs_nxsb_info *nxi)
|
||||
*/
|
||||
void apfs_transaction_abort(struct super_block *sb)
|
||||
{
|
||||
struct apfs_sb_info *sbi;
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_nx_transaction *nx_trans = &nxi->nx_transaction;
|
||||
struct apfs_bh_info *bhi, *tmp;
|
||||
@@ -924,35 +951,6 @@ void apfs_transaction_abort(struct super_block *sb)
|
||||
kfree(bhi);
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: get rid of all this stuff, it makes little sense. Maybe do an
|
||||
* actual read-only remount?
|
||||
*/
|
||||
list_for_each_entry(sbi, &nxi->vol_list, list) {
|
||||
struct apfs_vol_transaction *vol_trans = &sbi->s_transaction;
|
||||
|
||||
if (!vol_trans->t_old_vsb)
|
||||
continue;
|
||||
|
||||
/* Restore volume state for all volumes */
|
||||
brelse(sbi->s_vobject.o_bh);
|
||||
sbi->s_vobject.o_bh = vol_trans->t_old_vsb;
|
||||
sbi->s_vobject.data = sbi->s_vobject.o_bh->b_data;
|
||||
sbi->s_vobject.block_nr = vol_trans->t_old_vsb->b_blocknr;
|
||||
sbi->s_vsb_raw = (void *)vol_trans->t_old_vsb->b_data;
|
||||
vol_trans->t_old_vsb = NULL;
|
||||
|
||||
/* XXX: restore the old b-tree root nodes */
|
||||
brelse(sbi->s_omap->omap_root->object.o_bh);
|
||||
*(sbi->s_omap->omap_root) = vol_trans->t_old_omap_root;
|
||||
vol_trans->t_old_omap_root.object.o_bh = NULL;
|
||||
vol_trans->t_old_omap_root.object.data = NULL;
|
||||
brelse(sbi->s_cat_root->object.o_bh);
|
||||
*(sbi->s_cat_root) = vol_trans->t_old_cat_root;
|
||||
vol_trans->t_old_cat_root.object.o_bh = NULL;
|
||||
vol_trans->t_old_cat_root.object.data = NULL;
|
||||
}
|
||||
|
||||
sm = APFS_SM(sb);
|
||||
if (sm) {
|
||||
for (bmap_idx = 0; bmap_idx < sm->sm_ip_bmaps_count; ++bmap_idx) {
|
||||
@@ -971,7 +969,6 @@ void apfs_transaction_abort(struct super_block *sb)
|
||||
mutex_unlock(&nxs_mutex);
|
||||
up_write(&nxi->nx_big_sem);
|
||||
|
||||
/* ->evict_inode() will just fail if it starts a new transaction */
|
||||
list_for_each_entry_safe(ai, ai_tmp, &nx_trans->t_inodes, i_list) {
|
||||
list_del_init(&ai->i_list);
|
||||
iput(&ai->vfs_inode);
|
||||
|
||||
Reference in new issue
Block a user