mirror of
https://github.com/linux-apfs/linux-apfs-rw.git
synced 2026-08-18 02:19:59 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a2db9bd68 | ||
|
|
96e6d41bb7 | ||
|
|
6c2ce34a67 | ||
|
|
a34220c93d | ||
|
|
49507c65bc | ||
|
|
dfdafd2a07 | ||
|
|
2920180ed6 | ||
|
|
0667dbff17 | ||
|
|
af2ee526c0 | ||
|
|
44f4b4f631 | ||
|
|
b44d2a37bf | ||
|
|
4881d7c707 | ||
|
|
2982c8655f | ||
|
|
707d91b486 | ||
|
|
3e5c902a9a | ||
|
|
695455bfdb | ||
|
|
071cdacc32 | ||
|
|
57eb739f59 | ||
|
|
f088d1a2e3 | ||
|
|
8bcaaebb89 | ||
|
|
781e0024d2 | ||
|
|
5d41bd7e64 | ||
|
|
4e9984c815 | ||
|
|
f1c9ab6c38 |
No files matched your search
@@ -117,6 +117,11 @@ static inline bool apfs_node_has_fixed_kv_size(struct apfs_node *node)
|
||||
return (node->flags & APFS_BTNODE_FIXED_KV_SIZE) != 0;
|
||||
}
|
||||
|
||||
struct apfs_ip_bitmap_block_info {
|
||||
bool dirty; /* Do we need to commit this block to disk? */
|
||||
void *block; /* In-memory address of the bitmap block */
|
||||
};
|
||||
|
||||
/*
|
||||
* Space manager data in memory.
|
||||
*/
|
||||
@@ -147,12 +152,12 @@ struct apfs_spaceman {
|
||||
/* Number of ip bitmaps */
|
||||
u32 sm_ip_bmaps_count;
|
||||
/* List of ip bitmaps, in order */
|
||||
struct buffer_head *sm_ip_bmaps[];
|
||||
struct apfs_ip_bitmap_block_info 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 +171,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 */
|
||||
@@ -224,6 +222,11 @@ 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
|
||||
@@ -254,6 +257,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;
|
||||
@@ -333,9 +337,6 @@ 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;
|
||||
};
|
||||
@@ -1070,6 +1071,7 @@ extern int apfs_read_spaceman(struct super_block *sb);
|
||||
extern int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count);
|
||||
extern int apfs_free_queue_insert(struct super_block *sb, u64 bno, u64 count);
|
||||
extern int apfs_spaceman_allocate_block(struct super_block *sb, u64 *bno, bool backwards);
|
||||
extern int apfs_write_ip_bitmaps(struct super_block *sb);
|
||||
|
||||
/* super.c */
|
||||
extern int apfs_map_volume_super_bno(struct super_block *sb, u64 bno, bool check);
|
||||
@@ -1081,6 +1083,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);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PACKAGE_NAME="linux-apfs-rw"
|
||||
PACKAGE_VERSION="0.3.9"
|
||||
PACKAGE_VERSION="0.3.12"
|
||||
|
||||
BUILT_MODULE_NAME[0]="apfs"
|
||||
DEST_MODULE_LOCATION[0]="/extra"
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include <linux/blk_types.h>
|
||||
#include "apfs.h"
|
||||
|
||||
#define MAX(X, Y) ((X) <= (Y) ? (Y) : (X))
|
||||
|
||||
/**
|
||||
* apfs_ext_is_hole - Does this extent represent a hole in a sparse file?
|
||||
* @extent: the extent to check
|
||||
@@ -1651,7 +1649,7 @@ static int apfs_dstream_delete_front(struct super_block *sb, u64 ds_id)
|
||||
int ret;
|
||||
|
||||
fq = &sm_raw->sm_fq[APFS_SFQ_MAIN];
|
||||
if (le64_to_cpu(fq->sfq_count) > TRANSACTION_MAIN_QUEUE_MAX)
|
||||
if (le64_to_cpu(fq->sfq_count) > APFS_TRANS_MAIN_QUEUE_MAX)
|
||||
return -EAGAIN;
|
||||
|
||||
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
||||
@@ -1710,7 +1708,7 @@ next_extent:
|
||||
}
|
||||
}
|
||||
|
||||
if (le64_to_cpu(fq->sfq_count) <= TRANSACTION_MAIN_QUEUE_MAX)
|
||||
if (le64_to_cpu(fq->sfq_count) <= APFS_TRANS_MAIN_QUEUE_MAX)
|
||||
goto next_extent;
|
||||
ret = -EAGAIN;
|
||||
out:
|
||||
@@ -1973,7 +1971,7 @@ restart:
|
||||
prev_end = prev_ext.bno + prev_ext.blkcount;
|
||||
if (prev_end < *paddr_end) {
|
||||
/* The extent to put is part of a snapshot */
|
||||
tmp.phys_block_num = MAX(prev_end, paddr_min);
|
||||
tmp.phys_block_num = max(prev_end, paddr_min);
|
||||
tmp.len = (*paddr_end - tmp.phys_block_num) << sb->s_blocksize_bits;
|
||||
ret = apfs_create_update_pext(query, &tmp, -1);
|
||||
*paddr_end = tmp.phys_block_num;
|
||||
@@ -2113,7 +2111,7 @@ restart:
|
||||
prev_end = prev_ext.bno + prev_ext.blkcount;
|
||||
if (prev_end < *paddr_end) {
|
||||
/* The extent to take is part of a snapshot */
|
||||
tmp.phys_block_num = MAX(prev_end, paddr_min);
|
||||
tmp.phys_block_num = max(prev_end, paddr_min);
|
||||
tmp.len = (*paddr_end - tmp.phys_block_num) << sb->s_blocksize_bits;
|
||||
ret = apfs_create_update_pext(query, &tmp, +1);
|
||||
*paddr_end = tmp.phys_block_num;
|
||||
|
||||
@@ -565,7 +565,11 @@ int __apfs_write_begin(struct file *file, struct address_space *mapping, loff_t
|
||||
}
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
err = __block_write_begin(page_folio(page), pos, len, apfs_get_new_block);
|
||||
#else
|
||||
err = __block_write_begin(page, pos, len, apfs_get_new_block);
|
||||
#endif
|
||||
if (err) {
|
||||
apfs_err(sb, "CoW failed in inode 0x%llx", apfs_ino(inode));
|
||||
goto out_put_page;
|
||||
@@ -580,7 +584,11 @@ out_put_page:
|
||||
return err;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
static int apfs_write_begin(struct file *file, struct address_space *mapping,
|
||||
loff_t pos, unsigned int len,
|
||||
struct folio **foliop, void **fsdata)
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
|
||||
static int apfs_write_begin(struct file *file, struct address_space *mapping,
|
||||
loff_t pos, unsigned int len,
|
||||
struct page **pagep, void **fsdata)
|
||||
@@ -592,6 +600,10 @@ static int apfs_write_begin(struct file *file, struct address_space *mapping,
|
||||
{
|
||||
struct inode *inode = mapping->host;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
struct page *page = NULL;
|
||||
struct page **pagep = &page;
|
||||
#endif
|
||||
int blkcount = (len + sb->s_blocksize - 1) >> inode->i_blkbits;
|
||||
struct apfs_max_ops maxops;
|
||||
int err;
|
||||
@@ -615,6 +627,9 @@ static int apfs_write_begin(struct file *file, struct address_space *mapping,
|
||||
err = __apfs_write_begin(file, mapping, pos, len, flags, pagep, fsdata);
|
||||
if (err)
|
||||
goto fail;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
*foliop = page_folio(page);
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
@@ -628,7 +643,11 @@ int __apfs_write_end(struct file *file, struct address_space *mapping, loff_t po
|
||||
struct apfs_dstream_info *dstream = &APFS_I(inode)->i_dstream;
|
||||
int ret, err;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
ret = generic_write_end(file, mapping, pos, len, copied, page_folio(page), fsdata);
|
||||
#else
|
||||
ret = generic_write_end(file, mapping, pos, len, copied, page, fsdata);
|
||||
#endif
|
||||
dstream->ds_size = i_size_read(inode);
|
||||
if (ret < len && pos + len > inode->i_size) {
|
||||
truncate_pagecache(inode, inode->i_size);
|
||||
@@ -641,13 +660,22 @@ int __apfs_write_end(struct file *file, struct address_space *mapping, loff_t po
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
static int apfs_write_end(struct file *file, struct address_space *mapping,
|
||||
loff_t pos, unsigned int len, unsigned int copied,
|
||||
struct folio *folio, void *fsdata)
|
||||
#else
|
||||
static int apfs_write_end(struct file *file, struct address_space *mapping,
|
||||
loff_t pos, unsigned int len, unsigned int copied,
|
||||
struct page *page, void *fsdata)
|
||||
#endif
|
||||
{
|
||||
struct inode *inode = mapping->host;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_nx_transaction *trans = &APFS_NXI(sb)->nx_transaction;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
struct page *page = &folio->page;
|
||||
#endif
|
||||
int ret, err;
|
||||
|
||||
ret = __apfs_write_end(file, mapping, pos, len, copied, page, fsdata);
|
||||
@@ -693,9 +721,9 @@ static const struct address_space_operations apfs_aops = {
|
||||
#endif
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
|
||||
.readahead = apfs_readahead,
|
||||
.readahead = apfs_readahead,
|
||||
#else
|
||||
.readpages = apfs_readpages,
|
||||
.readpages = apfs_readpages,
|
||||
#endif
|
||||
|
||||
.write_begin = apfs_write_begin,
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include <linux/string.h>
|
||||
#include "libzbitmap.h"
|
||||
|
||||
#define MIN(x, y) ((x) > (y) ? (y) : (x))
|
||||
|
||||
#define ZBM_MAGIC "ZBM\x09"
|
||||
#define ZBM_MAGIC_SZ 4
|
||||
|
||||
|
||||
+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
|
||||
|
||||
+263
-135
File diff suppressed because it is too large.
Load diff
+121
-111
@@ -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));
|
||||
@@ -97,13 +101,18 @@ static bool apfs_transaction_has_room(struct super_block *sb, struct apfs_max_op
|
||||
static int apfs_read_single_ephemeral_object(struct super_block *sb, struct apfs_checkpoint_mapping *map)
|
||||
{
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_nx_superblock *raw_sb = NULL;
|
||||
struct apfs_ephemeral_object_info *list = NULL;
|
||||
struct buffer_head *bh = NULL;
|
||||
char *object = NULL;
|
||||
int count;
|
||||
u32 size;
|
||||
u64 bno, oid;
|
||||
int err, i;
|
||||
u32 data_blks, size;
|
||||
u64 data_base, bno, oid;
|
||||
int err, i, data_idx;
|
||||
|
||||
raw_sb = nxi->nx_raw;
|
||||
data_base = le64_to_cpu(raw_sb->nx_xp_data_base);
|
||||
data_blks = le32_to_cpu(raw_sb->nx_xp_data_blocks);
|
||||
|
||||
list = nxi->nx_eph_list;
|
||||
count = nxi->nx_eph_count;
|
||||
@@ -131,8 +140,9 @@ static int apfs_read_single_ephemeral_object(struct super_block *sb, struct apfs
|
||||
if (!object)
|
||||
return -ENOMEM;
|
||||
|
||||
data_idx = bno - data_base;
|
||||
for (i = 0; i < size >> sb->s_blocksize_bits; ++i) {
|
||||
bh = apfs_sb_bread(sb, bno + i);
|
||||
bh = apfs_sb_bread(sb, data_base + data_idx);
|
||||
if (!bh) {
|
||||
apfs_err(sb, "failed to read ephemeral block");
|
||||
err = -EIO;
|
||||
@@ -141,6 +151,9 @@ static int apfs_read_single_ephemeral_object(struct super_block *sb, struct apfs
|
||||
memcpy(object + (i << sb->s_blocksize_bits), bh->b_data, sb->s_blocksize);
|
||||
brelse(bh);
|
||||
bh = NULL;
|
||||
/* Somewhat surprisingly, objects can wrap around */
|
||||
if (++data_idx == data_blks)
|
||||
data_idx = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -262,6 +275,53 @@ 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);
|
||||
if (!sb || sb->s_flags & SB_RDONLY) {
|
||||
/* The commit already took place, or there was an abort */
|
||||
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,18 +332,14 @@ 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;
|
||||
|
||||
down_write(&nxi->nx_big_sem);
|
||||
mutex_lock(&nxs_mutex); /* Don't mount during a transaction */
|
||||
|
||||
if (sb->s_flags & SB_RDONLY) {
|
||||
/* A previous transaction has failed; this should be rare */
|
||||
mutex_unlock(&nxs_mutex);
|
||||
up_write(&nxi->nx_big_sem);
|
||||
return -EROFS;
|
||||
}
|
||||
@@ -295,7 +351,6 @@ int apfs_transaction_start(struct super_block *sb, struct apfs_max_ops maxops)
|
||||
if (!nxi->nx_eph_list) {
|
||||
err = apfs_read_ephemeral_objects(sb);
|
||||
if (err) {
|
||||
mutex_unlock(&nxs_mutex);
|
||||
up_write(&nxi->nx_big_sem);
|
||||
apfs_err(sb, "failed to read the ephemeral objects");
|
||||
return err;
|
||||
@@ -306,9 +361,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 +380,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");
|
||||
@@ -401,14 +444,12 @@ int apfs_transaction_flush_all_inodes(struct super_block *sb)
|
||||
list_del_init(&ai->i_list);
|
||||
|
||||
nx_trans->t_state |= APFS_NX_TRANS_COMMITTING;
|
||||
mutex_unlock(&nxs_mutex);
|
||||
up_write(&nxi->nx_big_sem);
|
||||
|
||||
/* 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 during writeback, error code is lost */
|
||||
@@ -432,18 +473,24 @@ int apfs_transaction_flush_all_inodes(struct super_block *sb)
|
||||
static int apfs_write_single_ephemeral_object(struct super_block *sb, struct apfs_obj_phys *obj_raw, const struct apfs_checkpoint_mapping *map)
|
||||
{
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_nx_superblock *raw_sb = NULL;
|
||||
struct buffer_head *bh = NULL;
|
||||
u64 bno;
|
||||
u32 size;
|
||||
int err, i;
|
||||
u64 data_base, bno;
|
||||
u32 data_blks, size;
|
||||
int err, i, data_idx;
|
||||
|
||||
raw_sb = nxi->nx_raw;
|
||||
data_base = le64_to_cpu(raw_sb->nx_xp_data_base);
|
||||
data_blks = le32_to_cpu(raw_sb->nx_xp_data_blocks);
|
||||
|
||||
bno = le64_to_cpu(map->cpm_paddr);
|
||||
size = le32_to_cpu(map->cpm_size);
|
||||
obj_raw->o_xid = cpu_to_le64(nxi->nx_xid);
|
||||
apfs_multiblock_set_csum((char *)obj_raw, size);
|
||||
|
||||
data_idx = bno - data_base;
|
||||
for (i = 0; i < size >> sb->s_blocksize_bits; ++i) {
|
||||
bh = apfs_getblk(sb, bno + i);
|
||||
bh = apfs_getblk(sb, data_base + data_idx);
|
||||
if (!bh) {
|
||||
apfs_err(sb, "failed to map ephemeral block");
|
||||
return -EIO;
|
||||
@@ -457,6 +504,9 @@ static int apfs_write_single_ephemeral_object(struct super_block *sb, struct apf
|
||||
memcpy(bh->b_data, (char *)obj_raw + (i << sb->s_blocksize_bits), sb->s_blocksize);
|
||||
brelse(bh);
|
||||
bh = NULL;
|
||||
/* Somewhat surprisingly, objects can wrap around */
|
||||
if (++data_idx == data_blks)
|
||||
data_idx = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -532,15 +582,6 @@ static int apfs_write_ephemeral_objects(struct super_block *sb)
|
||||
eph_info = &nxi->nx_eph_list[i];
|
||||
data_next = (data_index + data_len) % data_blks;
|
||||
obj_blkcnt = eph_info->size >> sb->s_blocksize_bits;
|
||||
if (obj_blkcnt > data_blks - data_next) {
|
||||
/*
|
||||
* This multiblock object does not fit in what's left
|
||||
* of the ring buffer, so move it to the beginning and
|
||||
* leave some empty blocks.
|
||||
*/
|
||||
data_len += data_blks - data_next;
|
||||
data_next = 0;
|
||||
}
|
||||
|
||||
err = apfs_create_cpoint_map(sb, cpm, eph_info->object, data_base + data_next, eph_info->size);
|
||||
if (err) {
|
||||
@@ -596,11 +637,9 @@ 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;
|
||||
u32 bmap_idx;
|
||||
|
||||
ASSERT(!(sb->s_flags & SB_RDONLY));
|
||||
|
||||
@@ -624,6 +663,16 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
|
||||
}
|
||||
sm->sm_free_cache_base = sm->sm_free_cache_blkcnt = 0;
|
||||
}
|
||||
/*
|
||||
* Writing the ip bitmaps modifies the spaceman, so it must happen
|
||||
* before we commit the ephemeral objects. It must also happen after we
|
||||
* flush the free queue, in case the last freed range was in the ip.
|
||||
*/
|
||||
err = apfs_write_ip_bitmaps(sb);
|
||||
if (err) {
|
||||
apfs_err(sb, "failed to commit the ip bitmaps");
|
||||
return err;
|
||||
}
|
||||
err = apfs_write_ephemeral_objects(sb);
|
||||
if (err)
|
||||
return err;
|
||||
@@ -644,7 +693,11 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
|
||||
}
|
||||
list_for_each_entry_safe(bhi, tmp, &nx_trans->t_buffers, list) {
|
||||
struct buffer_head *bh = bhi->bh;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 11, 0)
|
||||
struct folio *folio = NULL;
|
||||
#else
|
||||
struct page *page = NULL;
|
||||
#endif
|
||||
bool is_metadata;
|
||||
|
||||
ASSERT(buffer_trans(bh));
|
||||
@@ -664,27 +717,44 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
|
||||
kfree(bhi);
|
||||
bhi = NULL;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 11, 0)
|
||||
folio = page_folio(bh->b_page);
|
||||
folio_get(folio);
|
||||
#else
|
||||
page = bh->b_page;
|
||||
get_page(page);
|
||||
#endif
|
||||
|
||||
is_metadata = buffer_csum(bh);
|
||||
clear_buffer_csum(bh);
|
||||
put_bh(bh);
|
||||
bh = NULL;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 11, 0)
|
||||
folio_lock(folio);
|
||||
folio_mkclean(folio);
|
||||
#else
|
||||
/* Future writes to mmapped areas should fault for CoW */
|
||||
lock_page(page);
|
||||
page_mkclean(page);
|
||||
#endif
|
||||
/* XXX: otherwise, the page cache fills up and crashes the machine */
|
||||
if (!is_metadata) {
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 11, 0)
|
||||
try_to_free_buffers(folio);
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
|
||||
try_to_free_buffers(page_folio(page));
|
||||
#else
|
||||
try_to_free_buffers(page);
|
||||
#endif
|
||||
}
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 11, 0)
|
||||
folio_unlock(folio);
|
||||
folio_put(folio);
|
||||
#else
|
||||
unlock_page(page);
|
||||
put_page(page);
|
||||
#endif
|
||||
}
|
||||
err = apfs_checkpoint_end(sb);
|
||||
if (err) {
|
||||
@@ -692,30 +762,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;
|
||||
}
|
||||
APFS_SM(sb)->sm_raw = NULL;
|
||||
|
||||
nx_trans->t_starts_count = 0;
|
||||
nx_trans->t_buffers_count = 0;
|
||||
return 0;
|
||||
@@ -749,9 +795,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,17 +842,24 @@ 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);
|
||||
up_write(&nxi->nx_big_sem);
|
||||
return 0;
|
||||
}
|
||||
@@ -889,19 +942,15 @@ 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;
|
||||
struct apfs_inode_info *ai, *ai_tmp;
|
||||
struct apfs_spaceman *sm = NULL;
|
||||
u32 bmap_idx;
|
||||
|
||||
if (sb->s_flags & SB_RDONLY) {
|
||||
/* Transaction already aborted, do nothing */
|
||||
ASSERT(list_empty(&nx_trans->t_inodes));
|
||||
ASSERT(list_empty(&nx_trans->t_buffers));
|
||||
mutex_unlock(&nxs_mutex);
|
||||
up_write(&nxi->nx_big_sem);
|
||||
return;
|
||||
}
|
||||
@@ -924,51 +973,12 @@ 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) {
|
||||
brelse(sm->sm_ip_bmaps[bmap_idx]);
|
||||
sm->sm_ip_bmaps[bmap_idx] = NULL;
|
||||
}
|
||||
APFS_SM(sb)->sm_raw = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* It's not possible to undo in-memory changes from old operations in
|
||||
* the aborted transaction. To avoid corruption, never write again.
|
||||
*/
|
||||
apfs_force_readonly(nxi);
|
||||
|
||||
mutex_unlock(&nxs_mutex);
|
||||
up_write(&nxi->nx_big_sem);
|
||||
|
||||
list_for_each_entry_safe(ai, ai_tmp, &nx_trans->t_inodes, i_list) {
|
||||
|
||||
@@ -12,10 +12,9 @@
|
||||
#include <linux/types.h>
|
||||
#include <linux/nls.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/version.h>
|
||||
#include "unicode.h"
|
||||
|
||||
#define MIN(X, Y) ((X) <= (Y) ? (X) : (Y))
|
||||
|
||||
/* 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? */
|
||||
static u16 apfs_nfd_trie[];
|
||||
@@ -234,7 +233,7 @@ static int apfs_get_normalization_length(const char *utf8str, unsigned int total
|
||||
while (1) {
|
||||
if (!total_len || !*utf8str)
|
||||
return norm_len;
|
||||
utf8len = utf8_to_utf32(utf8str, MIN(total_len, 4), &utf32char);
|
||||
utf8len = utf8_to_utf32(utf8str, min(total_len, 4U), &utf32char);
|
||||
if (utf8len < 0) /* Invalid unicode; don't normalize anything */
|
||||
return 0;
|
||||
|
||||
@@ -306,7 +305,7 @@ new_starter:
|
||||
unicode_t utf32char;
|
||||
int utf8len, pos;
|
||||
|
||||
utf8len = utf8_to_utf32(utf8str, MIN(total_len, 4), &utf32char);
|
||||
utf8len = utf8_to_utf32(utf8str, min(total_len, 4U), &utf32char);
|
||||
for (pos = 0;; pos++, str_pos++) {
|
||||
unicode_t utf32norm;
|
||||
u8 ccc;
|
||||
|
||||
Reference in new issue
Block a user