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
+1
-2
@@ -22,8 +22,7 @@ This module is the result of reverse engineering and testing has been limited.
|
||||
If you make use of the write support, expect data corruption. Please report any
|
||||
issues that you find, but I can't promise a quick resolution at this stage.
|
||||
|
||||
Many features are not yet implemented, including encryption and most forms of
|
||||
compression.
|
||||
Encryption is not yet implemented even in read-only mode.
|
||||
|
||||
Build
|
||||
=====
|
||||
|
||||
@@ -133,6 +133,13 @@ struct apfs_spaceman {
|
||||
u32 sm_cib_count; /* Number of chunk-info blocks */
|
||||
u64 sm_free_count; /* Number of free blocks */
|
||||
u32 sm_addr_offset; /* Offset of cib addresses in @sm_raw */
|
||||
|
||||
/*
|
||||
* A range of freed blocks not yet put in the free queue. Extend this as
|
||||
* much as possible before creating an actual record.
|
||||
*/
|
||||
u64 sm_free_cache_base;
|
||||
u64 sm_free_cache_blkcnt;
|
||||
};
|
||||
|
||||
#define TRANSACTION_MAIN_QUEUE_MAX 4096
|
||||
@@ -660,6 +667,7 @@ struct apfs_phys_extent {
|
||||
u64 blkcount;
|
||||
u64 len; /* In bytes */
|
||||
u32 refcnt;
|
||||
u8 kind;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -667,12 +675,14 @@ struct apfs_phys_extent {
|
||||
*/
|
||||
struct apfs_dstream_info {
|
||||
struct super_block *ds_sb; /* Filesystem superblock */
|
||||
struct inode *ds_inode; /* NULL for xattr dstreams */
|
||||
u64 ds_id; /* ID of the extent records */
|
||||
u64 ds_size; /* Length of the stream */
|
||||
u64 ds_sparse_bytes;/* Hole byte count in stream */
|
||||
struct apfs_file_extent ds_cached_ext; /* Latest extent record */
|
||||
bool ds_ext_dirty; /* Is ds_cached_ext dirty? */
|
||||
spinlock_t ds_ext_lock; /* Protects ds_cached_ext */
|
||||
bool ds_shared; /* Has multiple references? */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -861,6 +871,8 @@ 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 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);
|
||||
extern int apfs_clone_extents(struct apfs_dstream_info *dstream, u64 new_id);
|
||||
|
||||
/* file.c */
|
||||
extern int apfs_fsync(struct file *file, loff_t start, loff_t end, int datasync);
|
||||
@@ -874,7 +886,11 @@ extern struct inode *apfs_new_inode(struct inode *dir, umode_t mode,
|
||||
dev_t rdev);
|
||||
extern int apfs_create_inode_rec(struct super_block *sb, struct inode *inode,
|
||||
struct dentry *dentry);
|
||||
extern int apfs_inode_create_exclusive_dstream(struct inode *inode);
|
||||
extern int APFS_CREATE_INODE_REC_MAXOPS(void);
|
||||
extern int __apfs_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned int len, unsigned int flags, struct page **pagep, void **fsdata);
|
||||
extern 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);
|
||||
extern int apfs_dstream_adj_refcnt(struct apfs_dstream_info *dstream, u32 delta);
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0)
|
||||
extern int apfs_setattr(struct dentry *dentry, struct iattr *iattr);
|
||||
@@ -957,6 +973,7 @@ extern int apfs_switch_to_snapshot(struct super_block *sb);
|
||||
|
||||
/* spaceman.c */
|
||||
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);
|
||||
|
||||
|
||||
+5
-5
@@ -49,11 +49,11 @@
|
||||
#define APFS_OBJECT_TYPE_GBITMAP 0x00000019
|
||||
#define APFS_OBJECT_TYPE_GBITMAP_TREE 0x0000001a
|
||||
#define APFS_OBJECT_TYPE_GBITMAP_BLOCK 0x0000001b
|
||||
#define OBJECT_TYPE_ER_RECOVERY_BLOCK 0x0000001c
|
||||
#define OBJECT_TYPE_SNAP_META_EXT 0x0000001d
|
||||
#define OBJECT_TYPE_INTEGRITY_META 0x0000001e
|
||||
#define OBJECT_TYPE_FEXT_TREE 0x0000001f
|
||||
#define OBJECT_TYPE_RESERVED_20 0x00000020
|
||||
#define APFS_OBJECT_TYPE_ER_RECOVERY_BLOCK 0x0000001c
|
||||
#define APFS_OBJECT_TYPE_SNAP_META_EXT 0x0000001d
|
||||
#define APFS_OBJECT_TYPE_INTEGRITY_META 0x0000001e
|
||||
#define APFS_OBJECT_TYPE_FEXT_TREE 0x0000001f
|
||||
#define APFS_OBJECT_TYPE_RESERVED_20 0x00000020
|
||||
#define APFS_OBJECT_TYPE_INVALID 0x00000000
|
||||
#define APFS_OBJECT_TYPE_TEST 0x000000ff
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PACKAGE_NAME="linux-apfs-rw"
|
||||
PACKAGE_VERSION="0.3.0"
|
||||
PACKAGE_VERSION="0.3.1"
|
||||
|
||||
BUILT_MODULE_NAME[0]="apfs"
|
||||
DEST_MODULE_LOCATION[0]="/extra"
|
||||
|
||||
@@ -41,6 +41,10 @@ static vm_fault_t apfs_page_mkwrite(struct vm_fault *vmf)
|
||||
goto out;
|
||||
apfs_inode_join_transaction(sb, inode);
|
||||
|
||||
err = apfs_inode_create_exclusive_dstream(inode);
|
||||
if (err)
|
||||
goto out_abort;
|
||||
|
||||
lock_page(page);
|
||||
wait_for_stable_page(page);
|
||||
if (page->mapping != inode->i_mapping) {
|
||||
@@ -130,15 +134,28 @@ int apfs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
|
||||
}
|
||||
|
||||
const struct file_operations apfs_file_operations = {
|
||||
.llseek = generic_file_llseek,
|
||||
.read_iter = generic_file_read_iter,
|
||||
.write_iter = generic_file_write_iter,
|
||||
.mmap = apfs_file_mmap,
|
||||
.open = generic_file_open,
|
||||
.fsync = apfs_fsync,
|
||||
.unlocked_ioctl = apfs_file_ioctl,
|
||||
.llseek = generic_file_llseek,
|
||||
.read_iter = generic_file_read_iter,
|
||||
.write_iter = generic_file_write_iter,
|
||||
.mmap = apfs_file_mmap,
|
||||
.open = generic_file_open,
|
||||
.fsync = apfs_fsync,
|
||||
.unlocked_ioctl = apfs_file_ioctl,
|
||||
.copy_file_range = generic_copy_file_range,
|
||||
.remap_file_range = apfs_remap_file_range,
|
||||
};
|
||||
|
||||
#if LINUX_VERSION_CODE == KERNEL_VERSION(5, 3, 0)
|
||||
/*
|
||||
* This is needed mainly to test clones with xfstests, so we only support the
|
||||
* kernel version I use during testing. TODO: support all kernel versions.
|
||||
*/
|
||||
int apfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, u64 start, u64 len)
|
||||
{
|
||||
return generic_block_fiemap(inode, fieinfo, start, len, apfs_get_block);
|
||||
}
|
||||
#endif
|
||||
|
||||
const struct inode_operations apfs_file_inode_operations = {
|
||||
.getattr = apfs_getattr,
|
||||
.listxattr = apfs_listxattr,
|
||||
@@ -148,4 +165,7 @@ const struct inode_operations apfs_file_inode_operations = {
|
||||
.fileattr_get = apfs_fileattr_get,
|
||||
.fileattr_set = apfs_fileattr_set,
|
||||
#endif
|
||||
#if LINUX_VERSION_CODE == KERNEL_VERSION(5, 3, 0)
|
||||
.fiemap = apfs_fiemap,
|
||||
#endif
|
||||
};
|
||||
@@ -92,6 +92,59 @@ out:
|
||||
}
|
||||
#define APFS_CREATE_DSTREAM_REC_MAXOPS 1
|
||||
|
||||
static int apfs_check_dstream_refcnt(struct inode *inode);
|
||||
static int apfs_put_dstream_rec(struct apfs_dstream_info *dstream);
|
||||
|
||||
/**
|
||||
* apfs_inode_create_exclusive_dstream - Make an inode's dstream not shared
|
||||
* @inode: the vfs inode
|
||||
*
|
||||
* Returns 0 on success, or a negative error code in case of failure.
|
||||
*/
|
||||
int apfs_inode_create_exclusive_dstream(struct inode *inode)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
|
||||
struct apfs_inode_info *ai = APFS_I(inode);
|
||||
struct apfs_dstream_info *dstream = &ai->i_dstream;
|
||||
u64 new_id;
|
||||
int err;
|
||||
|
||||
if (!ai->i_has_dstream || !dstream->ds_shared)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* The ds_shared field is not updated when the other user of the
|
||||
* dstream puts it, so it could be a false positive. Check it again
|
||||
* before actually putting the dstream. The double query is wasteful,
|
||||
* but I don't know if it makes sense to optimize this (TODO).
|
||||
*/
|
||||
err = apfs_check_dstream_refcnt(inode);
|
||||
if (err)
|
||||
return err;
|
||||
if (!dstream->ds_shared)
|
||||
return 0;
|
||||
err = apfs_put_dstream_rec(dstream);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
||||
new_id = le64_to_cpu(vsb_raw->apfs_next_obj_id);
|
||||
le64_add_cpu(&vsb_raw->apfs_next_obj_id, 1);
|
||||
|
||||
err = apfs_clone_extents(dstream, new_id);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
dstream->ds_id = new_id;
|
||||
err = apfs_create_dstream_rec(dstream);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
dstream->ds_shared = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_inode_create_dstream_rec - Create the data stream record for an inode
|
||||
* @inode: the vfs inode
|
||||
@@ -105,7 +158,7 @@ static int apfs_inode_create_dstream_rec(struct inode *inode)
|
||||
int err;
|
||||
|
||||
if (ai->i_has_dstream)
|
||||
return 0;
|
||||
return apfs_inode_create_exclusive_dstream(inode);
|
||||
|
||||
err = apfs_create_dstream_rec(&ai->i_dstream);
|
||||
if (err)
|
||||
@@ -116,13 +169,14 @@ static int apfs_inode_create_dstream_rec(struct inode *inode)
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_put_dstream_rec - Put a reference for a data stream record
|
||||
* @dstream: data stream info
|
||||
* apfs_dstream_adj_refcnt - Adjust dstream record refcount
|
||||
* @dstream: data stream info
|
||||
* @delta: desired change in reference count
|
||||
*
|
||||
* Deletes the record if the reference count goes to zero. Returns 0 on success
|
||||
* or a negative error code in case of failure.
|
||||
*/
|
||||
static int apfs_put_dstream_rec(struct apfs_dstream_info *dstream)
|
||||
int apfs_dstream_adj_refcnt(struct apfs_dstream_info *dstream, u32 delta)
|
||||
{
|
||||
struct super_block *sb = dstream->ds_sb;
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
@@ -133,6 +187,8 @@ static int apfs_put_dstream_rec(struct apfs_dstream_info *dstream)
|
||||
u32 refcnt;
|
||||
int ret;
|
||||
|
||||
ASSERT(APFS_I(dstream->ds_inode)->i_has_dstream);
|
||||
|
||||
apfs_init_dstream_id_key(dstream->ds_id, &key);
|
||||
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
||||
if (!query)
|
||||
@@ -143,7 +199,7 @@ static int apfs_put_dstream_rec(struct apfs_dstream_info *dstream)
|
||||
ret = apfs_btree_query(sb, &query);
|
||||
if (ret) {
|
||||
if (ret == -ENODATA)
|
||||
ret = dstream->ds_size ? -EFSCORRUPTED : 0;
|
||||
ret = -EFSCORRUPTED;
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -155,18 +211,35 @@ static int apfs_put_dstream_rec(struct apfs_dstream_info *dstream)
|
||||
raw_val = *(struct apfs_dstream_id_val *)(raw + query->off);
|
||||
refcnt = le32_to_cpu(raw_val.refcnt);
|
||||
|
||||
if (refcnt == 1) {
|
||||
refcnt += delta;
|
||||
if (refcnt == 0) {
|
||||
ret = apfs_btree_remove(query);
|
||||
goto out;
|
||||
}
|
||||
|
||||
raw_val.refcnt = cpu_to_le32(refcnt - 1);
|
||||
raw_val.refcnt = cpu_to_le32(refcnt);
|
||||
ret = apfs_btree_replace(query, NULL /* key */, 0 /* key_len */, &raw_val, sizeof(raw_val));
|
||||
out:
|
||||
apfs_free_query(query);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_put_dstream_rec - Put a reference for a data stream record
|
||||
* @dstream: data stream info
|
||||
*
|
||||
* Deletes the record if the reference count goes to zero. Returns 0 on success
|
||||
* or a negative error code in case of failure.
|
||||
*/
|
||||
static int apfs_put_dstream_rec(struct apfs_dstream_info *dstream)
|
||||
{
|
||||
struct apfs_inode_info *ai = APFS_I(dstream->ds_inode);
|
||||
|
||||
if (!ai->i_has_dstream)
|
||||
return 0;
|
||||
return apfs_dstream_adj_refcnt(dstream, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_create_crypto_rec - Create the crypto state record for an inode
|
||||
* @inode: the vfs inode
|
||||
@@ -382,15 +455,7 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if 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)
|
||||
#else
|
||||
static int apfs_write_begin(struct file *file, struct address_space *mapping,
|
||||
loff_t pos, unsigned int len, unsigned int flags,
|
||||
struct page **pagep, void **fsdata)
|
||||
#endif
|
||||
int __apfs_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned int len, unsigned int flags, struct page **pagep, void **fsdata)
|
||||
{
|
||||
struct inode *inode = mapping->host;
|
||||
struct apfs_dstream_info *dstream = &APFS_I(inode)->i_dstream;
|
||||
@@ -400,36 +465,19 @@ static int apfs_write_begin(struct file *file, struct address_space *mapping,
|
||||
unsigned int blocksize, block_start, block_end, from, to;
|
||||
pgoff_t index = pos >> PAGE_SHIFT;
|
||||
sector_t iblock = (sector_t)index << (PAGE_SHIFT - inode->i_blkbits);
|
||||
int blkcount = (len + sb->s_blocksize - 1) >> inode->i_blkbits;
|
||||
loff_t i_blks_end;
|
||||
struct apfs_max_ops maxops;
|
||||
int err;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
|
||||
unsigned int flags = 0;
|
||||
#endif
|
||||
|
||||
if (unlikely(pos >= APFS_MAX_FILE_SIZE))
|
||||
return -EFBIG;
|
||||
|
||||
maxops.cat = APFS_CREATE_DSTREAM_REC_MAXOPS +
|
||||
APFS_CREATE_CRYPTO_REC_MAXOPS +
|
||||
APFS_UPDATE_INODE_MAXOPS() +
|
||||
blkcount * APFS_GET_NEW_BLOCK_MAXOPS();
|
||||
maxops.blks = blkcount;
|
||||
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
if (err)
|
||||
return err;
|
||||
apfs_inode_join_transaction(sb, inode);
|
||||
|
||||
err = apfs_inode_create_dstream_rec(inode);
|
||||
if (err)
|
||||
goto out_abort;
|
||||
return err;
|
||||
|
||||
if(apfs_vol_is_encrypted(sb)) {
|
||||
err = apfs_create_crypto_rec(inode);
|
||||
if (err)
|
||||
goto out_abort;
|
||||
return err;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
|
||||
@@ -439,10 +487,8 @@ static int apfs_write_begin(struct file *file, struct address_space *mapping,
|
||||
#else
|
||||
page = grab_cache_page_write_begin(mapping, index, flags | AOP_FLAG_NOFS);
|
||||
#endif
|
||||
if (!page) {
|
||||
err = -ENOMEM;
|
||||
goto out_abort;
|
||||
}
|
||||
if (!page)
|
||||
return -ENOMEM;
|
||||
if (!page_has_buffers(page))
|
||||
create_empty_buffers(page, sb->s_blocksize, 0);
|
||||
|
||||
@@ -496,19 +542,55 @@ static int apfs_write_begin(struct file *file, struct address_space *mapping,
|
||||
out_put_page:
|
||||
unlock_page(page);
|
||||
put_page(page);
|
||||
out_abort:
|
||||
return err;
|
||||
}
|
||||
|
||||
#if 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)
|
||||
#else
|
||||
static int apfs_write_begin(struct file *file, struct address_space *mapping,
|
||||
loff_t pos, unsigned int len, unsigned int flags,
|
||||
struct page **pagep, void **fsdata)
|
||||
#endif
|
||||
{
|
||||
struct inode *inode = mapping->host;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
int blkcount = (len + sb->s_blocksize - 1) >> inode->i_blkbits;
|
||||
struct apfs_max_ops maxops;
|
||||
int err;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
|
||||
unsigned int flags = 0;
|
||||
#endif
|
||||
|
||||
if (unlikely(pos >= APFS_MAX_FILE_SIZE))
|
||||
return -EFBIG;
|
||||
|
||||
maxops.cat = APFS_CREATE_DSTREAM_REC_MAXOPS +
|
||||
APFS_CREATE_CRYPTO_REC_MAXOPS +
|
||||
APFS_UPDATE_INODE_MAXOPS() +
|
||||
blkcount * APFS_GET_NEW_BLOCK_MAXOPS();
|
||||
maxops.blks = blkcount;
|
||||
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = __apfs_write_begin(file, mapping, pos, len, flags, pagep, fsdata);
|
||||
if (err)
|
||||
goto fail;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
apfs_transaction_abort(sb);
|
||||
return err;
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
{
|
||||
struct inode *inode = mapping->host;
|
||||
struct apfs_dstream_info *dstream = &APFS_I(inode)->i_dstream;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_nx_transaction *trans = &APFS_NXI(sb)->nx_transaction;
|
||||
int ret, err;
|
||||
|
||||
ret = generic_write_end(file, mapping, pos, len, copied, page, fsdata);
|
||||
@@ -517,7 +599,24 @@ static int apfs_write_end(struct file *file, struct address_space *mapping,
|
||||
truncate_pagecache(inode, inode->i_size);
|
||||
err = apfs_truncate(dstream, inode->i_size);
|
||||
if (err)
|
||||
goto out_abort;
|
||||
return err;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
struct inode *inode = mapping->host;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_nx_transaction *trans = &APFS_NXI(sb)->nx_transaction;
|
||||
int ret, err;
|
||||
|
||||
ret = __apfs_write_end(file, mapping, pos, len, copied, page, fsdata);
|
||||
if (ret < 0) {
|
||||
err = ret;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if ((pos + ret) & (sb->s_blocksize - 1))
|
||||
@@ -529,7 +628,7 @@ static int apfs_write_end(struct file *file, struct address_space *mapping,
|
||||
if (!err)
|
||||
return ret;
|
||||
|
||||
out_abort:
|
||||
fail:
|
||||
apfs_transaction_abort(sb);
|
||||
return err;
|
||||
}
|
||||
@@ -776,6 +875,59 @@ static struct inode *apfs_iget_locked(struct super_block *sb, u64 cnid)
|
||||
return iget5_locked(sb, cnid, apfs_test_inode, apfs_set_inode, &cnid);
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_check_dstream_refcnt - Check if an inode's dstream is shared
|
||||
* @inode: the inode to check
|
||||
*
|
||||
* Sets the value of ds_shared for the inode's dstream. Returns 0 on success,
|
||||
* or a negative error code in case of failure.
|
||||
*/
|
||||
static int apfs_check_dstream_refcnt(struct inode *inode)
|
||||
{
|
||||
struct apfs_inode_info *ai = APFS_I(inode);
|
||||
struct apfs_dstream_info *dstream = &ai->i_dstream;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
struct apfs_key key;
|
||||
struct apfs_query *query = NULL;
|
||||
struct apfs_dstream_id_val raw_val;
|
||||
void *raw = NULL;
|
||||
u32 refcnt;
|
||||
int ret;
|
||||
|
||||
if (!ai->i_has_dstream) {
|
||||
dstream->ds_shared = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
apfs_init_dstream_id_key(dstream->ds_id, &key);
|
||||
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
||||
if (!query)
|
||||
return -ENOMEM;
|
||||
query->key = &key;
|
||||
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
|
||||
|
||||
ret = apfs_btree_query(sb, &query);
|
||||
if (ret) {
|
||||
if (ret == -ENODATA)
|
||||
ret = -EFSCORRUPTED;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (query->len != sizeof(raw_val)) {
|
||||
ret = -EFSCORRUPTED;
|
||||
goto fail;
|
||||
}
|
||||
raw = query->node->object.data;
|
||||
raw_val = *(struct apfs_dstream_id_val *)(raw + query->off);
|
||||
refcnt = le32_to_cpu(raw_val.refcnt);
|
||||
|
||||
dstream->ds_shared = refcnt > 1;
|
||||
fail:
|
||||
apfs_free_query(query);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_iget - Populate inode structures with metadata from disk
|
||||
* @sb: filesystem superblock
|
||||
@@ -807,6 +959,9 @@ struct inode *apfs_iget(struct super_block *sb, u64 cnid)
|
||||
}
|
||||
err = apfs_inode_from_query(query, inode);
|
||||
apfs_free_query(query);
|
||||
if (err)
|
||||
goto fail;
|
||||
err = apfs_check_dstream_refcnt(inode);
|
||||
if (err)
|
||||
goto fail;
|
||||
up_read(&nxi->nx_big_sem);
|
||||
@@ -1282,7 +1437,7 @@ int APFS_UPDATE_INODE_MAXOPS(void)
|
||||
*/
|
||||
static int apfs_delete_inode(struct inode *inode)
|
||||
{
|
||||
struct apfs_dstream_info *dstream = &APFS_I(inode)->i_dstream;
|
||||
struct apfs_dstream_info *dstream = NULL;
|
||||
struct apfs_query *query;
|
||||
int ret;
|
||||
|
||||
@@ -1290,6 +1445,17 @@ static int apfs_delete_inode(struct inode *inode)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* 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
|
||||
* big reason to improve it (TODO)
|
||||
*/
|
||||
ret = apfs_inode_create_exclusive_dstream(inode);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* TODO: truncate an orphan inode in multiple transactions */
|
||||
dstream = &APFS_I(inode)->i_dstream;
|
||||
ret = apfs_truncate(dstream, 0 /* new_size */);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -1405,6 +1571,7 @@ struct inode *apfs_new_inode(struct inode *dir, umode_t mode, dev_t rdev)
|
||||
dstream->ds_id = cnid;
|
||||
dstream->ds_size = 0;
|
||||
dstream->ds_sparse_bytes = 0;
|
||||
dstream->ds_shared = false;
|
||||
|
||||
now = current_time(inode);
|
||||
inode->i_atime = inode->i_mtime = inode->i_ctime = ai->i_crtime = now;
|
||||
|
||||
@@ -167,6 +167,16 @@ static int apfs_node_min_table_size(struct super_block *sb, u32 type, u16 flags)
|
||||
val_size = sizeof(__le64); /* We assume no ghosts here */
|
||||
toc_size = sizeof(struct apfs_kvoff);
|
||||
break;
|
||||
case APFS_OBJECT_TYPE_OMAP_SNAPSHOT:
|
||||
key_size = sizeof(__le64);
|
||||
val_size = leaf ? sizeof(struct apfs_omap_snapshot) : sizeof(__le64);
|
||||
toc_size = sizeof(struct apfs_kvoff);
|
||||
break;
|
||||
case APFS_OBJECT_TYPE_FEXT_TREE:
|
||||
key_size = sizeof(struct apfs_fext_tree_key);
|
||||
val_size = leaf ? sizeof(struct apfs_fext_tree_val) : sizeof(__le64);
|
||||
toc_size = sizeof(struct apfs_kvoff);
|
||||
break;
|
||||
default:
|
||||
/* Make room for one record at least */
|
||||
toc_size = sizeof(struct apfs_kvloc);
|
||||
@@ -238,6 +248,7 @@ int apfs_make_empty_btree_root(struct super_block *sb, u32 subtype, u64 *oid)
|
||||
return err;
|
||||
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
||||
le64_add_cpu(&vsb_raw->apfs_fs_alloc_count, 1);
|
||||
le64_add_cpu(&vsb_raw->apfs_total_blocks_alloced, 1);
|
||||
|
||||
bh = apfs_getblk(sb, bno);
|
||||
if (!bh)
|
||||
@@ -315,7 +326,9 @@ static struct apfs_node *apfs_create_node(struct super_block *sb, u32 storage)
|
||||
err = apfs_spaceman_allocate_block(sb, &bno, true /* backwards */);
|
||||
if (err)
|
||||
return ERR_PTR(err);
|
||||
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
||||
le64_add_cpu(&vsb_raw->apfs_fs_alloc_count, 1);
|
||||
le64_add_cpu(&vsb_raw->apfs_total_blocks_alloced, 1);
|
||||
|
||||
oid = le64_to_cpu(msb_raw->nx_next_oid);
|
||||
le64_add_cpu(&msb_raw->nx_next_oid, 1);
|
||||
@@ -328,7 +341,9 @@ static struct apfs_node *apfs_create_node(struct super_block *sb, u32 storage)
|
||||
if (err)
|
||||
return ERR_PTR(err);
|
||||
/* We don't write to the container's omap */
|
||||
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
||||
le64_add_cpu(&vsb_raw->apfs_fs_alloc_count, 1);
|
||||
le64_add_cpu(&vsb_raw->apfs_total_blocks_alloced, 1);
|
||||
oid = bno;
|
||||
break;
|
||||
case APFS_OBJ_EPHEMERAL:
|
||||
@@ -428,6 +443,7 @@ int apfs_delete_node(struct apfs_query *query)
|
||||
vsb_raw = APFS_SB(sb)->s_vsb_raw;
|
||||
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
||||
le64_add_cpu(&vsb_raw->apfs_fs_alloc_count, -1);
|
||||
le64_add_cpu(&vsb_raw->apfs_total_blocks_freed, 1);
|
||||
return 0;
|
||||
case APFS_QUERY_OMAP:
|
||||
case APFS_QUERY_EXTENTREF:
|
||||
@@ -439,6 +455,7 @@ int apfs_delete_node(struct apfs_query *query)
|
||||
vsb_raw = APFS_SB(sb)->s_vsb_raw;
|
||||
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
||||
le64_add_cpu(&vsb_raw->apfs_fs_alloc_count, -1);
|
||||
le64_add_cpu(&vsb_raw->apfs_total_blocks_freed, 1);
|
||||
return 0;
|
||||
case APFS_QUERY_FREE_QUEUE:
|
||||
err = apfs_cpoint_data_free(sb, bno);
|
||||
|
||||
@@ -315,6 +315,7 @@ struct buffer_head *apfs_read_ephemeral_object(struct super_block *sb, u64 oid)
|
||||
struct buffer_head *apfs_read_object_block(struct super_block *sb, u64 bno, bool write, bool preserve)
|
||||
{
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_superblock *vsb_raw = NULL;
|
||||
struct buffer_head *bh, *new_bh;
|
||||
struct apfs_obj_phys *obj;
|
||||
u32 type;
|
||||
@@ -353,9 +354,20 @@ struct buffer_head *apfs_read_object_block(struct super_block *sb, u64 bno, bool
|
||||
}
|
||||
memcpy(new_bh->b_data, bh->b_data, sb->s_blocksize);
|
||||
|
||||
/* Don't free the old copy if it's part of a snapshot */
|
||||
if (!preserve)
|
||||
/*
|
||||
* Don't free the old copy of the object if it's part of a snapshot.
|
||||
* Also increase the allocation count, except for the volume superblock
|
||||
* which is never counted there.
|
||||
*/
|
||||
if (!preserve) {
|
||||
err = apfs_free_queue_insert(sb, bh->b_blocknr, 1);
|
||||
} else if ((type & APFS_OBJECT_TYPE_MASK) != APFS_OBJECT_TYPE_FS) {
|
||||
vsb_raw = APFS_SB(sb)->s_vsb_raw;
|
||||
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
||||
le64_add_cpu(&vsb_raw->apfs_fs_alloc_count, 1);
|
||||
le64_add_cpu(&vsb_raw->apfs_total_blocks_alloced, 1);
|
||||
}
|
||||
|
||||
brelse(bh);
|
||||
bh = new_bh;
|
||||
new_bh = NULL;
|
||||
|
||||
+6
-4
@@ -33,7 +33,6 @@ static int apfs_create_superblock_snapshot(struct super_block *sb, u64 *bno)
|
||||
goto fail;
|
||||
}
|
||||
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
||||
le64_add_cpu(&vsb_raw->apfs_fs_alloc_count, 1);
|
||||
|
||||
curr_bh = sbi->s_vobject.o_bh;
|
||||
memcpy(snap_bh->b_data, curr_bh->b_data, sb->s_blocksize);
|
||||
@@ -108,12 +107,15 @@ static int apfs_create_snap_metadata_rec(struct inode *mntpoint, struct apfs_nod
|
||||
now = current_time(mntpoint);
|
||||
raw_val->create_time = cpu_to_le64(timespec64_to_ns(&now));
|
||||
raw_val->change_time = raw_val->create_time;
|
||||
raw_val->inum = 0; /* TODO: what is this? */
|
||||
raw_val->extentref_tree_type = vsb_raw->apfs_extentref_tree_type;
|
||||
raw_val->flags = 0;
|
||||
raw_val->name_len = cpu_to_le16(name_len + 1); /* Count the null byte */
|
||||
strcpy(raw_val->name, name);
|
||||
|
||||
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
|
||||
raw_val->inum = vsb_raw->apfs_next_obj_id;
|
||||
le64_add_cpu(&vsb_raw->apfs_next_obj_id, 1);
|
||||
|
||||
err = apfs_btree_insert(query, &raw_key, sizeof(raw_key), raw_val, val_len);
|
||||
fail:
|
||||
kfree(raw_val);
|
||||
@@ -342,11 +344,11 @@ static int apfs_do_ioc_take_snapshot(struct inode *mntpoint, const char *name)
|
||||
*/
|
||||
err = apfs_transaction_flush_all_inodes(sb);
|
||||
if (err)
|
||||
return err;
|
||||
goto fail;
|
||||
|
||||
err = apfs_create_superblock_snapshot(sb, &sblock_oid);
|
||||
if (err)
|
||||
return err;
|
||||
goto fail;
|
||||
|
||||
err = apfs_create_snap_meta_records(mntpoint, name, sblock_oid);
|
||||
if (err)
|
||||
|
||||
+54
-2
@@ -458,6 +458,8 @@ int apfs_read_spaceman(struct super_block *sb)
|
||||
if (sb->s_flags & SB_RDONLY) /* The space manager won't be needed */
|
||||
return 0;
|
||||
|
||||
spaceman->sm_free_cache_base = spaceman->sm_free_cache_blkcnt = 0;
|
||||
|
||||
sm_bh = apfs_read_ephemeral_object(sb, oid);
|
||||
if (IS_ERR(sm_bh))
|
||||
return PTR_ERR(sm_bh);
|
||||
@@ -607,14 +609,17 @@ static inline int apfs_chunk_mark_free(struct super_block *sb, char *bitmap,
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_free_queue_insert - Add a block range to its free queue
|
||||
* 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(struct super_block *sb, u64 bno, u64 count)
|
||||
int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
|
||||
{
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_spaceman *sm = APFS_SM(sb);
|
||||
@@ -674,6 +679,53 @@ fail:
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_free_queue_insert - Add a block range to its free queue
|
||||
* @sb: superblock structure
|
||||
* @bno: first block number to free
|
||||
* @count: number of consecutive blocks to free
|
||||
*
|
||||
* Uses a cache to delay the actual tree operations as much as possible.
|
||||
*
|
||||
* Returns 0 on success or a negative error code in case of failure.
|
||||
*/
|
||||
int apfs_free_queue_insert(struct super_block *sb, u64 bno, u64 count)
|
||||
{
|
||||
struct apfs_spaceman *sm = APFS_SM(sb);
|
||||
int err;
|
||||
|
||||
if (sm->sm_free_cache_base == 0) {
|
||||
/* Nothing yet cached */
|
||||
sm->sm_free_cache_base = bno;
|
||||
sm->sm_free_cache_blkcnt = count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* First attempt to extend the cache of freed blocks, but never cache
|
||||
* a range that doesn't belong to a single free queue.
|
||||
*/
|
||||
if (apfs_block_in_ip(sm, bno) == apfs_block_in_ip(sm, sm->sm_free_cache_base)) {
|
||||
if (bno == sm->sm_free_cache_base + sm->sm_free_cache_blkcnt) {
|
||||
sm->sm_free_cache_blkcnt += count;
|
||||
return 0;
|
||||
}
|
||||
if (bno + count == sm->sm_free_cache_base) {
|
||||
sm->sm_free_cache_base -= count;
|
||||
sm->sm_free_cache_blkcnt += count;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Failed to extend the cache, so flush it and replace it */
|
||||
err = apfs_free_queue_insert_nocache(sb, sm->sm_free_cache_base, sm->sm_free_cache_blkcnt);
|
||||
if (err)
|
||||
return err;
|
||||
sm->sm_free_cache_base = bno;
|
||||
sm->sm_free_cache_blkcnt = count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_chunk_alloc_free - Allocate or free block in given CIB and chunk
|
||||
* @sb: superblock structure
|
||||
|
||||
@@ -733,6 +733,7 @@ static struct inode *apfs_alloc_inode(struct super_block *sb)
|
||||
ai->vfs_inode.i_version = 1;
|
||||
#endif
|
||||
dstream->ds_sb = sb;
|
||||
dstream->ds_inode = &ai->vfs_inode;
|
||||
dstream->ds_cached_ext.len = 0;
|
||||
dstream->ds_ext_dirty = false;
|
||||
ai->i_nchildren = 0;
|
||||
|
||||
@@ -590,6 +590,7 @@ int apfs_transaction_flush_all_inodes(struct super_block *sb)
|
||||
*/
|
||||
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;
|
||||
@@ -604,6 +605,17 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* Now that nothing else will be freed, flush the last update to the
|
||||
* free queues so that it can be committed to disk with the other bhs
|
||||
*/
|
||||
if (sm->sm_free_cache_base) {
|
||||
err = apfs_free_queue_insert_nocache(sb, sm->sm_free_cache_base, sm->sm_free_cache_blkcnt);
|
||||
if (err)
|
||||
return err;
|
||||
sm->sm_free_cache_base = sm->sm_free_cache_blkcnt = 0;
|
||||
}
|
||||
|
||||
list_for_each_entry_safe(bhi, tmp, &nx_trans->t_buffers, list) {
|
||||
struct buffer_head *bh = bhi->bh;
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ static void apfs_dstream_from_xattr(struct super_block *sb, struct apfs_xattr *x
|
||||
struct apfs_xattr_dstream *xdata = (void *)xattr->xdata;
|
||||
|
||||
dstream->ds_sb = sb;
|
||||
dstream->ds_inode = NULL;
|
||||
dstream->ds_id = le64_to_cpu(xdata->xattr_obj_id);
|
||||
dstream->ds_size = le64_to_cpu(xdata->dstream.size);
|
||||
dstream->ds_sparse_bytes = 0; /* Irrelevant for xattrs */
|
||||
@@ -75,6 +76,9 @@ static void apfs_dstream_from_xattr(struct super_block *sb, struct apfs_xattr *x
|
||||
dstream->ds_cached_ext.len = 0;
|
||||
dstream->ds_ext_dirty = false;
|
||||
spin_lock_init(&dstream->ds_ext_lock);
|
||||
|
||||
/* Xattrs can't be cloned */
|
||||
dstream->ds_shared = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user