16 Commits
Author SHA1 Message Date
Ernesto A. Fernández d533b6be3a 0.3.9
This release fixes the build for the soon to be released 6.9 kernel.

Support is tentatively added for the new 0x80 incompatible volume
feature, which appears simple enough so far and isn't causing any
problems.

A console warning that showed up during compressed file reads on recent
kernels is now fixed.

Support is improved for tiny filesystems of under 100 MiB, though they
remain awkward to test due to fragmentation issues.

The behaviour of apfs_modified_by is tweaked to better match the
official driver.

Free queues should now be much harder to fill up. The most important
change here is that files are now deleted across multiple transactions.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-05-07 22:13:48 -03:00
Ernesto A. Fernández 02cb8bb8d9 Suuport volumes with the 0x80 incompatible feature
The official newfs_apfs now sets a 0x80 incompatible feature in all
volumes by default. The feature is undocumented as far as I know, but
after some testing it would seem that its effect is to set a flag in
preallocated extents. I can't imagine why such a feature would be
incompatible instead of read-only compatible, so there is a chance that
I'm missing something important, but for now enable support and let's
see if anything breaks.

By the way, no real changes are needed here because we don't preallocate
extents ourselves, and we put existing preallocated extents through the
same CoW as all the others. The only problem remaining is that writes
after the end of a file will not flip the "preallocation" flags that may
be set in previous blocks, but neither the official fsck nor the driver
seem to care about this. I just don't get what this flag is for, which
is a little scary of course.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-05-03 21:25:50 -03:00
Ernesto A. Fernández 6e7371ae02 Sync apfs_raw.h with the fsck again
I've recently noticed a number of new flags and added them to the fsck.
I heven't quite figured out most of them yet, but copy the information
here anyway. Also add checks for the new incompatible features, so that
they don't get accepted by default.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-05-03 20:54:26 -03:00
Ernesto A. Fernández 2a10ca61e6 Update the volume superblock with latest fields
If the latest newfs_apfs creates an image with the default options,
older versions of fsck_apfs complain about an unknown volume superblock
field called "apfs_doc_id_index_flags". Several such new fields appear
to have been added, and after some bit flipping I managed to retrieve
the names for all of them, except for "apfs_doc_id_tree_oid" which is
only a (probably correct) guess.

I have no plans for these fields right now, but document them all in
apfs_raw.h anyway, so that I don't forget.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-04-23 19:51:27 -03:00
Ernesto A. Fernández 48f6df6262 Improve error reporting for orphan cleanups
Orphan cleanup explicitly reports the error code from apfs_iget() when
it gets swallowed, but it's always ENODATA so it looks silly. Leave the
message in place but ignore the error code.

Instead, report all error codes returned by apfs_clean_orphans() before
they get discarded by apfs_orphan_cleanup_work(). That information might
actually be useful at some point.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-04-17 20:40:12 -03:00
Ernesto A. Fernández 33c9a99e96 Always update apfs_modified_by on unmount
The official reference is a little ambiguous about apfs_modified_by. On
its first introduction it says the following:

  If the implementation's information is already the last entry in this
  field, you can update the field as usual (creating a duplicate), or
  leave the field's value unmodified. Both behaviors are permitted.

So far the driver has followed these instructions and left the field
unmodified, but later on the reference describes the apfs_modified_by
timestamp as:

  The time that the program last modified this volume.

This seems to mean that the timestamp must always change, even if the
implementation in use has not. More importantly, on the matter of the
cloneinfo xid, the reference says the following:

  When unmounting a volume, the value of this field is set to the latest
  transaction identifier, the same as the apfs_modified_by field.

So apfs_modified_by must always get updated on unmount, or so it seems.
I looked at older checkpoints for a few official images, and indeed they
always seem to do this on unmount, and never before. Always by shifting.

Stop lawyering about the specification and just copy what the official
driver does.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-04-17 19:09:38 -03:00
Ernesto A. Fernández ca493f9586 Don't give up when a free queue gets full
I keep running into situations where too much fragmentation gets a free
queue full, and the filesystem goes read-only with the "free queue has
too many nodes" message. I've been patching this up by trying to flush
more often, but in the end it's best to force an extra flush whenever we
run out of space.

Rework apfs_free_queue_insert_nocache() so that it checks for space
before actually inserting anything. If we are out of room, flush and
start again. Make this last-resort flush stronger with a new "force"
argument for apfs_flush_free_queue(), otherwise it might still be
insufficient. Also, stop preserving multiple transactions in the queues,
I don't really see the point and it makes all this stuff much worse.

With these changes I'm hoping that free queues will no longer fill up
in practice, but it's still possible in theory, so leave an error
message in place to identify this issue quickly.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-04-12 17:24:04 -03:00
Ernesto A. Fernández 212cc6ef63 Make apfs_query_found_extent() check ids as well
I've been trying to run tests with a small image of around 100 MiB. I
wasn't expecting any problems, but surprisingly a lot of the tests fail.
The difference with larger containers appears to be that my hack to
avoid fragmentation (allocating data and metada in different chunks)
does not work when you only have one chunk. So this bug actually affects
any image, it's just easier to trigger here.

So, as for the bug itself: when working with large dstream xattrs (like
my apfs/002 test does), I regularly hit the "extent is not tail - bug!"
error. The problem is one that I've faced many times before with my
messy query code: queries looking for an extent without knowing it's
exact logical address can't be "exact" so they don't reliably return
ENODATA when they find nothing of value.

To deal with this, the extent code always checks the retrieved record by
calling apfs_query_found_extent(), but that only checks the record type.
In this particular case, the query is finding a proper file extent
record, but it belongs to a different dstream. I originally didn't think
such things could happen, but that has proved wrong more than once. From
now on, make apfs_query_found_extent() check the extent id as well.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-04-11 16:36:13 -03:00
Ernesto A. Fernández fa6d1d3986 Check if fq root is empty before reading first key
After the last patch I'm regularly getting the following error message
in the console:

  APFS (800010g): index out of bounds (0 of 0) (apfs_node_locate_key:598)

What happens here is that the container xid is set to 1 by the mkfs, but
the ip fq is empty so the oldest xid is set to 0. So when the driver
first starts a transaction, apfs_flush_free_queue() will believe that
there is an actual "0" transaction to flush, and end up trying to update
the oldest xid field for the empty queue.

This is all a bit odd but not a real problem, as soon as the fq starts
getting used all the flush checks will work as expected. The annoying
message comes from apfs_free_queue_oldest_xid(), which directly calls
apfs_node_locate_key() for the first key in the root node, without
verifying if such a key even exists. Avoid that.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-04-05 19:09:24 -03:00
Ernesto A. Fernández 076e715042 Preserve only one transaction in the ip free queue
Currently apfs_flush_free_queue() preserves several transactions in both
the ip and main free queue. This is a problem for tiny filesystems (5
MiB, for example) because the ip only has 6 blocks and inevitably uses 2
per transaction, getting full almost immediately. Allow more recent
transactions to get flushed for the ip.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-04-05 16:10:31 -03:00
Ernesto A. Fernández 38f9100760 Get rid of kmap_atomic()
As mentioned in the code, my implementation of apfs_compress_readpage()
was mostly copied from ext4_read_inline_page() because it was simple
enough and I didn't know what I was doing. As it turns out, I picked the
wrong function to copy: ext4_read_inline_page() only reads in-memory
data and doesn't go to sleep. We do read from disk, and we do go to
sleep, so newer kernels flood the console with warnings.

Replace kmap_atomic() with kmap(). The only expected negative effect of
this change would be worse performance on 32-bit archs, but I haven't
even tested those in years. The alternative kmap_local_page() is not
available in old kernels.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-03-27 21:09:50 -03:00
Ernesto A. Fernández cc320ed9ea Provide an explanation for forced read-only mounts
The driver requires setting an explicit "readwrite" mount option for an
actual writable mount to take place, otherwise the mount is silently
switched to read-only. The point is to prevent users that don't
understand the risks from potentially corrupting their data. Hopefully
this would push them to check the readme, but some might not even notice
that we have one (for example, if they got the driver as a distro
package). Try to explain the situation in the console.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-03-26 20:47:48 -03:00
Ernesto A. Fernández f50c8f2084 Open block devices as files with Linux 6.9
The first 6.9 release candidate is out, and bdev_open_by_path() is gone.
We are no longer supposed to work with block device handles, we must use
a normal open file struct instead. Update the code following what was
done to btrfs in upstream commit 9ae061cf2a46 ("btrfs: port device
access to file").

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-03-26 13:49:24 -03:00
Ernesto A. Fernández 21983fb418 Define SLAB_MEM_SPREAD flag for Linux 6.9
The first 6.9 release candidate is out, and the SLAB_MEM_SPREAD flag is
no longer available for kmem_cache_create(). Apparently it has been a
no-op for a while, so the least invasive way to fix the build is to just
define it as zero.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-03-26 13:48:32 -03:00
Ernesto A. Fernández 514a2164d8 Split orphan cleanup among multiple transactions
Gilgamesh reports via email that the following iozone benchmark is
failing:

  ./iozone -a -i 0 -r 1M -n 1g -g 1g -f /mnt/apfs/test

I've tried this myself on a small (3 GiB) filesystem, and it is the
commit after the unlink that fails, complaining about the main free
queue being full:

  APFS (800010g): free queue has too many nodes (348 > 347) (apfs_free_queue_insert_nocache:849)

The problem is that random writes to a big file create a lot of extents,
and when the file is deleted they all get dumped to the main free queue
as separate records. The queue gets full and the commit fails.

Stop forcing file deletion to happen in a single transaction. Instead,
set up a workqueue whenever an orphan inode gets evicted, and leave the
cleanup for another time. Also allow the cleanup to execute only
partially (deleting the first extents like the official driver) and
reschedule itself (again via inode eviction).

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-03-22 21:20:27 -03:00
Ernesto A. Fernández b8fe780c71 Don't leave stale record data on failed queries
When a query fails with ENODATA, the caller may still need to work with
the result (for instance to insert something in that location), but it
should never be confused for an actual valid record. Some changes I'm
working on trigger this problem regularly for apfs_query_found_extent(),
making it check stale records and report success. I need that function
to be reliable, so always clear the key and value lengths from queries
on failure.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-03-18 18:45:37 -03:00
12 changed files with 715 additions and 138 deletions

No files matched your search

+24 -14
View File
@@ -230,7 +230,9 @@ struct apfs_ephemeral_object_info {
*/
struct apfs_nxsb_info {
struct block_device *nx_bdev; /* Device for the container */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0)
struct file *nx_bdev_file;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
struct bdev_handle *nx_bdev_handle;
#endif
struct apfs_nx_superblock *nx_raw; /* On-disk main sb */
@@ -335,6 +337,7 @@ struct apfs_sb_info {
int s_trans_buffers_max;
struct inode *s_private_dir; /* Inode for the private directory */
struct work_struct s_orphan_cleanup_work;
};
static inline struct apfs_sb_info *APFS_SB(struct super_block *sb)
@@ -626,20 +629,21 @@ static inline u64 apfs_cat_cnid(struct apfs_key_header *key)
}
/* Flags for the query structure */
#define APFS_QUERY_TREE_MASK 00177 /* Which b-tree we query */
#define APFS_QUERY_OMAP 00001 /* This is a b-tree object map query */
#define APFS_QUERY_CAT 00002 /* This is a catalog tree query */
#define APFS_QUERY_FREE_QUEUE 00004 /* This is a free queue query */
#define APFS_QUERY_EXTENTREF 00010 /* This is an extent reference query */
#define APFS_QUERY_FEXT 00020 /* This is a fext tree query */
#define APFS_QUERY_SNAP_META 00040 /* This is a snapshot meta query */
#define APFS_QUERY_OMAP_SNAP 00100 /* This is an omap snapshots query */
#define APFS_QUERY_NEXT 00200 /* Find next of multiple matches */
#define APFS_QUERY_EXACT 00400 /* Search for an exact match */
#define APFS_QUERY_DONE 01000 /* The search at this level is over */
#define APFS_QUERY_ANY_NAME 02000 /* Multiple search for any name */
#define APFS_QUERY_ANY_NUMBER 04000 /* Multiple search for any number */
#define APFS_QUERY_TREE_MASK 000177 /* Which b-tree we query */
#define APFS_QUERY_OMAP 000001 /* This is a b-tree object map query */
#define APFS_QUERY_CAT 000002 /* This is a catalog tree query */
#define APFS_QUERY_FREE_QUEUE 000004 /* This is a free queue query */
#define APFS_QUERY_EXTENTREF 000010 /* This is an extent reference query */
#define APFS_QUERY_FEXT 000020 /* This is a fext tree query */
#define APFS_QUERY_SNAP_META 000040 /* This is a snapshot meta query */
#define APFS_QUERY_OMAP_SNAP 000100 /* This is an omap snapshots query */
#define APFS_QUERY_NEXT 000200 /* Find next of multiple matches */
#define APFS_QUERY_EXACT 000400 /* Search for an exact match */
#define APFS_QUERY_DONE 001000 /* The search at this level is over */
#define APFS_QUERY_ANY_NAME 002000 /* Multiple search for any name */
#define APFS_QUERY_ANY_NUMBER 004000 /* Multiple search for any number */
#define APFS_QUERY_MULTIPLE (APFS_QUERY_ANY_NAME | APFS_QUERY_ANY_NUMBER)
#define APFS_QUERY_PREV 010000 /* Find previous record */
/*
* Structure used to retrieve data from an APFS B-Tree. For now only used
@@ -755,6 +759,8 @@ struct apfs_inode_info {
bool i_has_dstream; /* Is there a dstream record? */
struct apfs_dstream_info i_dstream; /* Dstream data, if any */
bool i_cleaned; /* Orphan data already deleted */
struct inode vfs_inode;
};
@@ -872,6 +878,7 @@ extern int apfs_btree_remove(struct apfs_query *query);
extern void apfs_btree_change_node_count(struct apfs_query *query, int change);
extern int apfs_btree_replace(struct apfs_query *query, void *key, int key_len,
void *val, int val_len);
extern void apfs_query_direct_forward(struct apfs_query *query);
/* compress.c */
extern int apfs_compress_get_size(struct inode *inode, loff_t *size);
@@ -919,6 +926,7 @@ extern int apfs_unlink(struct inode *dir, struct dentry *dentry);
extern int apfs_rmdir(struct inode *dir, struct dentry *dentry);
extern int apfs_delete_orphan_link(struct inode *inode);
extern int APFS_DELETE_ORPHAN_LINK_MAXOPS(void);
extern u64 apfs_any_orphan_ino(struct super_block *sb, u64 *ino_p);
/* extents.c */
extern int apfs_extent_from_query(struct apfs_query *query,
@@ -934,6 +942,7 @@ extern int apfs_get_new_block(struct inode *inode, sector_t iblock,
struct buffer_head *bh_result, int create);
extern int APFS_GET_NEW_BLOCK_MAXOPS(void);
extern int apfs_truncate(struct apfs_dstream_info *dstream, loff_t new_size);
extern int apfs_inode_delete_front(struct inode *inode);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
extern loff_t apfs_remap_file_range(struct file *src_file, loff_t off, struct file *dst_file, loff_t destoff, loff_t len, unsigned int remap_flags);
#else
@@ -951,6 +960,7 @@ extern int apfs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
extern struct inode *apfs_iget(struct super_block *sb, u64 cnid);
extern int apfs_update_inode(struct inode *inode, char *new_name);
extern int APFS_UPDATE_INODE_MAXOPS(void);
extern void apfs_orphan_cleanup_work(struct work_struct *work);
extern void apfs_evict_inode(struct inode *inode);
extern struct inode *apfs_new_inode(struct inode *dir, umode_t mode,
dev_t rdev);
+48 -12
View File
@@ -268,6 +268,10 @@ struct apfs_phys_ext_val {
#define APFS_FILE_EXTENT_LEN_MASK 0x00ffffffffffffffULL
#define APFS_FILE_EXTENT_FLAG_MASK 0xff00000000000000ULL
#define APFS_FILE_EXTENT_FLAG_SHIFT 56
#define APFS_FILE_EXTENT_CRYPTO_FLAG 0x01 /* Made-up name */
#define APFS_FILE_EXTENT_PREALLOCATED 0x02 /* Made-up name */
#define APFS_VALID_FILE_EXTENT_FLAGS (APFS_FILE_EXTENT_CRYPTO_FLAG \
| APFS_FILE_EXTENT_PREALLOCATED)
/*
* Put a bound on maximum file size so that a growing truncation will always
@@ -1051,13 +1055,19 @@ struct apfs_checkpoint_map_phys {
#define APFS_FS_SPILLEDOVER 0x00000010LL
#define APFS_FS_RUN_SPILLOVER_CLEANER 0x00000020LL
#define APFS_FS_ALWAYS_CHECK_EXTENTREF 0x00000040LL
#define APFS_FS_PREVIOUSLY_SEALED 0x00000080LL /* Made-up name */
#define APFS_FS_PFK 0x00000100LL /* Made-up name */
#define APFS_FS_UNKNOWN_200 0x00000200LL
#define APFS_FS_FLAGS_VALID_MASK (APFS_FS_UNENCRYPTED \
| APFS_FS_EFFACEABLE \
| APFS_FS_RESERVED_4 \
| APFS_FS_ONEKEY \
| APFS_FS_SPILLEDOVER \
| APFS_FS_RUN_SPILLOVER_CLEANER \
| APFS_FS_ALWAYS_CHECK_EXTENTREF)
| APFS_FS_EFFACEABLE \
| APFS_FS_RESERVED_4 \
| APFS_FS_ONEKEY \
| APFS_FS_SPILLEDOVER \
| APFS_FS_RUN_SPILLOVER_CLEANER \
| APFS_FS_ALWAYS_CHECK_EXTENTREF \
| APFS_FS_PREVIOUSLY_SEALED \
| APFS_FS_PFK \
| APFS_FS_UNKNOWN_200)
#define APFS_FS_CRYPTOFLAGS (APFS_FS_UNENCRYPTED \
| APFS_FS_EFFACEABLE \
@@ -1124,7 +1134,9 @@ struct apfs_checkpoint_map_phys {
#define APFS_INCOMPAT_NORMALIZATION_INSENSITIVE 0x00000008LL
#define APFS_INCOMPAT_INCOMPLETE_RESTORE 0x00000010LL
#define APFS_INCOMPAT_SEALED_VOLUME 0x00000020LL
#define APFS_INCOMPAT_RESERVED_40 0x00000040LL
#define APFS_INCOMPAT_PFK 0x00000040LL /* Made-up name */
#define APFS_INCOMPAT_EXTENT_PREALLOC_FLAG 0x00000080LL /* Made-up name */
#define APFS_INCOMPAT_SECONDARY_FSROOT 0x00000100LL /* Made-up name */
#define APFS_SUPPORTED_INCOMPAT_MASK (APFS_INCOMPAT_CASE_INSENSITIVE \
| APFS_INCOMPAT_DATALESS_SNAPS \
@@ -1132,7 +1144,9 @@ struct apfs_checkpoint_map_phys {
| APFS_INCOMPAT_NORMALIZATION_INSENSITIVE \
| APFS_INCOMPAT_INCOMPLETE_RESTORE \
| APFS_INCOMPAT_SEALED_VOLUME \
| APFS_INCOMPAT_RESERVED_40)
| APFS_INCOMPAT_PFK \
| APFS_INCOMPAT_EXTENT_PREALLOC_FLAG \
| APFS_INCOMPAT_SECONDARY_FSROOT)
#define APFS_MODIFIED_NAMELEN 32
@@ -1162,6 +1176,18 @@ struct apfs_modified_by {
#define APFS_CRYPTO_RESERVED_5 5
#define APFS_UNASSIGNED_CRYPTO_ID (~0ULL)
/* Doc id index flags. I'm making up the names for now. */
#define APFS_DOC_ID_HAS_PREV_TREE 0x00000001
#define APFS_DOC_ID_UNKNOWN_02 0x00000002
#define APFS_DOC_ID_UNKNOWN_04 0x00000004
#define APFS_DOC_ID_UNKNOWN_08 0x00000008
#define APFS_DOC_ID_UNKNOWN_10 0x00000010
#define APFS_DOC_ID_VALID_FLAGS (APFS_DOC_ID_HAS_PREV_TREE \
| APFS_DOC_ID_UNKNOWN_02 \
| APFS_DOC_ID_UNKNOWN_04 \
| APFS_DOC_ID_UNKNOWN_08 \
| APFS_DOC_ID_UNKNOWN_10)
/*
* Structure used to store the encryption state
*/
@@ -1241,15 +1267,24 @@ struct apfs_superblock {
__le64 apfs_snap_meta_ext_oid;
char apfs_volume_group_id[UUID_SIZE];
/*3F0*/ char apfs_volume_group_id[UUID_SIZE];
__le64 apfs_integrity_meta_oid;
/*400*/ __le64 apfs_integrity_meta_oid;
__le64 apfs_fext_tree_oid;
__le32 apfs_fext_tree_type;
/*410*/ __le32 apfs_fext_tree_type;
__le32 reserved_type;
__le64 reserved_oid;
/*420*/ __le64 apfs_doc_id_index_xid;
__le32 apfs_doc_id_index_flags;
__le32 apfs_doc_id_tree_type;
/*430*/ __le64 apfs_doc_id_tree_oid; /* Made-up name */
__le64 apfs_prev_doc_id_tree_oid;
__le64 apfs_doc_id_fixup_cursor;
__le64 apfs_sec_root_tree_oid;
/*450*/ __le32 apfs_sec_root_tree_type;
} __packed;
/* Extended attributes constants */
@@ -1268,9 +1303,10 @@ enum {
APFS_XATTR_DATA_EMBEDDED = 0x00000002,
APFS_XATTR_FILE_SYSTEM_OWNED = 0x00000004,
APFS_XATTR_RESERVED_8 = 0x00000008,
APFS_XATTR_UNKNOWN_10 = 0x00000010,
};
#define APFS_XATTR_VALID_FLAGS 0x0000000f
#define APFS_XATTR_VALID_FLAGS 0x0000001f
/*
* Structure of the value of an extended attributes record
+51 -11
View File
@@ -398,8 +398,6 @@ struct apfs_query *apfs_alloc_query(struct apfs_node *node,
/* To be released by free_query. */
query->node = node;
/* Start the search with the last record and go backwards */
query->index = node->records;
if (parent) {
query->key = parent->key;
@@ -408,6 +406,15 @@ struct apfs_query *apfs_alloc_query(struct apfs_node *node,
query->depth = parent->depth + 1;
}
/*
* We start the search with the last record and go backwards, but
* some queries later use the PREV flag later to list them in order.
*/
if (query->flags & APFS_QUERY_PREV)
query->index = -1;
else
query->index = node->records;
return query;
}
@@ -514,7 +521,8 @@ next_node:
* than enough to map every block.
*/
apfs_err(sb, "btree is too high");
return -EFSCORRUPTED;
err = -EFSCORRUPTED;
goto fail;
}
err = apfs_node_query(sb, *query);
@@ -526,12 +534,16 @@ next_node:
err = apfs_query_set_before_first(sb, query);
if (err) {
apfs_err(sb, "failed to set before the first record");
return err;
goto fail;
}
return -ENODATA;
err = -ENODATA;
goto fail;
} else if (err == -EAGAIN) {
if (!(*query)->parent) /* We are at the root of the tree */
return -ENODATA;
if (!(*query)->parent) {
/* We are at the root of the tree */
err = -ENODATA;
goto fail;
}
/* Move back up one level and continue the query */
parent = (*query)->parent;
@@ -540,7 +552,7 @@ next_node:
*query = parent;
goto next_node;
} else if (err) {
return err;
goto fail;
}
if (apfs_node_is_leaf((*query)->node)) /* All done */
return 0;
@@ -549,14 +561,15 @@ next_node:
if (err) {
apfs_alert(sb, "bad index block: 0x%llx",
(*query)->node->object.block_nr);
return err;
goto fail;
}
/* Now go a level deeper and search the child */
node = apfs_read_node(sb, child_id, storage, false /* write */);
if (IS_ERR(node)) {
apfs_err(sb, "failed to read node 0x%llx", child_id);
return PTR_ERR(node);
err = PTR_ERR(node);
goto fail;
}
if (node->object.oid != child_id)
@@ -571,10 +584,16 @@ next_node:
if (!*query) {
apfs_node_free(node);
*query = parent;
return -ENOMEM;
err = -ENOMEM;
goto fail;
}
node = NULL;
goto next_node;
fail:
/* Don't leave stale record info here or some callers will use it */
(*query)->key_len = (*query)->len = 0;
return err;
}
static int __apfs_btree_replace(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
@@ -1132,3 +1151,24 @@ int apfs_btree_replace(struct apfs_query *query, void *key, int key_len, void *v
apfs_btree_change_rec_count(query, 0 /* change */, key_len, val_len);
return 0;
}
/**
* apfs_query_direct_forward - Set a query to start listing records forwards
* @query: a successfully executed query
*
* Multiple queries list records backwards, but queries marked with this
* function after execution will go in the opposite direction.
*/
void apfs_query_direct_forward(struct apfs_query *query)
{
if (query->flags & APFS_QUERY_PREV)
return;
apfs_assert_query_is_valid(query);
ASSERT(apfs_node_is_leaf(query->node));
while (query) {
query->flags |= APFS_QUERY_PREV;
query = query->parent;
}
}
+3 -3
View File
@@ -417,16 +417,16 @@ static int apfs_compress_read_folio(struct file *filp, struct folio *folio)
static int apfs_compress_readpage(struct file *filp, struct page *page)
{
#endif
void *addr = NULL;
char *addr = NULL;
ssize_t ret;
loff_t off;
/* Mostly copied from ext4_read_inline_page() */
off = page->index << PAGE_SHIFT;
addr = kmap_atomic(page);
addr = kmap(page);
ret = apfs_compress_file_read_page(filp, addr, off);
flush_dcache_page(page);
kunmap_atomic(addr);
kunmap(page);
if (ret >= 0) {
zero_user_segment(page, ret, PAGE_SIZE);
SetPageUptodate(page);
+67 -6
View File
@@ -1158,13 +1158,13 @@ fail:
/**
* apfs_orphan_name - Get the name for an orphan inode's invisible link
* @inode: the vfs inode
* @qname: on return, the name assigned to the link
* @ino: the inode number
* @qname: on return, the name assigned to the link
*
* Returns 0 on success; the caller must remember to free @qname->name after
* use. Returns a negative error code in case of failure.
*/
static int apfs_orphan_name(struct inode *inode, struct qstr *qname)
static int apfs_orphan_name(u64 ino, struct qstr *qname)
{
int max_len;
char *name;
@@ -1174,7 +1174,7 @@ static int apfs_orphan_name(struct inode *inode, struct qstr *qname)
name = kmalloc(max_len, GFP_KERNEL);
if (!name)
return -ENOMEM;
qname->len = snprintf(name, max_len, "0x%llx-dead", apfs_ino(inode));
qname->len = snprintf(name, max_len, "0x%llx-dead", ino);
qname->name = name;
return 0;
}
@@ -1193,7 +1193,7 @@ static int apfs_create_orphan_link(struct inode *inode)
struct qstr qname;
int err = 0;
err = apfs_orphan_name(inode, &qname);
err = apfs_orphan_name(apfs_ino(inode), &qname);
if (err)
return err;
err = apfs_create_dentry_rec(inode, &qname, apfs_ino(priv_dir), 0 /* sibling_id */);
@@ -1236,7 +1236,7 @@ int apfs_delete_orphan_link(struct inode *inode)
struct apfs_drec drec;
int err;
err = apfs_orphan_name(inode, &qname);
err = apfs_orphan_name(apfs_ino(inode), &qname);
if (err)
return err;
@@ -1481,3 +1481,64 @@ out_abort:
apfs_transaction_abort(sb);
return err;
}
/**
* apfs_any_orphan_ino - Find the inode number for any orphaned regular file
* @sb: filesytem superblock
* @ino_p: on return, the inode number found
*
* Returns 0 on success, or a negative error code in case of failure, which may
* be -ENODATA if there are no orphan files.
*/
u64 apfs_any_orphan_ino(struct super_block *sb, u64 *ino_p)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_query *query = NULL;
struct apfs_drec drec = {0};
struct qstr qname = {0};
bool hashed = apfs_is_normalization_insensitive(sb);
bool found = false;
int err;
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
apfs_init_drec_key(sb, APFS_PRIV_DIR_INO_NUM, NULL /* name */, 0 /* name_len */, &query->key);
query->flags = APFS_QUERY_CAT | APFS_QUERY_MULTIPLE | APFS_QUERY_EXACT;
while (!found) {
err = apfs_btree_query(sb, &query);
if (err) {
if (err == -ENODATA)
goto out;
apfs_err(sb, "drec query failed for private dir");
goto out;
}
err = apfs_drec_from_query(query, &drec, hashed);
if (err) {
apfs_alert(sb, "bad dentry record in private dir");
goto out;
}
/* These files are deleted immediately by ->evict_inode() */
if (drec.type != DT_REG)
continue;
/*
* Confirm that this is an orphan file, because the official
* reference allows other uses for the private directory.
*/
err = apfs_orphan_name(drec.ino, &qname);
if (err)
goto out;
found = strcmp(drec.name, qname.name) == 0;
kfree(qname.name);
qname.name = NULL;
}
*ino_p = drec.ino;
out:
apfs_free_query(query);
query = NULL;
return err;
}
+1 -1
View File
@@ -1,5 +1,5 @@
PACKAGE_NAME="linux-apfs-rw"
PACKAGE_VERSION="0.3.8"
PACKAGE_VERSION="0.3.9"
BUILT_MODULE_NAME[0]="apfs"
DEST_MODULE_LOCATION[0]="/extra"
+126 -2
View File
@@ -387,7 +387,7 @@ static int apfs_shrink_extent_tail(struct apfs_query *query, struct apfs_dstream
}
/**
* apfs_query_found_extent - Is this query pointing to an extent record?
* apfs_query_found_extent - Did this query find an extent with the right id?
* @query: the (successful) query that found the record
*/
static inline bool apfs_query_found_extent(struct apfs_query *query)
@@ -398,7 +398,12 @@ static inline bool apfs_query_found_extent(struct apfs_query *query)
if (query->key_len < sizeof(*hdr))
return false;
hdr = raw + query->key_off;
return apfs_cat_type(hdr) == APFS_TYPE_FILE_EXTENT;
if (apfs_cat_type(hdr) != APFS_TYPE_FILE_EXTENT)
return false;
if (apfs_cat_cnid(hdr) != query->key.id)
return false;
return true;
}
/**
@@ -1625,6 +1630,125 @@ int apfs_truncate(struct apfs_dstream_info *dstream, loff_t new_size)
return apfs_create_hole(dstream, old_blks, new_blks);
}
/**
* apfs_dstream_delete_front - Deletes as many leading extents as possible
* @sb: filesystem superblock
* @ds_id: id for the dstream to delete
*
* Returns 0 on success, or a negative error code in case of failure, which may
* be -ENODATA if there are no more extents, or -EAGAIN if the free queue is
* getting too full.
*/
static int apfs_dstream_delete_front(struct super_block *sb, u64 ds_id)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_spaceman *sm = APFS_SM(sb);
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
struct apfs_spaceman_free_queue *fq = NULL;
struct apfs_query *query = NULL;
struct apfs_file_extent head;
bool first_match = true;
int ret;
fq = &sm_raw->sm_fq[APFS_SFQ_MAIN];
if (le64_to_cpu(fq->sfq_count) > TRANSACTION_MAIN_QUEUE_MAX)
return -EAGAIN;
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
apfs_init_file_extent_key(ds_id, 0, &query->key);
query->flags = APFS_QUERY_CAT;
next_extent:
ret = apfs_btree_query(sb, &query);
if (ret && ret != -ENODATA) {
apfs_err(sb, "query failed for first extent of id 0x%llx", ds_id);
goto out;
}
apfs_query_direct_forward(query);
if (!apfs_query_found_extent(query)) {
/*
* After the original lookup, the query may not be set to the
* first extent, but instead to the record that comes right
* before.
*/
if (first_match) {
first_match = false;
goto next_extent;
}
ret = -ENODATA;
goto out;
}
first_match = false;
ret = apfs_extent_from_query(query, &head);
if (ret) {
apfs_err(sb, "bad head extent record on dstream 0x%llx", ds_id);
goto out;
}
ret = apfs_btree_remove(query);
if (ret) {
apfs_err(sb, "removal failed for id 0x%llx, addr 0x%llx", ds_id, head.logical_addr);
goto out;
}
/*
* The official fsck doesn't complain about wrong sparse byte counts
* for orphans, so I guess we don't need to update them here
*/
if (!apfs_ext_is_hole(&head)) {
ret = apfs_range_put_reference(sb, head.phys_block_num, head.len);
if (ret) {
apfs_err(sb, "failed to put range 0x%llx-0x%llx", head.phys_block_num, head.len);
goto out;
}
ret = apfs_crypto_adj_refcnt(sb, head.crypto_id, -1);
if (ret) {
apfs_err(sb, "failed to take crypto id 0x%llx", head.crypto_id);
goto out;
}
}
if (le64_to_cpu(fq->sfq_count) <= TRANSACTION_MAIN_QUEUE_MAX)
goto next_extent;
ret = -EAGAIN;
out:
apfs_free_query(query);
return ret;
}
/**
* apfs_inode_delete_front - Deletes as many leading extents as possible
* @inode: inode to delete
*
* Tries to delete all extents for @inode, in which case it returns 0. If the
* free queue is getting too full, deletes as much as is reasonable and returns
* -EAGAIN. May return other negative error codes as well.
*/
int apfs_inode_delete_front(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct apfs_dstream_info *dstream = NULL;
struct apfs_inode_info *ai = APFS_I(inode);
int ret;
if (!ai->i_has_dstream)
return 0;
dstream = &ai->i_dstream;
ret = apfs_flush_extent_cache(dstream);
if (ret) {
apfs_err(sb, "extent cache flush failed for dstream 0x%llx", dstream->ds_id);
return ret;
}
ret = apfs_dstream_delete_front(sb, dstream->ds_id);
if (ret == -ENODATA)
return 0;
return ret;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
loff_t apfs_remap_file_range(struct file *src_file, loff_t off, struct file *dst_file, loff_t destoff, loff_t len, unsigned int remap_flags)
#else
+205 -29
View File
@@ -886,7 +886,9 @@ static struct apfs_query *apfs_inode_lookup(const struct inode *inode)
if (!ret)
return query;
apfs_err(sb, "query failed for id 0x%llx", apfs_ino(inode));
/* Don't complain if an orphan is already gone */
if (!current_work() || ret != -ENODATA)
apfs_err(sb, "query failed for id 0x%llx", apfs_ino(inode));
apfs_free_query(query);
return ERR_PTR(ret);
}
@@ -1005,8 +1007,10 @@ struct inode *apfs_iget(struct super_block *sb, u64 cnid)
down_read(&nxi->nx_big_sem);
query = apfs_inode_lookup(inode);
if (IS_ERR(query)) {
apfs_err(sb, "lookup failed for ino 0x%llx", cnid);
err = PTR_ERR(query);
/* Don't complain if an orphan is already gone */
if (!current_work() || err != -ENODATA)
apfs_err(sb, "lookup failed for ino 0x%llx", cnid);
goto fail;
}
err = apfs_inode_from_query(query, inode);
@@ -1540,13 +1544,16 @@ int APFS_UPDATE_INODE_MAXOPS(void)
* apfs_delete_inode - Delete an inode record
* @inode: the vfs inode to delete
*
* Returns 0 on success or a negative error code in case of failure.
* Returns 0 on success or a negative error code in case of failure, which may
* be -EAGAIN if the inode was not deleted in full.
*/
static int apfs_delete_inode(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct apfs_inode_info *ai = APFS_I(inode);
struct apfs_dstream_info *dstream = NULL;
struct apfs_query *query;
u64 old_dstream_id;
int ret;
ret = apfs_delete_all_xattrs(inode);
@@ -1555,6 +1562,9 @@ static int apfs_delete_inode(struct inode *inode)
return ret;
}
dstream = &ai->i_dstream;
old_dstream_id = dstream->ds_id;
/*
* This is very wasteful since all the new extents and references will
* get deleted right away, but it only affects clones, so I don't see a
@@ -1566,12 +1576,27 @@ static int apfs_delete_inode(struct inode *inode)
return ret;
}
/* TODO: truncate an orphan inode in multiple transactions */
dstream = &APFS_I(inode)->i_dstream;
ret = apfs_truncate(dstream, 0 /* new_size */);
/* TODO: what about partial deletion of xattrs? Is that allowed? */
ret = apfs_inode_delete_front(inode);
if (ret) {
apfs_err(sb, "truncation failed for ino 0x%llx", apfs_ino(inode));
return ret;
/*
* If the inode had too many extents, only the first few get
* deleted and the inode remains in the orphan list for now.
* I don't know why the deletion starts at the front, but it
* seems to be what the official driver does.
*/
if (ret != -EAGAIN) {
apfs_err(sb, "head deletion failed for ino 0x%llx", apfs_ino(inode));
return ret;
}
if (dstream->ds_id != old_dstream_id) {
ret = apfs_update_inode(inode, NULL /* new_name */);
if (ret) {
apfs_err(sb, "dstream id update failed for orphan 0x%llx", apfs_ino(inode));
return ret;
}
}
return -EAGAIN;
}
ret = apfs_put_dstream_rec(dstream);
@@ -1579,6 +1604,8 @@ static int apfs_delete_inode(struct inode *inode)
apfs_err(sb, "failed to put dstream for ino 0x%llx", apfs_ino(inode));
return ret;
}
dstream = NULL;
ai->i_has_dstream = false;
query = apfs_inode_lookup(inode);
if (IS_ERR(query)) {
@@ -1587,42 +1614,191 @@ static int apfs_delete_inode(struct inode *inode)
}
ret = apfs_btree_remove(query);
apfs_free_query(query);
if (ret)
if (ret) {
apfs_err(sb, "removal failed for ino 0x%llx", apfs_ino(inode));
return ret;
}
ai->i_cleaned = true;
return ret;
}
#define APFS_DELETE_INODE_MAXOPS 1
/**
* apfs_clean_single_orphan - Clean the given orphan file
* @inode: inode for the file to clean
*
* Returns 0 on success or a negative error code in case of failure, which may
* be -EAGAIN if the file could not be deleted in full.
*/
static int apfs_clean_single_orphan(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct apfs_max_ops maxops = {0}; /* TODO: rethink this stuff */
u64 ino = apfs_ino(inode);
bool eagain = false;
int err;
err = apfs_transaction_start(sb, maxops);
if (err)
return err;
err = apfs_delete_inode(inode);
if (err) {
if (err != -EAGAIN) {
apfs_err(sb, "failed to delete orphan 0x%llx", ino);
goto fail;
}
eagain = true;
} else {
err = apfs_delete_orphan_link(inode);
if (err) {
apfs_err(sb, "failed to unlink orphan 0x%llx", ino);
goto fail;
}
}
err = apfs_transaction_commit(sb);
if (err)
goto fail;
return eagain ? -EAGAIN : 0;
fail:
apfs_transaction_abort(sb);
return err;
}
/**
* apfs_clean_any_orphan - Pick an orphan and delete as much as reasonable
* @sb: filesystem superblock
*
* Returns 0 on success, or a negative error code in case of failure, which may
* be -ENODATA if there are no more orphan files or -EAGAIN if a file could not
* be deleted in full.
*/
static int apfs_clean_any_orphan(struct super_block *sb)
{
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct inode *inode = NULL;
int err;
u64 ino;
down_read(&nxi->nx_big_sem);
err = apfs_any_orphan_ino(sb, &ino);
up_read(&nxi->nx_big_sem);
if (err) {
if (err == -ENODATA)
return -ENODATA;
apfs_err(sb, "failed to find orphan inode numbers");
return err;
}
inode = apfs_iget(sb, ino);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
if (err != -ENODATA) {
apfs_err(sb, "iget failed for orphan 0x%llx", ino);
return err;
}
/*
* This happens rarely for files with no extents, if we hit a
* race with ->evict_inode(). Not a problem: the file is gone.
*/
apfs_notice(sb, "orphan 0x%llx not found", ino);
return 0;
}
if (atomic_read(&inode->i_count) > 1)
goto out;
err = apfs_clean_single_orphan(inode);
if (err && err != -EAGAIN) {
apfs_err(sb, "failed to clean orphan 0x%llx", ino);
goto out;
}
out:
iput(inode);
return err;
}
/**
* apfs_clean_orphans - Delete as many orphan files as is reasonable
* @sb: filesystem superblock
*
* Returns 0 on success or a negative error code in case of failure.
*/
static int apfs_clean_orphans(struct super_block *sb)
{
int ret, i;
for (i = 0; i < 100; ++i) {
ret = apfs_clean_any_orphan(sb);
if (ret) {
if (ret == -ENODATA)
return 0;
if (ret == -EAGAIN)
break;
apfs_err(sb, "failed to delete an orphan file");
return ret;
}
}
/*
* If a file is too big, or if there are too many files, take a break
* and continue later.
*/
if (atomic_read(&sb->s_active) != 0)
schedule_work(&APFS_SB(sb)->s_orphan_cleanup_work);
return 0;
}
void apfs_evict_inode(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct apfs_max_ops maxops;
struct apfs_inode_info *ai = APFS_I(inode);
int err;
if (is_bad_inode(inode) || inode->i_nlink)
goto out_clear;
if (is_bad_inode(inode) || inode->i_nlink || ai->i_cleaned)
goto out;
maxops.cat = APFS_DELETE_INODE_MAXOPS + APFS_DELETE_ORPHAN_LINK_MAXOPS();
maxops.blks = 0;
if (!ai->i_has_dstream || ai->i_dstream.ds_size == 0) {
/* For files with no extents, scheduled cleanup wastes time */
err = apfs_clean_single_orphan(inode);
if (err)
apfs_err(sb, "failed to clean orphan 0x%llx (err:%d)", apfs_ino(inode), err);
goto out;
}
if (apfs_transaction_start(sb, maxops))
goto out_report;
if (apfs_delete_inode(inode))
goto out_abort;
if (apfs_delete_orphan_link(inode))
goto out_abort;
if (apfs_transaction_commit(sb))
goto out_abort;
goto out_clear;
out_abort:
apfs_transaction_abort(sb);
out_report:
apfs_err(sb, "failed to delete orphan inode 0x%llx", apfs_ino(inode));
out_clear:
/*
* If the inode still has extents then schedule cleanup for the rest
* of it. Not during unmount though: completing all cleanup could take
* a while so just leave future mounts to handle the orphans.
*/
if (atomic_read(&sb->s_active))
schedule_work(&APFS_SB(sb)->s_orphan_cleanup_work);
out:
truncate_inode_pages_final(&inode->i_data);
clear_inode(inode);
}
void apfs_orphan_cleanup_work(struct work_struct *work)
{
struct super_block *sb = NULL;
struct apfs_sb_info *sbi = NULL;
struct inode *priv = NULL;
int err;
sbi = container_of(work, struct apfs_sb_info, s_orphan_cleanup_work);
priv = sbi->s_private_dir;
sb = priv->i_sb;
if (sb->s_flags & SB_RDONLY) {
apfs_alert(sb, "attempt to flush orphans in read-only mount");
return;
}
err = apfs_clean_orphans(sb);
if (err)
apfs_err(sb, "orphan cleanup failed (err:%d)", err);
}
/**
* apfs_insert_inode_locked - Wrapper for insert_inode_locked4()
* @inode: vfs inode to insert in cache
+38
View File
@@ -803,6 +803,42 @@ static int apfs_key_from_query(struct apfs_query *query, struct apfs_key *key)
return err;
}
/**
* apfs_node_prev - Find the previous record in the current node
* @sb: filesystem superblock
* @query: query in execution
*
* Returns 0 on success, -EAGAIN if the previous record is in another node,
* -ENODATA if no more records exist, or another negative error code in case
* of failure.
*
* The meaning of "next" and "previous" is reverted here, because regular
* multiple always start with the final record, and then they go backwards.
* TODO: consider renaming this for clarity.
*/
static int apfs_node_prev(struct super_block *sb, struct apfs_query *query)
{
struct apfs_node *node = query->node;
if (query->index + 1 == node->records) {
/* The next record may be in another node */
return -EAGAIN;
}
++query->index;
query->key_len = apfs_node_locate_key(node, query->index, &query->key_off);
if (query->key_len == 0) {
apfs_err(sb, "bad key for index %d", query->index);
return -EFSCORRUPTED;
}
query->len = apfs_node_locate_data(node, query->index, &query->off);
if (query->len == 0) {
apfs_err(sb, "bad value for index %d", query->index);
return -EFSCORRUPTED;
}
return 0;
}
/**
* apfs_node_next - Find the next matching record in the current node
* @sb: filesystem superblock
@@ -892,6 +928,8 @@ int apfs_node_query(struct super_block *sb, struct apfs_query *query)
int cmp;
int err;
if (query->flags & APFS_QUERY_PREV)
return apfs_node_prev(sb, query);
if (query->flags & APFS_QUERY_NEXT)
return apfs_node_next(sb, query);
+84 -28
View File
@@ -465,9 +465,14 @@ static u64 apfs_free_queue_oldest_xid(struct apfs_node *root)
char *raw = root->object.data;
int len, off;
len = apfs_node_locate_key(root, 0, &off);
if (len != sizeof(*key)) /* No records in queue (or corruption) */
if (root->records == 0)
return 0;
len = apfs_node_locate_key(root, 0, &off);
if (len != sizeof(*key)) {
/* TODO: abort transaction */
apfs_err(root->object.sb, "bad key length (%d)", len);
return 0;
}
key = (struct apfs_spaceman_free_queue_key *)(raw + off);
return le64_to_cpu(key->sfqk_xid);
}
@@ -476,10 +481,11 @@ static u64 apfs_free_queue_oldest_xid(struct apfs_node *root)
* apfs_flush_free_queue - Free ip blocks queued by old transactions
* @sb: superblock structure
* @qid: queue to be freed
* @force: flush as much as possible
*
* Returns 0 on success or a negative error code in case of failure.
*/
static int apfs_flush_free_queue(struct super_block *sb, unsigned int qid)
static int apfs_flush_free_queue(struct super_block *sb, unsigned int qid, bool force)
{
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_spaceman *sm = APFS_SM(sb);
@@ -496,10 +502,21 @@ static int apfs_flush_free_queue(struct super_block *sb, unsigned int qid)
return PTR_ERR(fq_root);
}
/* Preserve a few transactions */
while (oldest + 4 < nxi->nx_xid) {
while (oldest) {
u64 sfq_count;
/*
* Try to preserve one transaction here. I don't really know
* what free queues are for so this is probably silly.
*/
if (force) {
if (oldest == nxi->nx_xid)
break;
} else {
if (oldest + 1 >= nxi->nx_xid)
break;
}
while (true) {
u64 count = 0;
@@ -518,6 +535,9 @@ static int apfs_flush_free_queue(struct super_block *sb, unsigned int qid)
oldest = apfs_free_queue_oldest_xid(fq_root);
fq->sfq_oldest_xid = cpu_to_le64(oldest);
if (force)
continue;
/*
* Flushing a single transaction may not be enough to avoid
* running out of space in the ip, but it's probably best not
@@ -640,12 +660,12 @@ int apfs_read_spaceman(struct super_block *sb)
apfs_err(sb, "failed to rotate ip bitmaps");
goto fail;
}
err = apfs_flush_free_queue(sb, APFS_SFQ_IP);
err = apfs_flush_free_queue(sb, APFS_SFQ_IP, false /* force */);
if (err) {
apfs_err(sb, "failed to flush ip fq");
goto fail;
}
err = apfs_flush_free_queue(sb, APFS_SFQ_MAIN);
err = apfs_flush_free_queue(sb, APFS_SFQ_MAIN, false /* force */);
if (err) {
apfs_err(sb, "failed to flush main fq");
goto fail;
@@ -772,17 +792,16 @@ static inline int apfs_chunk_mark_free(struct super_block *sb, char *bitmap,
}
/**
* apfs_free_queue_insert_nocache - Add a block range to its free queue
* apfs_free_queue_try_insert - Try to add a block range to its free queue
* @sb: superblock structure
* @bno: first block number to free
* @count: number of consecutive blocks to free
*
* Same as apfs_free_queue_insert(), but writes to the free queue directly,
* bypassing the cache of the latest freed block range.
*
* Returns 0 on success or a negative error code in case of failure.
* Same as apfs_free_queue_insert_nocache(), except that this one can also fail
* with -EAGAIN if there is no room for the new record, so that the caller can
* flush the queue and retry.
*/
int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
static int apfs_free_queue_try_insert(struct super_block *sb, u64 bno, u64 count)
{
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_spaceman *sm = APFS_SM(sb);
@@ -792,6 +811,8 @@ int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
struct apfs_btree_info *fq_info = NULL;
struct apfs_query *query = NULL;
struct apfs_spaceman_free_queue_key raw_key;
bool ghost = count == 1;
int needed_room;
__le64 raw_val;
u64 node_count;
u16 node_limit;
@@ -823,9 +844,20 @@ int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
goto fail;
}
fq_info = (void *)fq_root->object.data + sb->s_blocksize - sizeof(*fq_info);
node_count = le64_to_cpu(fq_info->bt_node_count);
node_limit = le16_to_cpu(fq->sfq_tree_node_limit);
if (node_count == node_limit) {
needed_room = sizeof(raw_key) + (ghost ? 0 : sizeof(raw_val));
if (!apfs_node_has_room(query->node, needed_room, false /* replace */)) {
err = -EAGAIN;
goto fail;
}
}
raw_key.sfqk_xid = cpu_to_le64(nxi->nx_xid);
raw_key.sfqk_paddr = cpu_to_le64(bno);
if (count == 1) {
if (ghost) {
/* A lack of value (ghost record) means single-block extent */
err = apfs_btree_insert(query, &raw_key, sizeof(raw_key), NULL /* val */, 0 /* val_len */);
} else {
@@ -837,20 +869,6 @@ int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
goto fail;
}
fq_info = (void *)fq_root->object.data + sb->s_blocksize - sizeof(*fq_info);
node_count = le64_to_cpu(fq_info->bt_node_count);
node_limit = le16_to_cpu(fq->sfq_tree_node_limit);
if (node_count > node_limit) {
/*
* Ideally this should never happen, but at this point I can't
* be certain of that (TODO). If it does happen, it's best to
* abort and avoid corruption.
*/
apfs_alert(sb, "free queue has too many nodes (%llu > %u)", node_count, node_limit);
err = -EFSCORRUPTED;
goto fail;
}
if (!fq->sfq_oldest_xid)
fq->sfq_oldest_xid = cpu_to_le64(nxi->nx_xid);
le64_add_cpu(&fq->sfq_count, count);
@@ -861,6 +879,44 @@ fail:
return err;
}
/**
* apfs_free_queue_insert_nocache - Add a block range to its free queue
* @sb: superblock structure
* @bno: first block number to free
* @count: number of consecutive blocks to free
*
* Same as apfs_free_queue_insert(), but writes to the free queue directly,
* bypassing the cache of the latest freed block range.
*
* Returns 0 on success or a negative error code in case of failure.
*/
int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
{
struct apfs_spaceman *sm = APFS_SM(sb);
unsigned int qid;
int err;
err = apfs_free_queue_try_insert(sb, bno, count);
if (err == -EAGAIN) {
qid = apfs_block_in_ip(sm, bno) ? APFS_SFQ_IP : APFS_SFQ_MAIN;
err = apfs_flush_free_queue(sb, qid, true /* force */);
if (err) {
apfs_err(sb, "failed to flush fq to make room");
return err;
}
err = apfs_free_queue_try_insert(sb, bno, count);
}
if (err) {
if (err == -EAGAIN) {
apfs_alert(sb, "failed to make room in fq - bug!");
err = -EFSCORRUPTED;
}
apfs_err(sb, "fq insert failed (0x%llx-0x%llx)", bno, count);
return err;
}
return 0;
}
/**
* apfs_free_queue_insert - Add a block range to its free queue
* @sb: superblock structure
+64 -27
View File
@@ -253,9 +253,8 @@ fail:
* apfs_update_software_info - Write the module info to a modified volume
* @sb: superblock structure
*
* Does nothing if the module information is already present at index zero of
* the apfs_modified_by array. Otherwise, writes it there after shifting the
* rest of the entries to the right.
* Writes this module's information to index zero of the apfs_modified_by
* array, shifting the rest of the entries to the right.
*/
static void apfs_update_software_info(struct super_block *sb)
{
@@ -268,11 +267,7 @@ static void apfs_update_software_info(struct super_block *sb)
ASSERT(strlen(APFS_MODULE_ID_STRING) < APFS_MODIFIED_NAMELEN);
mod_by = raw->apfs_modified_by;
/* This check could be optimized away, but does it matter? */
if (!strcmp(mod_by->id, APFS_MODULE_ID_STRING))
return;
memmove(mod_by + 1, mod_by, (APFS_MAX_HIST - 1) * sizeof(*mod_by));
memset(mod_by->id, 0, sizeof(mod_by->id));
strscpy(mod_by->id, APFS_MODULE_ID_STRING, sizeof(mod_by->id));
mod_by->timestamp = cpu_to_le64(ktime_get_real_ns());
@@ -322,7 +317,9 @@ static inline void apfs_free_main_super(struct apfs_sb_info *sbi)
kfree(nxi->nx_raw);
nxi->nx_raw = NULL;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0)
fput(nxi->nx_bdev_file);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
bdev_release(nxi->nx_bdev_handle);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)
blkdev_put(nxi->nx_bdev, &apfs_fs_type);
@@ -475,13 +472,7 @@ int apfs_map_volume_super(struct super_block *sb, bool write)
* Snapshots could get mounted during a transaction, so the fletcher
* checksum doesn't have to be valid.
*/
err = apfs_map_volume_super_bno(sb, vsb, !write && !sbi->s_snap_name);
if (err)
return err;
if (write)
apfs_update_software_info(sb);
return 0;
return apfs_map_volume_super_bno(sb, vsb, !write && !sbi->s_snap_name);
fail:
brelse(bh);
@@ -686,7 +677,10 @@ static void apfs_put_super(struct super_block *sb)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
/* Update the volume's unmount time */
/* Cleanups won't reschedule themselves during unmount */
flush_work(&sbi->s_orphan_cleanup_work);
/* Stop flushing orphans and update the volume as needed */
if (!(sb->s_flags & SB_RDONLY)) {
struct apfs_superblock *vsb_raw;
struct buffer_head *vsb_bh;
@@ -700,6 +694,7 @@ static void apfs_put_super(struct super_block *sb)
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
ASSERT(buffer_trans(vsb_bh));
apfs_update_software_info(sb);
vsb_raw->apfs_unmount_time = cpu_to_le64(ktime_get_real_ns());
set_buffer_csum(vsb_bh);
@@ -765,6 +760,7 @@ static struct inode *apfs_alloc_inode(struct super_block *sb)
dstream->ds_ext_dirty = false;
ai->i_nchildren = 0;
INIT_LIST_HEAD(&ai->i_list);
ai->i_cleaned = false;
return &ai->vfs_inode;
}
@@ -789,6 +785,10 @@ static void init_once(void *p)
inode_init_once(&ai->vfs_inode);
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0)
#define SLAB_MEM_SPREAD 0
#endif
static int __init init_inodecache(void)
{
apfs_inode_cachep = kmem_cache_create("apfs_inode_cache",
@@ -1209,10 +1209,15 @@ static int parse_options(struct super_block *sb, char *options)
out:
apfs_set_nx_flags(sb, nx_flags);
if ((nxi->nx_flags & APFS_READWRITE) && !(sb->s_flags & SB_RDONLY))
apfs_notice(sb, "experimental write support is enabled");
else
sb->s_flags |= SB_RDONLY;
if (!(sb->s_flags & SB_RDONLY)) {
if (nxi->nx_flags & APFS_READWRITE) {
apfs_notice(sb, "experimental write support is enabled");
} else {
apfs_warn(sb, "experimental writes disabled to avoid data loss");
apfs_warn(sb, "if you really want them, check the README");
sb->s_flags |= SB_RDONLY;
}
}
return 0;
}
@@ -1290,8 +1295,12 @@ static int apfs_check_vol_features(struct super_block *sb)
apfs_warn(sb, "incomplete restore is not supported");
return -EINVAL;
}
if (features & APFS_INCOMPAT_RESERVED_40) {
apfs_warn(sb, "reserved incompatible feature flag is set");
if (features & APFS_INCOMPAT_PFK) {
apfs_warn(sb, "PFK is not supported");
return -EINVAL;
}
if (features & APFS_INCOMPAT_SECONDARY_FSROOT) {
apfs_warn(sb, "secondary fsroot is not supported");
return -EINVAL;
}
if (features & APFS_INCOMPAT_SEALED_VOLUME) {
@@ -1301,6 +1310,16 @@ static int apfs_check_vol_features(struct super_block *sb)
}
apfs_info(sb, "volume is sealed");
}
/*
* As far as I can see, all this feature seems to do is define a new
* flag (which I call APFS_FILE_EXTENT_PREALLOCATED) for extents that
* are fully after the end of their file. I don't get why this change
* is incompatible instead of read-only compatible, so I fear I might
* be missing something. I will never be certain though, so for now
* allow the mount and hope for the best.
*/
if (features & APFS_INCOMPAT_EXTENT_PREALLOC_FLAG)
apfs_warn(sb, "extent prealloc flag is set");
features = le64_to_cpu(vsb_raw->apfs_fs_flags);
/* Some encrypted volumes are readable anyway */
@@ -1390,7 +1409,7 @@ static void apfs_set_trans_buffer_limit(struct super_block *sb)
static int apfs_fill_super(struct super_block *sb, void *data, int silent)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct inode *root;
struct inode *root = NULL, *priv = NULL;
int err;
ASSERT(sbi);
@@ -1454,12 +1473,20 @@ static int apfs_fill_super(struct super_block *sb, void *data, int silent)
err = PTR_ERR(root);
goto failed_mount;
}
sb->s_root = d_make_root(root);
if (!sb->s_root) {
apfs_err(sb, "unable to get root dentry");
err = -ENOMEM;
goto failed_mount;
}
INIT_WORK(&sbi->s_orphan_cleanup_work, apfs_orphan_cleanup_work);
if (!(sb->s_flags & SB_RDONLY)) {
priv = sbi->s_private_dir;
if (APFS_I(priv)->i_nchildren)
schedule_work(&sbi->s_orphan_cleanup_work);
}
return 0;
failed_mount:
@@ -1583,16 +1610,26 @@ static int apfs_attach_nxi(struct apfs_sb_info *sbi, const char *dev_name, fmode
nxi = apfs_nx_find_by_dev(dev);
if (!nxi) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
struct bdev_handle *handle;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0)
struct file *file = NULL;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
struct bdev_handle *handle = NULL;
#endif
struct block_device *bdev;
struct block_device *bdev = NULL;
nxi = kzalloc(sizeof(*nxi), GFP_KERNEL);
if (!nxi)
return -ENOMEM;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0)
file = bdev_file_open_by_path(dev_name, mode, &apfs_fs_type, NULL);
if (IS_ERR(file)) {
kfree(nxi);
return PTR_ERR(file);
}
nxi->nx_bdev_file = file;
bdev = file_bdev(file);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
handle = bdev_open_by_path(dev_name, mode, &apfs_fs_type, NULL);
if (IS_ERR(handle)) {
kfree(nxi);
+4 -5
View File
@@ -404,16 +404,16 @@ int apfs_transaction_flush_all_inodes(struct super_block *sb)
mutex_unlock(&nxs_mutex);
up_write(&nxi->nx_big_sem);
/* Unlocked, so it may call ->evict_inode() */
/* Unlocked, so it may call evict() and wait for writeback */
iput(inode);
down_write(&nxi->nx_big_sem);
mutex_lock(&nxs_mutex);
nx_trans->t_state = 0;
/* Transaction aborted by ->evict_inode(), error code is lost */
/* Transaction aborted during writeback, error code is lost */
if (sb->s_flags & SB_RDONLY) {
apfs_err(sb, "abort during inode eviction");
apfs_err(sb, "abort during inode writeback");
return -EROFS;
}
}
@@ -736,7 +736,7 @@ static bool apfs_transaction_need_commit(struct super_block *sb)
return false;
}
/* Avoid nested commits on ->evict_inode() */
/* Avoid nested commits on inode writeback */
if (nx_trans->t_state & APFS_NX_TRANS_COMMITTING)
return false;
@@ -971,7 +971,6 @@ void apfs_transaction_abort(struct super_block *sb)
mutex_unlock(&nxs_mutex);
up_write(&nxi->nx_big_sem);
/* ->evict_inode() will just fail if it starts a new transaction */
list_for_each_entry_safe(ai, ai_tmp, &nx_trans->t_inodes, i_list) {
list_del_init(&ai->i_list);
iput(&ai->vfs_inode);