22 Commits
Author SHA1 Message Date
Ernesto A. Fernández 72e8873a6a 0.3.1
Snapshots and clones seem to be working properly now, though testing has
been somewhat limited. As far as I know (I could definitely be wrong),
this is the last thing that was needed to fully support writes to
unencrypted volumes, so it's an important change that warrants a new
release. Everything remains experimental, of course.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-03-23 21:15:51 -03:00
Ernesto A. Fernández 8bb6d325ea Try to make longer free queue records
When running xfstests generic/142 (a file clone test), I encounter the
following error:

  APFS (2cg): creation of cpm blocks not yet supported

This is expected to happen when we have an absurdly large number of free
queue records. In this particular test, many writes are made to the
middle of files, so each of these writes becomes its own extent. When
the file is later deleted, all of these extents are deleted in a single
massive transaction. Each of them becomes its own free queue record, and
there is no opportunity to flush the queue.

There are three problems in here. The first one is that writes to the
middle of files should be allowed to allocate longer extents. The second
one is that truncation of orphan files should get split in multiple
transactions. These problems are important, but for now I just want to
test file clones, so I'll leave the fixes for later.

The third problem, much easier to fix, is that the driver should never
create bursts of small consecutive free queue records: it should just
merge them all into a bigger one. For that purpose, keep a cache of the
latest freed range, and always attempt to extend it instead of creating
a new record directly.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-03-23 00:50:22 -03:00
Ernesto A. Fernández 7ea6920bc4 Give a clone its own dstream before mmap writes
Call apfs_inode_create_exclusive_dstream() during mkwrite(), just like
we do during write_begin(), or else things could get weird for cloned
files. This patch fixes generic/140 of xfstests.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-03-22 19:18:34 -03:00
Ernesto A. Fernández 699a07189a Don't try to map invalid blocks in get_block()
If __apfs_get_block() gets called with a block number after the end of
the file, it still maps the buffer head successfully but using invalid
information from the previous extent. So far this has never been a
problem for any of the callers, but the previous patch implemented
FIEMAP and generic_block_fiemap() appears to use an unmapped buffer head
to check for EOF. With the current code, it just loops forever.

So, do the right thing and check that the logical block is in range
before doing any extent lookups. At this point there is no reason why
apfs_extent_read() should ever fail to find a matching extent, so make
it return EFSCORRUPTED when that happens.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-03-22 18:30:18 -03:00
Ernesto A. Fernández 1490bc7d36 Support FIEMAP for the 5.3 kernel
I'm trying to run the "clone" group of xfstests, but it seems that those
tests require the FIEMAP ioctl to confirm that clones share the same
physical extents. This doesn't seem trivial to implement for all kernel
versions, so for now only do it for 5.3, the one I use for testing.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-03-22 16:14:02 -03:00
Ernesto A. Fernández d74165587b Don't free the blocks on update extent deletion
If an UPDATE record is created to take a reference, and then a reference
to that same range gets put, then the UPDATE extent will simply get
deleted. When that happens, we mistakenly mark all of its blocks as
free, but the blocks are still in use by the original snapshot. Fix
this.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-03-20 21:29:59 -03:00
Ernesto A. Fernández e8847c734a Don't copy the sparse flag into cloned inodes
When an inode becomes a clone, the internal flags from the original get
copied to it. The problem is that a few of these flags imply the
presence of a matching xfield, but those haven't been created at this
point. The sparse xfield, in particular, may never get created at all if
the original file is no longer sparse. Don't copy those xfield flags,
just set them when the xfield gets created as usual.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-03-17 21:32:10 -03:00
Ernesto A. Fernández 201b0872fd Correct return value for apfs_remap_file_range()
The return value for apfs_remap_file_range() on sucess is supposed to be
the length of the copied region, not 0. I haven't checked if this has
any practical consequences, but fix it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-03-17 17:20:49 -03:00
Ernesto A. Fernández 396650b056 Don't let copy_file_range() use clones
I'm seeing a silly regression in generic/430: now that clones are
implemented, copy_file_range() has started to use them under the hood,
but it only wants to clone a portion of the file, and that's not
something I'm interested in supporting.

To avoid this on newer kernels, set ->copy_file_range() to the generic
implementation. For older kernels, start throwing errors if anything
calls apfs_remap_file_range() for partial ranges. This is the right
thing to do in general, since we don't support that.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-03-17 17:20:49 -03:00
Ernesto A. Fernández b00b571757 Implement writes to cloned extents
This rewrites a considerable part of the physical extents code, so let's
hope for the best.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-03-17 01:42:44 -03:00
Ernesto A. Fernández c994a77bfd Fork cloned dstreams on write operations
When first created, a clone is just a new reference to the original
dstream. At this point the driver can't even delete a cloned file,
because all of the dstream extents go away, and those are still in use
by the other copies.

So, before any write operation happens on a clone file, create a new
dstream for the modified copy, with its own file extents and new
references to the physical extents. This should at least allow deletion
to work correctly, but real writes will not, because individual extent
changes haven't been implemented yet. It won't be hard though, because
now my mind is a little more clear about this stuff.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-03-17 01:42:44 -03:00
Ernesto A. Fernández 573fe59e7f Implement basic file cloning functionality
Implement the FICLONE ioctl to be able to create clones of files. This
will never work the same as macos, because Linux requires that the user
first creates the target file, and only then it can be made into a
clone. A crash halfway throught would leave an empty file in place of
the clone.

This patch only duplicates the data stream, it does not deal with the
actual extents. Reads from clones should work correctly, but writes will
not. I'll get to that right away.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-03-08 22:22:18 -03:00
Ernesto A. Fernández 76babcc7ad Fix cleanup on failure during snapshot creation
It seems that I messed up the cleanups during some late fixups to the
code in apfs_do_ioc_take_snapshot(): the transaction could be left open
in some cases. Fix it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-03-08 19:18:50 -03:00
Ernesto A. Fernández 9c39466269 Make updates to blocks_freed and blocks_alloced
The apfs_total_blocks_freed and apfs_total_blocks_alloced fields of the
volume superblock keep track of the total number of blocks that have
been freed/alloced through the whole lifetime of the volume so far.
Since the value of these fields depends on historical operations, it
can't be calculated from the volume contents, so I figured I could just
ignore them without consequences.

This seems to be mostly correct, but the official fsck does complain if
the freed blocks appear to be more than the alloced blocks. It's just a
warning, not an error, but it does often trigger after the official
driver makes changes to one of my images. So, try to be careful with
these two fields from now on.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-03-06 22:05:29 -03:00
Ernesto A. Fernández 7a5d4085ea Fix apfs_node_min_table_size() for non-leaf nodes
In the previous patch I forgot to consider that index nodes have
different value sizes than leaf nodes, even for fixed key/value size
trees. Fix it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-02-22 17:30:45 -03:00
Ernesto A. Fernández d29943de45 Fix apfs_node_min_table_size() for new tree types
When I started working with OMAP_SNAPSHOT trees, I forgot to add support
for them inside apfs_node_min_table_size(). The result is that the index
of each of those trees is initialized to a tiny minimum size, as if the
keys and values were of variable length. This went unnoticed for a while
because of other bugs in my fsck, which I've recently fixed in
5a20a212d8f0 ("apfsck: fix checks of index size for fixed kv size"). The
official fsck always complained though, I just wasn't using it:

  ** Checking the snapshot metadata.
  error: btn: invalid btn_table_space (0, 64), given btn_flags (0x7)

So, implement apfs_node_min_table_size() properly for the OMAP_SNAPSHOT
tree type. For consistency with the fsck, also implement the FEXT tree
type, but I don't actually support writes to sealed volumes in the
driver at the moment.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-02-21 22:55:31 -03:00
Ernesto A. Fernández 2e63fbc8dd Add APFS prefix to all object type macros
Some recently defined object types are missing the "APFS_" prefix. I've
fixed this a while back in the fsck, do it in the driver too.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-02-21 22:54:39 -03:00
Ernesto A. Fernández 15d5ab65d5 Set a snapshot's inum on creation
Right now the "inum" field of new snapshot metadata record gets set to
zero, and the official fsck is complaining about it. I was confused for
a while about this field because I thought it referred to an actual file
somewhere, but now I'm pretty sure it's just an identifier for the
snapshot, which gets picked from the same pool as regular inode numbers.
So, do that.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-02-21 20:16:18 -03:00
Ernesto A. Fernández 8c330866de Fix volume block count after snapshots
There are two bugs in the way apfs_fs_alloc_count is calculated after a
snapshot. I had not noticed them so far because it randomly gets the
right number as long as no node splits happen, but the new test I'm
working on (called apfs/006 for now) does split nodes and reports the
corruption.

Anyway, the problems are that the volume superblock should never get
counted in apfs_fs_alloc_count, not even after a snapshot; and that
regular CoW of virtual nodes needs to increase apfs_fs_alloc_count if
the original is preserved in a snapshot. Fix both now.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-02-17 22:53:18 -03:00
Ernesto A. Fernández 0e15c381c1 Fix mmap regressions from snapshot writes
The previous commit has caused regressions for a couple of mmap tests
from xfstests. I always assumed that all buffer heads mapping a physical
block would be the same object, with the same metadata, but that's only
true for buffer heads in the same mapping. So the apfs_sb_bread() inside
apfs_zero_dstream_tail() will not get the same bh that joined the
transaction via mmap, and two separate CoWs will happen, losing some of
the writes.

So, first of all, apfs_zero_dstream_tail() can't just read blocks from
the device like it used to, it needs to have a reference to the file
mapping and work with that. Put a pointer to the inode in the dstream
info for this purpose. Now, since individual buffer tails are already
zeroed during ->write_begin(), spin the part of that code that doesn't
deal with transactions into __apfs_write_begin() and __apfs_write_end(),
and use those here. Finally, reorder apfs_dstream_get_new_block() to
avoid problems with the extent cache during this recursion.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-02-16 18:47:28 -03:00
Ernesto A. Fernández 9e5e2aaf17 Implement writes to snapshot files
Make the necessary changes to the extent code so that writes to files
that are part of a snapshot will work correctly. I don't believe there
is any real risk of deleting the original content at this point, because
those physical extents are kept in a separate tree. The real problem is
keeping track of extent references, because that will become important
for the official implementation if a snapshot gets deleted.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-02-16 18:47:28 -03:00
Ernesto A. Fernández b5a2f9fb62 Update the README with compression support
As I mentioned in the release notes, the driver now has a reasonable
support for compression. The README still claims that it doesn't,
though, because I forgot to update it before the release. Fix it now.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-02-02 19:19:00 -03:00
14 changed files with 1124 additions and 325 deletions

No files matched your search

+1 -2
View File
@@ -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
=====
+17
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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"
+750 -254
View File
File diff suppressed because it is too large. Load diff
+27 -7
View File
@@ -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
};
+215 -48
View File
@@ -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;
+17
View File
@@ -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);
+14 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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;
+12
View File
@@ -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;
+4
View File
@@ -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;
}
/**