60 Commits
Author SHA1 Message Date
Ernesto A. Fernández 695455bfdb 0.3.10
Transactions are now committed regularly, so it should no longer be
strictly necessary to unmount the filesystem cleanly for writes to take
effect. It's still recommended, of course.

The release also fixes two bugs. One was a big performance regression
from 0.3.9, seen when deleting large numbers of files. The other was
that mounts would go read-only if two snapshots were attempted with the
same name.

Support is added for the new 6.10 kernel version, with a patch by Woody
Suwalski.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-07-15 15:13:17 -03:00
Woody Suwalski 071cdacc32 Small tweaks for building on kernel 6.10 2024-05-28 20:46:02 -03:00
Ernesto A. Fernández 57eb739f59 Don't abort a snapshot if the name is in use
Attempting to create two snapshots with the same name will abort a
transaction and leave the filesystem in a read-only state. From the
comment it seems that I was aware of this issue when I first implemented
snapshots, but I guess I didn't like the obvious solution in this patch
at the time, so I decided to leave it for later.

Since taking snapshots is not expected to be a bottleneck, just run the
query for the name once more ahead of time, as soon as the transaction
opens. Now if the name already exists we don't need to abort, because no
changes have taken place.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-05-28 18:54:56 -03:00
Ernesto A. Fernández f088d1a2e3 Set up a task to commit transactions
Currently my driver only commits transactions "synchronously", that is,
immediately following a user action that triggers it. This implies, in
particular, that the filesystem must get unmounted explicitly or else
data will almost certainly get dropped. While technically valid
behaviour, this is very weird and some people have complained about it
in the past.

So, stop ignoring requests for commit via apfs_transaction_commit() when
they are considered premature. Instead schedule a commit task, with a
small delay to avoid constant commits when the driver is still busy.

Note that the previous two patches were intended to help with this one:
the plan was that the commit task wouldn't need to hold a specific
volume superblock, and could instead work with the nx info. In the end
this proved too messy, so I'm just saving the volume superblock that
requested the commit in the transaction struct.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-05-23 18:46:25 -03:00
Ernesto A. Fernández 8bcaaebb89 Move trans_buffers_max from volume to container
The limit on the number of buffer heads in a transaction depends only on
the ram size, so it's the same for all volumes. Move it to the container
superblock info. Frankly this could just be a global variable, set when
the driver is first inserted, but there is no problem with recalculating
it on all mounts.

The point of this change, along with the previous patch, is that soon we
won't require holding some volume to start and commit transactions.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-05-21 18:26:18 -03:00
Ernesto A. Fernández 781e0024d2 Stop attempting to undo aborted transactions
When I first implemented transactions, my intention was to revert the
in-memory filesystem structures to a consistent state whenever we hit
the kind of errors that can be expected during normal operation. So I
always kept some stuff from the previous transaction around to copy it
back if needed. I never tested any of this though, so it most likely
never worked.

Later improvements mean that the kind of silly errors I had in mind
(things like ENOENT) are no longer supposed to happen during
transactions. The one exception here is ENOSPC, but that's only because
I haven't yet bothered to clean up the space reservations. The errors
that do happen are things like ENOMEM, EIO or EFSCORRUPTED, and I think
it's probably ok if the mount doesn't work perfectly after one of those.
Instead we force it into a read-only state, and let the user decide how
to proceed.

So, there is now little point in keeping my original half-baked plans
littering the transaction code. Remove all that stuff.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-05-20 20:06:53 -03:00
Ernesto A. Fernández 5d41bd7e64 Initialize transaction fields on first mount
The container superblock info keeps a list of inodes and buffers that
have been modified in the current transaction. This is a bit of a hack,
but so far I haven't figured out a way to get rid of it. The lists need
to be initialized before they are first used, and this is currently done
by apfs_transaction_start() itself, after checking if this is the first
start since the last commit. This works fine, but it's really weird,
since the list gets regularly reinitialized to "empty" after getting
emptied naturally by a commit. Initialization should really be separate
from all the code that actually works with the transaction.

Define a new apfs_transaction_init() to take care of this, and call it
from apfs_attach_nxi() which already takes care of the initialization of
all other container info fields.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-05-20 15:18:14 -03:00
Ernesto A. Fernández 4e9984c815 Rename the transaction limit constants
TRANSACTION_MAIN_QUEUE_MAX, TRANSACTION_BUFFERS_MAX and
TRANSACTION_STARTS_MAX were originally desfined inside transaction.c, so
I guess I didn't bother to give them the driver prefix. I don't expect
the names to collide with anything, but it's tidier to follow the same
naming conventions everywhere, so run

  sed -i 's/TRANSACTION_/APFS_TRANS_/' *.{c,h}

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-05-16 20:49:12 -03:00
Ernesto A. Fernández f1c9ab6c38 Adjust TRANSACTION_MAIN_QUEUE_MAX
The iozone automatic tests suffered a big performance regression between
0.3.8 and 0.3.9, in my case from around 14m to close to 20m. The problem
goes away if the newly introduced apfs_dstream_delete_front() is tweaked
to delete files in larger chunks. That said, I don't think my choice for
TRANSACTION_MAIN_QUEUE_MAX was ever based on anything, so it's better to
adjust that instead of coming up with some hack for file deletion alone.

After a lot of testing, I think I get the best results if I raise it
from 4096 to 10000. The iozone benchmark is now better than before, at
around 13m for me.

Of course this will mean that transactions commit less often. I should
look into putting that in a workqueue like the file deletion.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-05-16 20:32:39 -03:00
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
Ernesto A. Fernández 3f9d529bcb 0.3.8
This release reworks ephemeral objects to support larger containers of
up to ~7.8 TiB, and to allow the creation of checkpoint mapping blocks.

Also the build is no longer broken for the new 6.8 kernel, thanks to a
patch by Woody Suwalski.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-03-11 00:32:25 -03:00
Woody Suwalski 60ef1e9991 Building on kernel 6.8
[eafer: moved bdev handle to the container superblock]
2024-03-10 20:58:42 -03:00
Ernesto A. Fernández 0313a0fee1 Rework handling of ephemeral objects
In pretty much all containers I've encountered so far, every objects has
a single block. There is an annoying exception though: containers with a
size between ~6.7 TiB and ~7.8 TiB have a multiblock spaceman. I have no
simple way to deal with this because my transactions currently handle
ephemeral objects very roughly, ignoring all the cpm metadata and just
updating each block in the expected header location.

Instead of trying to hack this in somehow, start doing the right thing
as explained in the official reference and keep all ephemeral objects in
memory at all times. So, when the first transaction starts, go through
the cpm metadata and read each object along with its size and oid. When
a new object gets created (or an old one gets deleted), all changes
happen in memory. On commit time, checksums are updated and all objects
get written to disk, but nothing neads to be read.

So this is all much cleaner, and I took this opportunity to support
multiple cpm blocks as well.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-03-01 21:12:56 -03:00
Ernesto A. Fernández 784dd43146 Fix typo in previous commit
It's annoying to have to make a new commit just for this, but I had
already pushed the bad one...

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-02-22 16:01:46 -03:00
Ernesto A. Fernández 7df84d2173 Mention fusion drives in the README
Fusion drives are not yet supported, but the README does not mention
this. Fix it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-02-20 20:04:21 -03:00
Ernesto A. Fernández fbf052e1e9 Check container features as soon as possible
Failure to mount fusion drives currently gets reported in a confusing
way:

  https://github.com/linux-apfs/linux-apfs-rw/issues/39#issuecomment-1947466697

The problem is that all feature checks are currently run at the same
time for both the container and the volume, after the volume superblock
has been mapped. Fusion drives never get that far, we can't even work
with the container's object map.

Split the feature checks into two functions, one for the container and
one for the volume. Leave the call for the volume in the same place, but
move the one for the container into apfs_map_main_super().

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-02-20 19:46:42 -03:00
Ernesto A. Fernández 41af006115 0.3.7
This is a small release that fixes the dkms build, recently broken by
the implementation of version reporting. From now on I will run a dkms
build before each new tag.

The release also lifts the ban on filesystems with half-written backup
superblocks, a problem that some users appear to have encountered
already.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-01-18 20:07:43 -03:00
Ernesto A. Fernández cba2045422 Report dkms version when git is not available
Some users might build the driver from the tarball that github produces
on release, so the reported git commit would be an empty string. Instead
extract the version from the dkms file and use that, but add a question
mark so that I know that it might not be accurate.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-01-18 19:35:20 -03:00
Ernesto A. Fernández 5e133a2ca5 Fix version header generation for dkms
The dkms build fails for the latest release:

  https://github.com/linux-apfs/linux-apfs-rw/pull/60

The problem is that dkms does not call my Makefile at all, so the
version header is never generated. Dkms does have a PRE_BUILD hook
though, so use that to call a script that takes care of this.

From now on, I must always remember to run a dkms build before a new
release.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-01-17 22:36:05 -03:00
Ernesto A. Fernández 9ae07db606 Don't refuse to mount a bad backup superblock
The backup superblock is only needed to locate the actual superblock, so
it doesn't matter if it's corrupted beyond the first sector, which the
disk is supposed to write atomically. If a crash happened at the right
moment during unmount, any implementation might end up with a
half-written superblock where the checksum doesn't add up, but that's
perfectly fine.

If this is encountered during mount, just report it and proceed. This is
not a theoretical concern, someone appears to have run into the problem
already:

  https://techsparx.com/linux/ubuntu/mount-apfs.html

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-01-08 21:22:08 -03:00
Ernesto A. Fernández 10557edf23 0.3.6
This release heavily reworks the node defragmentation and split logic to
fix a handful of rarely triggered data corruption bugs. The new version
is easier to understand and hopefully more robust.

Support is added for containers with a size of up to ~6.7 TiB, improving
the previous maximum of ~1.3 TiB. The next release will probably keep
pushing this limit.

Changes are made to the way that subvolume "devices" are reported. Now
we are closer to matching the behaviour of btrfs and userland tools like
Nautilus are far less confused.

The driver version information is now provided by both the on-disk
filesystem metadata and by modinfo. This may be useful for responding to
bugs at some point.

A few small issues with snapshot reporting have been addressed, as well
as an mmap-related warning in recent kernel versions.

Finally, support is added for the 6.7 kernel version (released today),
contributed by Woody Suwalski.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-01-07 20:53:51 -03:00
Ernesto A. Fernández bcedfe1752 Read error before setting error pointer to null
I'm in a rush to get the tag out today and I'm making a mess. The
previous patch set an error pointer to null in apfs_node_split(), but I
didn't notice that the actual error would get read later. Reorder this.
I usually would just squash this into the previous patch, but in my rush
I have already pushed it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-01-07 19:10:40 -03:00
Ernesto A. Fernández 8b2287d91f Don't free error pointers in apfs_node_split()
We mistakenly free an error pointer if apfs_create_node() happens to
fail. Set the node to null before the cleanup, but I guess I should try
to avoid error pointers in the future.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-01-07 19:03:09 -03:00
Woody Suwalski 5dc836ad96 Support for kernel 6.7
[eafer: fixed some issues with folios and timestamps]
Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-01-07 16:27:45 -03:00
Ernesto A. Fernández fe13bc9636 Don't refresh queries pointing to deleted nodes
Running generic/013 for a few minutes still leaves the filesystem in a
corrupted state, with a missing inode record. This is another regression
caused by my big rework of the node split logic. When a single-record
node is needed but the parent update fails with -EAGAIN,
apfs_create_single_rec_node() leaves the query pointing to the aborted
new node. The refresh does not change this, so the inode record ends up
abandoned in this ghost node.

In then end all of this is caused by the weird way I originally designed
the query struct, which forces me to always keep a more or less valid
leaf query instead of just nuking the whole thing and starting over. I'd
like to change this eventually, but for now just adjust the cleanup for
apfs_create_single_rec_node().

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-01-06 01:48:44 -03:00
Ernesto A. Fernández af1491622d Avoid filename normalizations issues on refresh
The previous patch introduced a regression in generic/013, seen when the
test is allowed to run for 20 minutes or so. The result is a corrupted
filesystem where one inode appears to have too many dentries, and
another inode has too few to balance.

The problem is that query refreshes just rerun the query once and expect
the correct result, but dentry queries for an existing record actually
need to be called repeatedly to deal with hash collisions. For
normalization-sensitive filesystems (which I use for most of my tests)
"collisions" are constant because there is no hash. So if a split
happens during a dentry deletion, chances are very high that the refresh
will end up pointing to the wrong record.

Don't add awkward code to deal with this to apfs_query_refresh().
Instead transform the multiple query from apfs_dentry_lookup() into a
regular exact query before returning, setting the key to the
unnormalized name. The refresh code will have no trouble working with
that.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-01-04 23:22:34 -03:00
Ernesto A. Fernández e7983cb3fe Restart btree modifications after a node split
I'm encountering some rare failures in generic/075. The problem happens
when a node split is triggered by inserting a record into the first
position. The parent needs to get updated of course, but we've lost the
reference to it after the split, so the filesystem becomes corrupted.

The reality is that this stuff has always been too hacky, and new corner
cases keep popping up. Stop trying to guess which queries need to know
and might have forgotten their ancestors: just restart the whole btree
operation after any split, refreshing the query with a simple rerun.
This has to be the safest possible approach, and I don't expect it to
hurt performance, since node splits should never be a bottleneck.

Big changes are needed to get it to work though, because the b-tree must
be in a consistent state whenever the query gets refreshed. There are
two different considerations here. First, no split can take place unless
the parent has enough room to index the new child node; otherwise the
parent split would trigger a query refresh while half the child's
records are orphaned. To sum up, every cascade of node splits must
happen from root to leaf.

The second consideration is that, even if a node has enough room, no
updates related to the actual requested operation (the leaf record
insertion/deletion/replacement) can take place at any level until all
splits are done. A different policy would mean that a query refresh
might become necessary halfway through the operation, and so the leaf
record could be found to be unexpectedly missing, or covered by the
wrong index key.

So at a given level: the split, if needed, always comes first, followed
by the function call for parent updates, and only then the node gets
modified. After a split an EAGAIN error gets thrown, which then bubbles
back up to the leaf (either through the splits themselves, or through
the parent update calls), so only a single split can happen for the
whole b-tree on each refresh iteration - always at the highest possible
level in the split cascade that would have been triggered before this
patch. The btree, of course, remains valid on EAGAIN, and the leaf
records remain the same.

Inside apfs_node_split() itself, no changes to the old node can be done
until the new one is safely inserted in the b-tree. If that triggers a
split at a higher level, then the new node must be properly cleaned up
(making use of the recent changes to apfs_delete_node()) and, as usual,
EAGAIN must get bubbled up.

A similar cleanup needs to be take place in the one other function that
creates non-root nodes: apfs_create_single_rec_node(). The new
single-record node must be repeatedly destroyed until the parent manages
to index it without any splits.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-01-04 22:01:40 -03:00
Ernesto A. Fernández 2de7f65d5b Implement a helper to extract root node from query
Implement apfs_query_root() and use it to simplify two functions. It
will get reused further in the following patch, including inside node.c,
so don't make it static.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-12-30 16:29:55 -03:00
Ernesto A. Fernández e872d3971a Allow NULL to be passed to apfs_node_free()
Allow NULL node pointers to be "freed" with apfs_node_free(). This will
simplify some cleanups soon.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-12-29 23:48:30 -03:00
Ernesto A. Fernández eb6dd65019 Don't let node insert/replace fail with ENOSPC
Currently node insertions/deletions are attempted immediately and they
return -ENOSPC when there is no room, letting the caller know that the
node needs to get split. But in future patches I'll need to reorder the
operations so that the node only gets updated after all the ancestors.

The previous patches already ensured that apfs_node_replace() will never
erroneously report lack of space, no matter how fragmented the node may
be. This was already true of apfs_node_insert(). So from now on we can
safely run space checks ahead of time with only the node metadata, use
that to decide if a split is needed, and only update the records after
all that is done.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-12-29 23:22:46 -03:00
Ernesto A. Fernández 0c8beaa186 Also defragment freed space in apfs_node_replace()
In the near future we want to make sure that apfs_node_replace() will
never fail as long as it has enough space. The defragmentation from the
previous patch is not enough for this, because it still leaves the
replaced record in place instead of moving those useless bytes into the
free area.

To solve this as simply as possible, set the table-of-contents entry for
the replaced record to zero length, for either the key or the value (or
both), depending on which we are replacing. The defragmentation code
will take care of the rest. Of course this weird toc entry remains in
place if the function fails, but it will be replaced eventually so it
should not matter.

By the way, this hack cannot be used on trees with fixed key/value
sizes, because those sizes are simply not reported in the toc. Of course
records in such trees can generally be replaced in place, so they won't
need defragmentation. There is one exception though: the ghost records
of the free queue. Luckily we don't currently call replace on free queue
leaves, so we can safely ignore this problem. Leave an assert in place
so that I don't forget.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-12-29 22:04:16 -03:00
Ernesto A. Fernández 2adf86cba9 Defragment the node in apfs_node_replace()
Like we already do in apfs_node_insert(), attempt to defragment the node
before returning -ENOSPC in apfs_node_replace(). This shouldn't make
much of a difference right now (other than packing the records a bit
tighter together), but in future patches it will help us prevent
apfs_node_replace() from ever failing in normal operation.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-12-29 21:56:02 -03:00
Ernesto A. Fernández f883518a96 Move toc entry update into new function
Define a new apfs_node_update_toc_entry() to be used when replacing
records in a node. This makes apfs_node_replace() somewhat cleaner, but
more importantly it will allow that part to be reused in future patches.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-12-29 19:40:24 -03:00
Ernesto A. Fernández 4eb83167a9 Document some node functions that may throw ENOSPC
Improve the documentation of the record allocation functions to make it
clear that they may return -ENOSPC, because that case will often need to
be handled separately in the future.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-12-29 19:35:22 -03:00
Ernesto A. Fernández e053f3b2b5 Call apfs_delete_node() with a node, not a query
Some changes I'm working on will require being able to delete nodes that
have not yet been indexed in a btree. This cannot be done with the
current version of apfs_delete_node(), because it handles the parent
node modifications itself. So from now on, let the caller handle that if
it's necessary. This is also more symmetrical with apfs_create_node(),
as would be expected.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-12-29 18:28:01 -03:00
Ernesto A. Fernández 196ac31122 Don't allow a stale offset for ghost records
When apfs_node_locate_data() gets called for a ghost record (that is, a
record without a value), the function correctly returns the zero length,
but the offset is left unset, and so a stale number may remain in the
query structure. This does not matter of course, because a zero-length
value will never be read, but I've found it very distracting while
debugging a separate issue with query offsets. Just set it to zero.

To be consistent, do the same in apfs_node_insert(), though I haven't
been bothered by this one yet.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-12-28 19:48:25 -03:00
Ernesto A. Fernández f1775ed0ef Turn the search key into a query field
I've been working on making some very needed changes to how node splits
work. The plan is to always rerun queries after a split, to keep the
query chain valid. One problem I've encountered while implementing this
is that the search key is not really preserved by apfs_update_inode().
We have a pointer to it, but it was allocated in the stack of some other
function, so the data is just noise.

I've always thought it was silly to allocate both a query and a key
before a lookup. Instead, turn the search key into a field within the
query structure, and copy it to the children as the query gets run. This
will ensure that the key remains available for future reruns.

One thing to note is that, while this simplifies most callers a little
bit, some of them still allocate a key in the stack and later copy it to
the query. I didn't want to make any big changes and risk introducing
bugs at this point, since I'm busy with other stuff.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-12-20 20:33:03 -03:00
Ernesto A. Fernández a33f06d8da Do use anon devices, but in a different way
Like I mentioned in the previous patch, df is refusing to acknowledge
multiple mounted volumes or snapshots for the same device, and it's
reporting only one. Luckily the source code for df is straightforward,
so I figured out that it calls stat() on the mount point to find the
device number instead of just accepting the one reported by mountinfo.
This way each mounted volume/snapshot can choose to report a different
number, instead of just going with the one set in the superblock.

After confirming that this is indeed what btrfs does, implement it here.
This fixes the df issue, but sadly it doesn't fix all problems with
thunar. It can tell the different mounts apart, but for some reason I
need to close it and reopen it for that to happen (in fact I now realize
that it could already do that in the previous commit). Since btrfs does
have plugins for udisks and such, maybe this is not the driver's fault,
but I'm not sure.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-12-08 22:08:13 -03:00
Ernesto A. Fernández 9c0d396e20 Don't use anon devices
Nautilus and other file managers fail to unmount apfs volumes:

  https://github.com/linux-apfs/linux-apfs-rw/issues/58

This has been broken for years, since commit 4a6c0e2b77 ("Support
simultaneous mount of multiple volumes"), but I never noticed because I
always use the command line, and there are no problems either there or
with libmount.

The error message ("Device `%s' is not mounted") appears to come from
udisks. I don't know much about this stuff, but from tests to my driver
it seems that userland expects /proc/self/mountinfo to report the device
number for the mounted device, not just the name. The tricks we do with
anon devices to support multiple mounts cause a confusion.

Stop using anon devices. Instead set the real device number on the
superblock, and ignore it for all purposes other that mountinfo
reporting, which is done by the common code.

This should solve the reported bug, but causes other weird behaviour.
For instance, if I mount the current transaction and later mount a
snapshot, both df and thunar refuse to acknowledge the existence of the
first mount. I don't know how btrfs deals with this, but they seem to
have plugins for udisks and libblockdev so it's probably not as simple
as a driver change.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-12-07 22:16:55 -03:00
Ernesto A. Fernández 760cf779ef Add snapshot name to apfs_show_options()
I just noticed that the snapshot name option is not getting output in
/proc/mounts. I guess I just forgot to implement this, do it now.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-12-07 21:52:06 -03:00
Ernesto A. Fernández 0d274c7b48 Fix ->statfs() block count for snapshot mounts
I just noticed that df throws a "No data available" error if it's used
while a snapshot is mounted. The problem happens while gathering the
total number of used blocks: to iterate through the existing volume
superblocks, this requires working with the container omap, which we
otherwise only do for snapshots during mount. apfs_omap_lookup_block()
is written with the idea that snapshots will only call it for the volume
omap, and so it uses the snapshot xid to get the wanted (old) version of
the object. But for used space calculations we actually want the latest,
and in fact the container omap doesn't even have the old versions of the
volumes, so the call fails.

Reimplement apfs_omap_lookup_block() as a wrapper around a new
apfs_omap_lookup_block_with_xid() that uses the current snapshot
transaction, and use this to implement apfs_omap_lookup_newest_block()
to be used in this situation.

By the way, there was no output on dmesg when I discovered this bug.
That's no good, so add a new error message in here.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-12-07 21:22:32 -03:00
Ernesto A. Fernández 53a9145401 Support multiblock internal pool bitmaps
A container with a size starting at 1454383304704 bytes (~1.3 TiB) needs
more than one block to hold the bitmap of its internal pool. Add support
for such large filesystems.

To this end, replace the single buffer head holding the bitmap in the
spaceman struct with an array, which requires allocating the spaceman
separately from the container superblock info. Then rework the alloc and
free functions to work with the whole array.

The biggest changes are in apfs_rotate_ip_bitmaps(), which now has to
copy all the bitmap blocks at once to make them available for the
transaction. This is probably not what the official implementation
intended, but it should be technically correct.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-12-01 20:46:36 -03:00
Ernesto A. Fernández 1e8251a458 Report driver version for modinfo
The previous commit started reporting the driver version on disk after a
modification. Report it as well in the driver metadata, so that it can
be obtained with modinfo.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-11-14 20:54:54 -03:00
Ernesto A. Fernández e0b1eeb29d Include release info in apfs_modified_by field
The filesystem superblock has an apfs_modified_by field to report which
implementation is responsible for the latest changes. This could be
useful to track down bugs eventually, but it will need to have more
information. Right now we only report my name and the name of the
module; change it to also report the exact commit id, or the tag if
there is one. There is a strict size limit here, so shorten my name to
"eafer", which is what I use on github, so it will make the driver easy
to track down.

Also move the APFS_MODULE_ID_STRING macro definition to super.c. We'll
now need to recompile that file with every build, but if we left it
inside apfs.h then we would have to recompile everything all the time.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-11-03 22:10:03 -03:00
Ernesto A. Fernández 3c36f372be Get rid of apfs_end_buffer_write_sync()
Some mmap related tests such as generic/030 are throwing the following
callstack on kernels 6.1 and above:

[ 9810.190496] ------------[ cut here ]------------
[ 9810.192000] irq 15 handler ata_bmdma_interrupt+0x0/0x1f0 [libata] enabled interrupts
[ 9810.193639] WARNING: CPU: 0 PID: 0 at kernel/irq/handle.c:161 __handle_irq_event_percpu+0x150/0x190
[ 9810.195220] Modules linked in: apfs(OE) libcrc32c(E) 9p(E) fscache(E) netfs(E) intel_rapl_msr(E) intel_rapl_common(E) intel_pmc_core_pltdrv(E) intel_pmc_core(E) kvm_intel(E) kvm(E) irqbypass(E) ghash_clmulni_intel(E) ppdev(E) aesni_intel(E) bochs(E) libaes(E) drm_vram_helper(E) drm_ttm_helper(E) crypto_simd(E) cryptd(E) ttm(E) rapl(E) snd_pcm(E) snd_timer(E) 9pnet_virtio(E) drm_kms_helper(E) 9pnet(E) snd(E) soundcore(E) pcspkr(E) parport_pc(E) parport(E) button(E) joydev(E) evdev(E) serio_raw(E) sg(E) binfmt_misc(E) fuse(E) drm(E) efi_pstore(E) configfs(E) qemu_fw_cfg(E) ip_tables(E) x_tables(E) autofs4(E) ext4(E) crc32c_generic(E) crc16(E) mbcache(E) jbd2(E) sd_mod(E) t10_pi(E) crc64_rocksoft(E) crc64(E) crc_t10dif(E) crct10dif_generic(E) ata_generic(E) ata_piix(E) libata(E) crct10dif_pclmul(E) crct10dif_common(E) crc32_pclmul(E) virtio_pci(E) virtio_pci_legacy_dev(E) crc32c_intel(E) virtio_pci_modern_dev(E) virtio(E) psmouse(E) virtio_ring(E) e1000(E) scsi_mod(E) scsi_common(E)
[ 9810.195294]  i2c_piix4(E) floppy(E) [last unloaded: apfs(OE)]
[ 9810.215274] CPU: 0 PID: 0 Comm: swapper/0 Tainted: G        W  OE      6.0.0-rc2+ #24
[ 9810.216639] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
[ 9810.217983] RIP: 0010:__handle_irq_event_percpu+0x150/0x190
[ 9810.218904] Code: 44 00 00 e9 09 ff ff ff 80 3d 8f 16 4e 01 00 75 1b 48 8b 13 44 89 ee 48 c7 c7 e8 31 91 bc c6 05 79 16 4e 01 01 e8 90 26 81 00 <0f> 0b fa 0f 1f 44 00 00 e9 fc fe ff ff 3e 48 0f ba 6b 40 01 0f 82
[ 9810.223642] RSP: 0018:ffffaf4240003f60 EFLAGS: 00010286
[ 9810.224836] RAX: 0000000000000000 RBX: ffff94804172f080 RCX: 0000000000000000
[ 9810.226451] RDX: 0000000000010002 RSI: ffffffffbc94d2a2 RDI: 00000000ffffffff
[ 9810.228082] RBP: 0000000000000001 R08: 0000000000000000 R09: ffffaf4240003e00
[ 9810.229506] R10: 0000000000000003 R11: ffffffffbccc4778 R12: 0000000000000000
[ 9810.231182] R13: 000000000000000f R14: ffff94804019ae00 R15: 0000000000000000
[ 9810.232856] FS:  0000000000000000(0000) GS:ffff948177c00000(0000) knlGS:0000000000000000
[ 9810.234771] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 9810.236112] CR2: 00007f6d608b1210 CR3: 000000006020a004 CR4: 0000000000370ef0
[ 9810.237812] Call Trace:
[ 9810.239294]  <IRQ>
[ 9810.239767]  handle_irq_event+0x34/0x70
[ 9810.240688]  handle_edge_irq+0x9f/0x240
[ 9810.241577]  __common_interrupt+0x63/0x100
[ 9810.242444]  common_interrupt+0xb4/0xd0
[ 9810.243141]  </IRQ>
[ 9810.243575]  <TASK>
[ 9810.244008]  asm_common_interrupt+0x22/0x40
[ 9810.244820] RIP: 0010:native_safe_halt+0xb/0x10
[ 9810.245705] Code: 01 00 3e 80 48 02 20 48 8b 00 a8 08 75 bf e9 7b ff ff ff cc cc cc cc cc cc cc cc cc cc cc cc 66 90 0f 00 2d a9 eb 49 00 fb f4 <c3> cc cc cc cc 66 90 0f 00 2d 99 eb 49 00 f4 c3 cc cc cc cc cc 0f
[ 9810.249342] RSP: 0018:ffffffffbcc03e90 EFLAGS: 00000252
[ 9810.250451] RAX: ffffffffbc16ab90 RBX: ffffffffbcc139c0 RCX: 0000000000000000
[ 9810.252391] RDX: 4000000000000000 RSI: ffffffffbc94d2a2 RDI: 00000000005ec6ec
[ 9810.253784] RBP: 0000000000000000 R08: 00000a4bf1fdb94f R09: 0000000000000000
[ 9810.256613] R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000000000
[ 9810.258508] R13: 0000000000000000 R14: ffffffffbcc13118 R15: 0000000000000000
[ 9810.260053]  ? ldsem_down_write+0x230/0x230
[ 9810.261021]  default_idle+0xa/0x10
[ 9810.261734]  default_idle_call+0x36/0xf0
[ 9810.262574]  do_idle+0x220/0x2b0
[ 9810.263341]  cpu_startup_entry+0x19/0x20
[ 9810.264217]  rest_init+0xcb/0xd0
[ 9810.265119]  arch_call_rest_init+0xa/0x14
[ 9810.266173]  start_kernel+0x6d2/0x6fb
[ 9810.267059]  secondary_startup_64_no_verify+0xce/0xdb
[ 9810.268261]  </TASK>
[ 9810.268837] ---[ end trace 0000000000000000 ]---

I bisected this back to upstream commit f168420c62e7 ("blk-mq: don't
redirect completion for hctx withs only one ctx mapping"), but that
doesn't tell me much.

I can't tell for sure what's going wrong here, but it seems to be
related to my implementation of ->b_end_io(), which exists to force page
faults when we write to an mmapped page again after a commit. I didn't
follow any clear reference when I wrote that code, and I'm guessing
something about it is not allowed. Also, this code runs asynchronously,
which seems to fit with the nature of the problem, since my driver is
not mentioned in the callstack.

Just use the standard end_buffer_write_sync() for ->b_end_io(), and wait
for each buffer before cleaning its page. This is similar to what we do
for reads and it doesn't have any original async stuff, so hopefully it
will work better. At least none of my tests are failing anymore.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-11-03 20:49:35 -03:00
20 changed files with 2782 additions and 1671 deletions

No files matched your search

+1
View File
@@ -9,3 +9,4 @@
*.a
*.mod
tags
version.h
+2
View File
@@ -14,8 +14,10 @@ apfs-y := btree.o compress.o dir.o extents.o file.o inode.o key.o libzbitmap.o \
spaceman.o super.o symlink.o transaction.o unicode.o xattr.o xfield.o
default:
./genver.sh
make -C $(KERNEL_DIR) M=$(PWD)
install:
make -C $(KERNEL_DIR) M=$(PWD) modules_install
clean:
rm -f version.h
make -C $(KERNEL_DIR) M=$(PWD) clean
+2 -1
View File
@@ -22,7 +22,8 @@ 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.
Encryption is not yet implemented even in read-only mode.
Encryption is not yet implemented even in read-only mode, and neither are
fusion drives.
Build
=====
+101 -48
View File
@@ -13,8 +13,6 @@
#include <linux/version.h>
#include "apfs_raw.h"
#define APFS_MODULE_ID_STRING "linux-apfs by EA Fernández"
#define EFSBADCRC EBADMSG /* Bad CRC detected */
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
@@ -66,6 +64,7 @@ struct apfs_object {
*/
struct buffer_head *o_bh;
char *data; /* The raw object */
bool ephemeral; /* Is this an ephemeral object? */
};
/* Constants used in managing the size of a node's table of contents */
@@ -123,8 +122,8 @@ static inline bool apfs_node_has_fixed_kv_size(struct apfs_node *node)
*/
struct apfs_spaceman {
struct apfs_spaceman_phys *sm_raw; /* On-disk spaceman structure */
struct buffer_head *sm_bh; /* Buffer head for @sm_raw */
struct buffer_head *sm_ip; /* Current internal pool */
struct apfs_nxsb_info *sm_nxi; /* Container superblock */
u32 sm_size; /* Size of @sm_raw in bytes */
u32 sm_blocks_per_chunk; /* Blocks covered by a bitmap block */
u32 sm_chunks_per_cib; /* Chunk count in a chunk-info block */
@@ -140,11 +139,20 @@ struct apfs_spaceman {
*/
u64 sm_free_cache_base;
u64 sm_free_cache_blkcnt;
/* Shift to match an ip block with its bitmap in the array */
int sm_ip_bmaps_shift;
/* Mask to find an ip block's offset inside its ip bitmap */
u32 sm_ip_bmaps_mask;
/* Number of ip bitmaps */
u32 sm_ip_bmaps_count;
/* List of ip bitmaps, in order */
struct buffer_head *sm_ip_bmaps[];
};
#define TRANSACTION_MAIN_QUEUE_MAX 4096
#define TRANSACTION_BUFFERS_MAX 65536
#define TRANSACTION_STARTS_MAX 65536
#define APFS_TRANS_MAIN_QUEUE_MAX 10000
#define APFS_TRANS_BUFFERS_MAX 65536
#define APFS_TRANS_STARTS_MAX 65536
/* Possible states for the container transaction structure */
#define APFS_NX_TRANS_FORCE_COMMIT 1 /* Commit guaranteed */
@@ -156,25 +164,17 @@ struct apfs_spaceman {
* Structure that keeps track of a container transaction.
*/
struct apfs_nx_transaction {
struct buffer_head *t_old_msb; /* Main superblock being replaced */
unsigned int t_state;
struct delayed_work t_work; /* Work task for transaction commits */
struct super_block *t_work_sb; /* sb that last queued a commit */
struct list_head t_inodes; /* List of inodes in the transaction */
struct list_head t_buffers; /* List of buffers in the transaction */
size_t t_buffers_count; /* Count of items on the list */
int t_starts_count; /* Count of starts for transaction */
};
/*
* Structure that keeps track of a volume transaction.
*/
struct apfs_vol_transaction {
struct buffer_head *t_old_vsb; /* Volume superblock being replaced */
struct apfs_node t_old_omap_root; /* Omap root node being replaced */
struct apfs_node t_old_cat_root; /* Catalog root node being replaced */
};
/* State bits for buffer heads in a transaction */
#define BH_TRANS BH_PrivateStart /* Attached to a transaction */
#define BH_CSUM (BH_PrivateStart + 1) /* Requires checksum update */
@@ -197,6 +197,23 @@ struct apfs_max_ops {
int blks; /* Maximum extent blocks that may need changing */
};
/*
* List entry for an in-memory ephemeral object
*/
struct apfs_ephemeral_object_info {
u64 oid; /* Ephemeral object id */
u32 size; /* Size of the object in bytes */
void *object; /* In-memory address of the object */
};
/*
* We allocate a fixed space for the list of ephemeral objects. I don't
* actually know how big this should be allowed to get, but all the objects
* must be written down with each transaction commit, so probably not too big.
*/
#define APFS_EPHEMERAL_LIST_SIZE 32768
#define APFS_EPHEMERAL_LIST_LIMIT (APFS_EPHEMERAL_LIST_SIZE / sizeof(struct apfs_ephemeral_object_info))
/* Mount option flags for a container */
#define APFS_CHECK_NODES 1
#define APFS_READWRITE 2
@@ -206,10 +223,19 @@ struct apfs_max_ops {
*/
struct apfs_nxsb_info {
struct block_device *nx_bdev; /* Device for the container */
#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 */
struct apfs_object nx_object; /* Main superblock object */
u64 nx_bno; /* Current block number for the checkpoint superblock */
u64 nx_xid; /* Latest transaction id */
/* List of ephemeral objects in memory (except the superblock) */
struct apfs_ephemeral_object_info *nx_eph_list;
int nx_eph_count;
struct list_head vol_list; /* List of mounted volumes in container */
unsigned int nx_flags; /* Mount options shared by all volumes */
@@ -219,8 +245,9 @@ struct apfs_nxsb_info {
unsigned long nx_blocksize;
unsigned char nx_blocksize_bits;
struct apfs_spaceman nx_spaceman;
struct apfs_spaceman *nx_spaceman;
struct apfs_nx_transaction nx_transaction;
int nx_trans_buffers_max;
/* For now, a single semaphore for every operation */
struct rw_semaphore nx_big_sem;
@@ -283,6 +310,8 @@ struct apfs_sb_info {
struct list_head list; /* List of mounted volumes in container */
struct apfs_superblock *s_vsb_raw; /* On-disk volume sb */
dev_t s_anon_dev; /* Anonymous device for this volume-snapshot */
char *s_snap_name; /* Label for the mounted snapshot */
u64 s_snap_xid; /* Transaction id for mounted snapshot */
@@ -298,10 +327,8 @@ struct apfs_sb_info {
struct apfs_crypto_state_val *s_dflt_pfk; /* default per-file key */
struct apfs_vol_transaction s_transaction;
int s_trans_buffers_max;
struct inode *s_private_dir; /* Inode for the private directory */
struct work_struct s_orphan_cleanup_work;
};
static inline struct apfs_sb_info *APFS_SB(struct super_block *sb)
@@ -342,7 +369,7 @@ static inline struct apfs_nxsb_info *APFS_NXI(struct super_block *sb)
*/
static inline struct apfs_spaceman *APFS_SM(struct super_block *sb)
{
return &APFS_NXI(sb)->nx_spaceman;
return APFS_NXI(sb)->nx_spaceman;
}
static inline bool apfs_is_case_insensitive(struct super_block *sb)
@@ -593,20 +620,21 @@ static inline u64 apfs_cat_cnid(struct apfs_key_header *key)
}
/* Flags for the query structure */
#define APFS_QUERY_TREE_MASK 00177 /* Which b-tree we query */
#define APFS_QUERY_OMAP 00001 /* This is a b-tree object map query */
#define APFS_QUERY_CAT 00002 /* This is a catalog tree query */
#define APFS_QUERY_FREE_QUEUE 00004 /* This is a free queue query */
#define APFS_QUERY_EXTENTREF 00010 /* This is an extent reference query */
#define APFS_QUERY_FEXT 00020 /* This is a fext tree query */
#define APFS_QUERY_SNAP_META 00040 /* This is a snapshot meta query */
#define APFS_QUERY_OMAP_SNAP 00100 /* This is an omap snapshots query */
#define APFS_QUERY_NEXT 00200 /* Find next of multiple matches */
#define APFS_QUERY_EXACT 00400 /* Search for an exact match */
#define APFS_QUERY_DONE 01000 /* The search at this level is over */
#define APFS_QUERY_ANY_NAME 02000 /* Multiple search for any name */
#define APFS_QUERY_ANY_NUMBER 04000 /* Multiple search for any number */
#define APFS_QUERY_TREE_MASK 000177 /* Which b-tree we query */
#define APFS_QUERY_OMAP 000001 /* This is a b-tree object map query */
#define APFS_QUERY_CAT 000002 /* This is a catalog tree query */
#define APFS_QUERY_FREE_QUEUE 000004 /* This is a free queue query */
#define APFS_QUERY_EXTENTREF 000010 /* This is an extent reference query */
#define APFS_QUERY_FEXT 000020 /* This is a fext tree query */
#define APFS_QUERY_SNAP_META 000040 /* This is a snapshot meta query */
#define APFS_QUERY_OMAP_SNAP 000100 /* This is an omap snapshots query */
#define APFS_QUERY_NEXT 000200 /* Find next of multiple matches */
#define APFS_QUERY_EXACT 000400 /* Search for an exact match */
#define APFS_QUERY_DONE 001000 /* The search at this level is over */
#define APFS_QUERY_ANY_NAME 002000 /* Multiple search for any name */
#define APFS_QUERY_ANY_NUMBER 004000 /* Multiple search for any number */
#define APFS_QUERY_MULTIPLE (APFS_QUERY_ANY_NAME | APFS_QUERY_ANY_NUMBER)
#define APFS_QUERY_PREV 010000 /* Find previous record */
/*
* Structure used to retrieve data from an APFS B-Tree. For now only used
@@ -614,7 +642,7 @@ static inline u64 apfs_cat_cnid(struct apfs_key_header *key)
*/
struct apfs_query {
struct apfs_node *node; /* Node being searched */
struct apfs_key *key; /* What the query is looking for */
struct apfs_key key; /* What the query is looking for */
struct apfs_query *parent; /* Query for parent node */
unsigned int flags;
@@ -722,6 +750,8 @@ struct apfs_inode_info {
bool i_has_dstream; /* Is there a dstream record? */
struct apfs_dstream_info i_dstream; /* Dstream data, if any */
bool i_cleaned; /* Orphan data already deleted */
struct inode vfs_inode;
};
@@ -822,21 +852,24 @@ do { \
} while (0)
/* btree.c */
extern struct apfs_node *apfs_query_root(const struct apfs_query *query);
extern struct apfs_query *apfs_alloc_query(struct apfs_node *node,
struct apfs_query *parent);
extern void apfs_free_query(struct apfs_query *query);
extern int apfs_btree_query(struct super_block *sb, struct apfs_query **query);
extern int apfs_omap_lookup_block(struct super_block *sb, struct apfs_omap *omap,
u64 id, u64 *block, bool write);
extern int apfs_omap_lookup_block(struct super_block *sb, struct apfs_omap *omap, u64 id, u64 *block, bool write);
extern int apfs_omap_lookup_newest_block(struct super_block *sb, struct apfs_omap *omap, u64 id, u64 *block, bool write);
extern int apfs_create_omap_rec(struct super_block *sb, u64 oid, u64 bno);
extern int apfs_delete_omap_rec(struct super_block *sb, u64 oid);
extern int apfs_query_join_transaction(struct apfs_query *query);
extern int __apfs_btree_insert(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
extern int apfs_btree_insert(struct apfs_query *query, void *key, int key_len,
void *val, int val_len);
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);
@@ -884,6 +917,7 @@ extern int apfs_unlink(struct inode *dir, struct dentry *dentry);
extern int apfs_rmdir(struct inode *dir, struct dentry *dentry);
extern int apfs_delete_orphan_link(struct inode *inode);
extern int APFS_DELETE_ORPHAN_LINK_MAXOPS(void);
extern u64 apfs_any_orphan_ino(struct super_block *sb, u64 *ino_p);
/* extents.c */
extern int apfs_extent_from_query(struct apfs_query *query,
@@ -899,6 +933,7 @@ extern int apfs_get_new_block(struct inode *inode, sector_t iblock,
struct buffer_head *bh_result, int create);
extern int APFS_GET_NEW_BLOCK_MAXOPS(void);
extern int apfs_truncate(struct apfs_dstream_info *dstream, loff_t new_size);
extern int apfs_inode_delete_front(struct inode *inode);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
extern loff_t apfs_remap_file_range(struct file *src_file, loff_t off, struct file *dst_file, loff_t destoff, loff_t len, unsigned int remap_flags);
#else
@@ -916,6 +951,7 @@ extern int apfs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
extern struct inode *apfs_iget(struct super_block *sb, u64 cnid);
extern int apfs_update_inode(struct inode *inode, char *new_name);
extern int APFS_UPDATE_INODE_MAXOPS(void);
extern void apfs_orphan_cleanup_work(struct work_struct *work);
extern void apfs_evict_inode(struct inode *inode);
extern struct inode *apfs_new_inode(struct inode *dir, umode_t mode,
dev_t rdev);
@@ -990,7 +1026,7 @@ extern __printf(5, 6) void apfs_msg(struct super_block *sb, const char *prefix,
extern struct apfs_node *apfs_read_node(struct super_block *sb, u64 oid,
u32 storage, bool write);
extern void apfs_update_node(struct apfs_node *node);
extern int apfs_delete_node(struct apfs_query *query);
extern int apfs_delete_node(struct apfs_node *node, int type);
extern int apfs_node_query(struct super_block *sb, struct apfs_query *query);
extern void apfs_node_query_first(struct apfs_query *query);
extern int apfs_omap_map_from_query(struct apfs_query *query, struct apfs_omap_map *map);
@@ -998,6 +1034,7 @@ extern int apfs_node_split(struct apfs_query *query);
extern int apfs_node_locate_key(struct apfs_node *node, int index, int *off);
extern void apfs_node_free(struct apfs_node *node);
extern void apfs_node_free_range(struct apfs_node *node, u16 off, u16 len);
extern bool apfs_node_has_room(struct apfs_node *node, int length, bool replace);
extern int apfs_node_replace(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
extern int apfs_node_insert(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
extern int apfs_create_single_rec_node(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
@@ -1005,12 +1042,12 @@ extern int apfs_make_empty_btree_root(struct super_block *sb, u32 subtype, u64 *
/* object.c */
extern int apfs_obj_verify_csum(struct super_block *sb, struct buffer_head *bh);
extern void apfs_obj_set_csum(struct super_block *sb,
struct apfs_obj_phys *obj);
extern int apfs_create_cpoint_map(struct super_block *sb, u64 oid, u64 bno);
extern int apfs_remove_cpoint_map(struct super_block *sb, u64 bno);
extern struct buffer_head *apfs_read_ephemeral_object(struct super_block *sb,
u64 oid);
extern void apfs_obj_set_csum(struct super_block *sb, struct apfs_obj_phys *obj);
extern int apfs_multiblock_verify_csum(char *object, u32 size);
extern void apfs_multiblock_set_csum(char *object, u32 size);
extern int apfs_create_cpm_block(struct super_block *sb, u64 bno, struct buffer_head **bh_p);
extern int apfs_create_cpoint_map(struct super_block *sb, struct apfs_checkpoint_map_phys *cpm, struct apfs_obj_phys *obj, u64 bno, u32 size);
extern struct apfs_ephemeral_object_info *apfs_ephemeral_object_lookup(struct super_block *sb, u64 oid);
extern struct buffer_head *apfs_read_object_block(struct super_block *sb, u64 bno, bool write, bool preserve);
extern u32 apfs_index_in_data_area(struct super_block *sb, u64 bno);
extern u64 apfs_data_index_to_bno(struct super_block *sb, u32 index);
@@ -1034,8 +1071,8 @@ extern int apfs_read_catalog(struct super_block *sb, bool write);
extern int apfs_sync_fs(struct super_block *sb, int wait);
/* transaction.c */
extern void apfs_cpoint_data_allocate(struct super_block *sb, u64 *bno);
extern int apfs_cpoint_data_free(struct super_block *sb, u64 bno);
extern void apfs_transaction_init(struct apfs_nx_transaction *trans);
extern int apfs_transaction_start(struct super_block *sb, struct apfs_max_ops maxops);
extern int apfs_transaction_commit(struct super_block *sb);
extern void apfs_inode_join_transaction(struct super_block *sb, struct inode *inode);
@@ -1091,6 +1128,18 @@ extern const struct inode_operations apfs_symlink_inode_operations;
/* xattr.c */
extern const struct xattr_handler *apfs_xattr_handlers[];
/**
* apfs_assert_query_is_valid - Assert that all of a query's ancestors are set
* @query: the query to check
*
* A query may lose some of its ancestors during a node split, but nothing
* should be done to such a query until it gets refreshed.
*/
static inline void apfs_assert_query_is_valid(const struct apfs_query *query)
{
ASSERT(apfs_node_is_root(apfs_query_root(query)));
}
/*
* TODO: the following are modified variants of buffer head functions that will
* work with the shared block device for the container. The correct approach
@@ -1118,7 +1167,11 @@ apfs_getblk(struct super_block *sb, sector_t block)
{
struct buffer_head *bh;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
bh = __getblk_gfp(APFS_NXI(sb)->nx_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
#else
bh = bdev_getblk(APFS_NXI(sb)->nx_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
#endif
if (bh)
set_buffer_uptodate(bh);
return bh;
+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
+409 -210
View File
File diff suppressed because it is too large. Load diff
+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);
+105 -43
View File
@@ -99,24 +99,25 @@ static struct apfs_query *apfs_dentry_lookup(struct inode *dir,
{
struct super_block *sb = dir->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
u64 cnid = apfs_ino(dir);
bool hashed = apfs_is_normalization_insensitive(sb);
int err;
apfs_init_drec_key(sb, cnid, child->name, child->len, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return ERR_PTR(-ENOMEM);
query->key = &key;
apfs_init_drec_key(sb, cnid, child->name, child->len, &query->key);
/*
* Distinct filenames in the same directory may (rarely) share the same
* hash. The query code cannot handle that because their order in the
* b-tree would depend on their unnormalized original names. Just get
* hash. The query code cannot handle that because their order in the
* b-tree would depend on their unnormalized original names. Just get
* all the candidates and check them one by one.
*
* This is very wasteful for normalization-sensitive filesystems: there
* are no hashes so we just check every single file in the directory for
* no reason. This would be easy to avoid but does it matter? (TODO)
*/
query->flags |= APFS_QUERY_CAT | APFS_QUERY_ANY_NAME | APFS_QUERY_EXACT;
do {
@@ -128,6 +129,14 @@ static struct apfs_query *apfs_dentry_lookup(struct inode *dir,
goto fail;
} while (unlikely(apfs_filename_cmp(sb, child->name, child->len, drec->name, drec->name_len)));
/*
* We may need to refresh the query later, but the refresh code doesn't
* know how to deal with hash collisions. Instead set the key to the
* unnormalized name and pretend that this was never a multiple query
* in the first place.
*/
query->key.name = drec->name;
query->flags &= ~(APFS_QUERY_MULTIPLE | APFS_QUERY_DONE | APFS_QUERY_NEXT);
return query;
fail:
@@ -173,7 +182,6 @@ static int apfs_readdir(struct file *file, struct dir_context *ctx)
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_key key;
struct apfs_query *query;
u64 cnid = apfs_ino(inode);
loff_t pos;
@@ -193,8 +201,7 @@ static int apfs_readdir(struct file *file, struct dir_context *ctx)
}
/* We want all the children for the cnid, regardless of the name */
apfs_init_drec_key(sb, cnid, NULL /* name */, 0 /* name_len */, &key);
query->key = &key;
apfs_init_drec_key(sb, cnid, NULL /* name */, 0 /* name_len */, &query->key);
query->flags = APFS_QUERY_CAT | APFS_QUERY_MULTIPLE | APFS_QUERY_EXACT;
pos = ctx->pos - 2;
@@ -361,7 +368,6 @@ static int apfs_create_dentry_rec(struct inode *inode, struct qstr *qname,
{
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
void *raw_key = NULL;
struct apfs_drec_val *raw_val = NULL;
@@ -369,21 +375,20 @@ static int apfs_create_dentry_rec(struct inode *inode, struct qstr *qname,
bool hashed = apfs_is_normalization_insensitive(sb);
int ret;
apfs_init_drec_key(sb, parent_id, qname->name, qname->len, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_drec_key(sb, parent_id, qname->name, qname->len, &query->key);
query->flags |= APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
if (ret && ret != -ENODATA) {
apfs_err(sb, "query failed in dir 0x%llx (hash 0x%llx)", parent_id, key.number);
apfs_err(sb, "query failed in dir 0x%llx (hash 0x%llx)", parent_id, query->key.number);
goto fail;
}
if (hashed)
key_len = apfs_build_dentry_hashed_key(qname, key.number, parent_id,
key_len = apfs_build_dentry_hashed_key(qname, query->key.number, parent_id,
(struct apfs_drec_hashed_key **)&raw_key);
else
key_len = apfs_build_dentry_unhashed_key(qname, parent_id,
@@ -401,7 +406,7 @@ static int apfs_create_dentry_rec(struct inode *inode, struct qstr *qname,
/* TODO: deal with hash collisions */
ret = apfs_btree_insert(query, raw_key, key_len, raw_val, val_len);
if (ret)
apfs_err(sb, "insertion failed in dir 0x%llx (hash 0x%llx)", parent_id, key.number);
apfs_err(sb, "insertion failed in dir 0x%llx (hash 0x%llx)", parent_id, query->key.number);
fail:
kfree(raw_val);
@@ -453,18 +458,16 @@ static int apfs_create_sibling_link_rec(struct dentry *dentry,
{
struct super_block *sb = dentry->d_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query = NULL;
struct apfs_sibling_link_key raw_key;
struct apfs_sibling_val *raw_val;
int val_len;
int ret;
apfs_init_sibling_link_key(apfs_ino(inode), sibling_id, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_sibling_link_key(apfs_ino(inode), sibling_id, &query->key);
query->flags |= APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
@@ -503,17 +506,15 @@ static int apfs_create_sibling_map_rec(struct dentry *dentry,
{
struct super_block *sb = dentry->d_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query = NULL;
struct apfs_sibling_map_key raw_key;
struct apfs_sibling_map_val raw_val;
int ret;
apfs_init_sibling_map_key(sibling_id, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_sibling_map_key(sibling_id, &query->key);
query->flags |= APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
@@ -603,8 +604,10 @@ static int apfs_create_dentry(struct dentry *dentry, struct inode *inode)
/* Now update the parent inode */
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
parent->i_mtime = parent->i_ctime = current_time(inode);
#else
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
parent->i_mtime = inode_set_ctime_current(parent);
#else
inode_set_mtime_to_ts(parent, inode_set_ctime_current(parent));
#endif
++APFS_I(parent)->i_nchildren;
apfs_inode_join_transaction(parent->i_sb, parent);
@@ -923,17 +926,15 @@ static int apfs_delete_sibling_link_rec(struct dentry *dentry, u64 sibling_id)
struct super_block *sb = dentry->d_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct inode *inode = d_inode(dentry);
struct apfs_key key;
struct apfs_query *query = NULL;
int ret;
ASSERT(sibling_id);
apfs_init_sibling_link_key(apfs_ino(inode), sibling_id, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_sibling_link_key(apfs_ino(inode), sibling_id, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -966,17 +967,15 @@ static int apfs_delete_sibling_map_rec(struct dentry *dentry, u64 sibling_id)
{
struct super_block *sb = dentry->d_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query = NULL;
int ret;
ASSERT(sibling_id);
apfs_init_sibling_map_key(sibling_id, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_sibling_map_key(sibling_id, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -1040,8 +1039,10 @@ static int apfs_delete_dentry(struct dentry *dentry)
/* Now update the parent inode */
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
parent->i_mtime = parent->i_ctime = current_time(parent);
#else
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
parent->i_mtime = inode_set_ctime_current(parent);
#else
inode_set_mtime_to_ts(parent, inode_set_ctime_current(parent));
#endif
--APFS_I(parent)->i_nchildren;
apfs_inode_join_transaction(sb, parent);
@@ -1119,18 +1120,14 @@ static int apfs_find_primary_link(struct inode *inode, char **name, u64 *parent)
{
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
int err;
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
apfs_init_sibling_link_key(apfs_ino(inode), 0 /* sibling_id */, &key);
query->key = &key;
query->flags |= APFS_QUERY_CAT | APFS_QUERY_ANY_NUMBER |
APFS_QUERY_EXACT;
apfs_init_sibling_link_key(apfs_ino(inode), 0 /* sibling_id */, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_ANY_NUMBER | APFS_QUERY_EXACT;
/* The primary link is the one with the lowest sibling id */
*name = NULL;
@@ -1161,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;
@@ -1177,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;
}
@@ -1196,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 */);
@@ -1208,8 +1205,10 @@ static int apfs_create_orphan_link(struct inode *inode)
/* Now update the child count for private-dir */
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
priv_dir->i_mtime = priv_dir->i_ctime = current_time(priv_dir);
#else
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
priv_dir->i_mtime = inode_set_ctime_current(priv_dir);
#else
inode_set_mtime_to_ts(priv_dir, inode_set_ctime_current(priv_dir));
#endif
++APFS_I(priv_dir)->i_nchildren;
apfs_inode_join_transaction(sb, priv_dir);
@@ -1237,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;
@@ -1257,8 +1256,10 @@ int apfs_delete_orphan_link(struct inode *inode)
/* Now update the child count for private-dir */
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
priv_dir->i_mtime = priv_dir->i_ctime = current_time(priv_dir);
#else
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
priv_dir->i_mtime = inode_set_ctime_current(priv_dir);
#else
inode_set_mtime_to_ts(priv_dir, inode_set_ctime_current(priv_dir));
#endif
--APFS_I(priv_dir)->i_nchildren;
apfs_inode_join_transaction(sb, priv_dir);
@@ -1480,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;
}
+2 -1
View File
@@ -1,7 +1,8 @@
PACKAGE_NAME="linux-apfs-rw"
PACKAGE_VERSION="0.3.5"
PACKAGE_VERSION="0.3.10"
BUILT_MODULE_NAME[0]="apfs"
DEST_MODULE_LOCATION[0]="/extra"
AUTOINSTALL="yes"
PRE_BUILD="genver.sh"
+148 -29
View File
@@ -139,7 +139,7 @@ static int apfs_extent_read(struct apfs_dstream_info *dstream, sector_t dsblock,
ret = -ENOMEM;
goto done;
}
query->key = &key;
query->key = key;
query->flags = apfs_is_sealed(sb) ? APFS_QUERY_FEXT : APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
@@ -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;
}
/**
@@ -413,7 +418,6 @@ static int apfs_update_tail_extent(struct apfs_dstream_info *dstream, const stru
{
struct super_block *sb = dstream->ds_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_file_extent_key raw_key;
struct apfs_file_extent_val raw_val;
@@ -431,13 +435,11 @@ static int apfs_update_tail_extent(struct apfs_dstream_info *dstream, const stru
new_crypto = 0;
raw_val.crypto_id = cpu_to_le64(new_crypto);
/* We want the last extent record */
apfs_init_file_extent_key(extent_id, -1, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
/* We want the last extent record */
apfs_init_file_extent_key(extent_id, -1, &query->key);
query->flags = APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
@@ -614,7 +616,7 @@ search_and_insert:
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
query->key = key;
query->flags = APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
@@ -801,7 +803,6 @@ static int apfs_insert_phys_extent(struct apfs_dstream_info *dstream, const stru
struct super_block *sb = dstream->ds_sb;
struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
struct apfs_node *extref_root;
struct apfs_key key;
struct apfs_query *query = NULL;
struct apfs_phys_extent pext;
u64 blkcnt = extent->len >> sb->s_blocksize_bits;
@@ -830,8 +831,7 @@ static int apfs_insert_phys_extent(struct apfs_dstream_info *dstream, const stru
* one.
*/
last_bno = extent->phys_block_num + blkcnt - 1;
apfs_init_extent_key(last_bno, &key);
query->key = &key;
apfs_init_extent_key(last_bno, &query->key);
query->flags = APFS_QUERY_EXTENTREF;
ret = apfs_btree_query(sb, &query);
@@ -1153,7 +1153,6 @@ static int apfs_create_hole(struct apfs_dstream_info *dstream, u64 start, u64 en
{
struct super_block *sb = dstream->ds_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_file_extent_key raw_key;
struct apfs_file_extent_val raw_val;
@@ -1173,11 +1172,10 @@ static int apfs_create_hole(struct apfs_dstream_info *dstream, u64 start, u64 en
raw_val.phys_block_num = cpu_to_le64(0); /* It's a hole... */
raw_val.crypto_id = cpu_to_le64(apfs_vol_is_encrypted(sb) ? extent_id : 0);
apfs_init_file_extent_key(extent_id, start, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_file_extent_key(extent_id, start, &query->key);
query->flags = APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
@@ -1260,7 +1258,6 @@ static int apfs_range_in_snap(struct super_block *sb, u64 bno, u64 blkcnt, bool
{
struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
struct apfs_node *extref_root = NULL;
struct apfs_key key;
struct apfs_query *query = NULL;
struct apfs_phys_extent pext = {0};
int ret;
@@ -1287,8 +1284,7 @@ static int apfs_range_in_snap(struct super_block *sb, u64 bno, u64 blkcnt, bool
goto out;
}
apfs_init_extent_key(bno, &key);
query->key = &key;
apfs_init_extent_key(bno, &query->key);
query->flags = APFS_QUERY_EXTENTREF;
ret = apfs_btree_query(sb, &query);
@@ -1500,18 +1496,15 @@ static int apfs_shrink_dstream_last_extent(struct apfs_dstream_info *dstream, lo
{
struct super_block *sb = dstream->ds_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_file_extent tail;
u64 extent_id = dstream->ds_id;
int ret = 0;
apfs_init_file_extent_key(extent_id, -1, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_file_extent_key(extent_id, -1, &query->key);
query->flags = APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
@@ -1637,6 +1630,125 @@ int apfs_truncate(struct apfs_dstream_info *dstream, loff_t new_size)
return apfs_create_hole(dstream, old_blks, new_blks);
}
/**
* apfs_dstream_delete_front - Deletes as many leading extents as possible
* @sb: filesystem superblock
* @ds_id: id for the dstream to delete
*
* Returns 0 on success, or a negative error code in case of failure, which may
* be -ENODATA if there are no more extents, or -EAGAIN if the free queue is
* getting too full.
*/
static int apfs_dstream_delete_front(struct super_block *sb, u64 ds_id)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_spaceman *sm = APFS_SM(sb);
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
struct apfs_spaceman_free_queue *fq = NULL;
struct apfs_query *query = NULL;
struct apfs_file_extent head;
bool first_match = true;
int ret;
fq = &sm_raw->sm_fq[APFS_SFQ_MAIN];
if (le64_to_cpu(fq->sfq_count) > APFS_TRANS_MAIN_QUEUE_MAX)
return -EAGAIN;
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
apfs_init_file_extent_key(ds_id, 0, &query->key);
query->flags = APFS_QUERY_CAT;
next_extent:
ret = apfs_btree_query(sb, &query);
if (ret && ret != -ENODATA) {
apfs_err(sb, "query failed for first extent of id 0x%llx", ds_id);
goto out;
}
apfs_query_direct_forward(query);
if (!apfs_query_found_extent(query)) {
/*
* After the original lookup, the query may not be set to the
* first extent, but instead to the record that comes right
* before.
*/
if (first_match) {
first_match = false;
goto next_extent;
}
ret = -ENODATA;
goto out;
}
first_match = false;
ret = apfs_extent_from_query(query, &head);
if (ret) {
apfs_err(sb, "bad head extent record on dstream 0x%llx", ds_id);
goto out;
}
ret = apfs_btree_remove(query);
if (ret) {
apfs_err(sb, "removal failed for id 0x%llx, addr 0x%llx", ds_id, head.logical_addr);
goto out;
}
/*
* The official fsck doesn't complain about wrong sparse byte counts
* for orphans, so I guess we don't need to update them here
*/
if (!apfs_ext_is_hole(&head)) {
ret = apfs_range_put_reference(sb, head.phys_block_num, head.len);
if (ret) {
apfs_err(sb, "failed to put range 0x%llx-0x%llx", head.phys_block_num, head.len);
goto out;
}
ret = apfs_crypto_adj_refcnt(sb, head.crypto_id, -1);
if (ret) {
apfs_err(sb, "failed to take crypto id 0x%llx", head.crypto_id);
goto out;
}
}
if (le64_to_cpu(fq->sfq_count) <= APFS_TRANS_MAIN_QUEUE_MAX)
goto next_extent;
ret = -EAGAIN;
out:
apfs_free_query(query);
return ret;
}
/**
* apfs_inode_delete_front - Deletes as many leading extents as possible
* @inode: inode to delete
*
* Tries to delete all extents for @inode, in which case it returns 0. If the
* free queue is getting too full, deletes as much as is reasonable and returns
* -EAGAIN. May return other negative error codes as well.
*/
int apfs_inode_delete_front(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct apfs_dstream_info *dstream = NULL;
struct apfs_inode_info *ai = APFS_I(inode);
int ret;
if (!ai->i_has_dstream)
return 0;
dstream = &ai->i_dstream;
ret = apfs_flush_extent_cache(dstream);
if (ret) {
apfs_err(sb, "extent cache flush failed for dstream 0x%llx", dstream->ds_id);
return ret;
}
ret = apfs_dstream_delete_front(sb, dstream->ds_id);
if (ret == -ENODATA)
return 0;
return ret;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
loff_t apfs_remap_file_range(struct file *src_file, loff_t off, struct file *dst_file, loff_t destoff, loff_t len, unsigned int remap_flags)
#else
@@ -1706,8 +1818,10 @@ int apfs_clone_file_range(struct file *src_file, loff_t off, struct file *dst_fi
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
dst_inode->i_mtime = dst_inode->i_ctime = current_time(dst_inode);
#else
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
dst_inode->i_mtime = inode_set_ctime_current(dst_inode);
#else
inode_set_mtime_to_ts(dst_inode, inode_set_ctime_current(dst_inode));
#endif
dst_inode->i_size = src_inode->i_size;
dst_ai->i_key_class = src_ai->i_key_class;
@@ -1760,18 +1874,15 @@ fail:
static int apfs_extent_create_record(struct super_block *sb, u64 dstream_id, struct apfs_file_extent *extent)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query = NULL;
struct apfs_file_extent_val raw_val;
struct apfs_file_extent_key raw_key;
int ret = 0;
apfs_init_file_extent_key(dstream_id, extent->logical_addr, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_file_extent_key(dstream_id, extent->logical_addr, &query->key);
query->flags = APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -1835,7 +1946,7 @@ restart:
ret = -ENOMEM;
goto out;
}
query->key = &key;
query->key = key;
query->flags = APFS_QUERY_EXTENTREF;
ret = apfs_btree_query(sb, &query);
@@ -1975,7 +2086,7 @@ restart:
ret = -ENOMEM;
goto out;
}
query->key = &key;
query->key = key;
query->flags = APFS_QUERY_EXTENTREF;
ret = apfs_btree_query(sb, &query);
@@ -2186,7 +2297,11 @@ int apfs_nonsparse_dstream_read(struct apfs_dstream_info *dstream, void *buf, si
goto out;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
bhs[idx] = __getblk_gfp(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
#else
bhs[idx] = bdev_getblk(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
#endif
if (!bhs[idx]) {
apfs_err(sb, "failed to map block 0x%llx", bno);
ret = -EIO;
@@ -2258,7 +2373,11 @@ void apfs_nonsparse_dstream_preread(struct apfs_dstream_info *dstream)
if (ret || bno == 0)
return;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
bh = __getblk_gfp(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
#else
bh = bdev_getblk(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
#endif
if (!bh)
return;
if (!buffer_uptodate(bh)) {
+28 -2
View File
@@ -5,6 +5,9 @@
#include "apfs.h"
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
#include <linux/splice.h>
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 17, 0)
typedef int vm_fault_t;
#endif
@@ -18,6 +21,9 @@ static vm_fault_t apfs_page_mkwrite(struct vm_fault *vmf)
struct vm_area_struct *vma = vmf->vma;
#endif
struct page *page = vmf->page;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
struct folio *folio;
#endif
struct inode *inode = file_inode(vma->vm_file);
struct super_block *sb = inode->i_sb;
struct buffer_head *bh, *head;
@@ -54,8 +60,16 @@ static vm_fault_t apfs_page_mkwrite(struct vm_fault *vmf)
goto out_unlock;
}
if (!page_has_buffers(page))
if (!page_has_buffers(page)) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
create_empty_buffers(page, sb->s_blocksize, 0);
#else
folio = page_folio(page);
bh = folio_buffers(folio);
if (!bh)
bh = create_empty_buffers(folio, sb->s_blocksize, 0);
#endif
}
size = i_size_read(inode);
if (page->index == size >> PAGE_SHIFT)
@@ -141,6 +155,16 @@ int apfs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
return apfs_sync_fs(sb, true /* wait */);
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
static ssize_t apfs_copy_file_range(struct file *src_file, loff_t src_off,
struct file *dst_file, loff_t dst_off,
size_t len, unsigned int flags)
{
return (splice_copy_file_range(src_file, src_off,
dst_file, dst_off, len));
}
#endif
const struct file_operations apfs_file_operations = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
@@ -150,7 +174,9 @@ const struct file_operations apfs_file_operations = {
.fsync = apfs_fsync,
.unlocked_ioctl = apfs_file_ioctl,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
.copy_file_range = apfs_copy_file_range,
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
.copy_file_range = generic_copy_file_range,
#endif
Executable
+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
#
# Script to generate the module version header
#
if command -v git >/dev/null 2>&1 && [ -d .git ]; then
GIT_COMMIT=$(git describe HEAD | tail -c 9)
else
GIT_COMMIT="$(grep PACKAGE_VERSION dkms.conf | cut -d '"' -f2)?"
fi
printf '#define GIT_COMMIT\t"%s"\n' "$GIT_COMMIT" > version.h
+258 -62
View File
File diff suppressed because it is too large. Load diff
+477 -310
View File
File diff suppressed because it is too large. Load diff
+99 -198
View File
@@ -37,131 +37,129 @@ static u64 apfs_fletcher64(void *addr, size_t len)
int apfs_obj_verify_csum(struct super_block *sb, struct buffer_head *bh)
{
struct apfs_obj_phys *obj = (struct apfs_obj_phys *)bh->b_data;
/* The checksum may be stale until the transaction is committed */
if (buffer_trans(bh))
return 1;
return apfs_multiblock_verify_csum(bh->b_data, sb->s_blocksize);
}
return (le64_to_cpu(obj->o_cksum) ==
apfs_fletcher64((char *) obj + APFS_MAX_CKSUM_SIZE,
sb->s_blocksize - APFS_MAX_CKSUM_SIZE));
/**
* apfs_multiblock_verify_csum - Verify an object's checksum
* @object: the object to verify
* @size: size of the object in bytes (may be multiple blocks)
*
* Returns 1 on success, 0 on failure.
*/
int apfs_multiblock_verify_csum(char *object, u32 size)
{
struct apfs_obj_phys *obj = (struct apfs_obj_phys *)object;
u64 actual_csum, header_csum;
header_csum = le64_to_cpu(obj->o_cksum);
actual_csum = apfs_fletcher64(object + APFS_MAX_CKSUM_SIZE, size - APFS_MAX_CKSUM_SIZE);
return header_csum == actual_csum;
}
/**
* apfs_obj_set_csum - Set the fletcher checksum in an object header
* @sb: superblock structure
* @obj: the object header
*
* The object must have a length of a single block.
*/
void apfs_obj_set_csum(struct super_block *sb, struct apfs_obj_phys *obj)
{
u64 cksum = apfs_fletcher64((char *)obj + APFS_MAX_CKSUM_SIZE,
sb->s_blocksize - APFS_MAX_CKSUM_SIZE);
apfs_multiblock_set_csum((char *)obj, sb->s_blocksize);
}
/**
* apfs_multiblock_set_csum - Set an object's checksum
* @object: the object to checksum
* @size: size of the object in bytes (may be multiple blocks)
*/
void apfs_multiblock_set_csum(char *object, u32 size)
{
struct apfs_obj_phys *obj = (struct apfs_obj_phys *)object;
u64 cksum;
cksum = apfs_fletcher64(object + APFS_MAX_CKSUM_SIZE, size - APFS_MAX_CKSUM_SIZE);
obj->o_cksum = cpu_to_le64(cksum);
}
/**
* apfs_cpm_lookup_oid - Search a checkpoint-mapping block for a given oid
* @sb: superblock structure
* @cpm: checkpoint-mapping block (on disk)
* @oid: the ephemeral object id to look up
* @bno: on return, the block number for the object
*
* Returns -EFSCORRUPTED in case of corruption, or -EAGAIN if @oid is not
* listed in @cpm; returns 0 on success.
*/
static int apfs_cpm_lookup_oid(struct super_block *sb,
struct apfs_checkpoint_map_phys *cpm,
u64 oid, u64 *bno)
{
u32 map_count = le32_to_cpu(cpm->cpm_count);
int i;
if (map_count > apfs_max_maps_per_block(sb)) {
apfs_err(sb, "block has too many maps (%d)", map_count);
return -EFSCORRUPTED;
}
for (i = 0; i < map_count; ++i) {
struct apfs_checkpoint_mapping *map = &cpm->cpm_map[i];
if (le64_to_cpu(map->cpm_oid) == oid) {
*bno = le64_to_cpu(map->cpm_paddr);
return 0;
}
}
return -EAGAIN; /* The mapping may still be in the next block */
}
/**
* apfs_read_cpm_block - Read the checkpoint mapping block
* @sb: super block structure
*
* Only a single cpm block is supported for now. Returns the buffer head for
* the block on success, or NULL in case of failure.
*/
static struct buffer_head *apfs_read_cpm_block(struct super_block *sb)
{
struct apfs_nx_superblock *raw_sb = APFS_NXI(sb)->nx_raw;
u64 desc_base = le64_to_cpu(raw_sb->nx_xp_desc_base);
u32 desc_index = le32_to_cpu(raw_sb->nx_xp_desc_index);
u32 desc_blks = le32_to_cpu(raw_sb->nx_xp_desc_blocks);
u32 desc_len = le32_to_cpu(raw_sb->nx_xp_desc_len);
u64 cpm_bno;
if (!desc_blks || desc_len < 2)
return NULL;
/* Last block in area is superblock; we want the last mapping block */
cpm_bno = desc_base + (desc_index + desc_len - 2) % desc_blks;
return apfs_sb_bread(sb, cpm_bno);
}
/**
* apfs_create_cpoint_map - Create a checkpoint mapping
* apfs_create_cpm_block - Create a new checkpoint-mapping block
* @sb: filesystem superblock
* @oid: ephemeral object id
* @bno: block number
* @bno: block number to use
* @bh_p: on return, the buffer head for the block
*
* Only mappings for free queue nodes are supported for now. 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.
*/
int apfs_create_cpoint_map(struct super_block *sb, u64 oid, u64 bno)
int apfs_create_cpm_block(struct super_block *sb, u64 bno, struct buffer_head **bh_p)
{
struct buffer_head *bh;
struct apfs_checkpoint_map_phys *cpm;
struct apfs_checkpoint_mapping *map;
u32 cpm_count;
int err = 0;
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_checkpoint_map_phys *cpm = NULL;
struct buffer_head *bh = NULL;
int err;
bh = apfs_read_cpm_block(sb);
if (!bh)
bh = apfs_getblk(sb, bno);
if (!bh) {
apfs_err(sb, "failed to map cpm block");
return -EIO;
cpm = (struct apfs_checkpoint_map_phys *)bh->b_data;
}
err = apfs_transaction_join(sb, bh);
if (err) {
brelse(bh);
return err;
}
set_buffer_csum(bh);
cpm = (void *)bh->b_data;
memset(cpm, 0, sb->s_blocksize);
cpm->cpm_o.o_oid = cpu_to_le64(bno);
cpm->cpm_o.o_xid = cpu_to_le64(nxi->nx_xid);
cpm->cpm_o.o_type = cpu_to_le32(APFS_OBJ_PHYSICAL | APFS_OBJECT_TYPE_CHECKPOINT_MAP);
cpm->cpm_o.o_subtype = cpu_to_le32(APFS_OBJECT_TYPE_INVALID);
/* For now: the caller will have to update these fields */
cpm->cpm_flags = cpu_to_le32(APFS_CHECKPOINT_MAP_LAST);
cpm->cpm_count = 0;
*bh_p = bh;
return 0;
}
/**
* apfs_create_cpoint_map - Create a checkpoint mapping for an object
* @sb: filesystem superblock
* @cpm: checkpoint mapping block to use
* @obj: header for the ephemeral object
* @bno: block number for the ephemeral object
* @size: size of the ephemeral object in bytes
*
* Returns 0 on success or a negative error code in case of failure, which may
* be -ENOSPC if @cpm is full.
*/
int apfs_create_cpoint_map(struct super_block *sb, struct apfs_checkpoint_map_phys *cpm, struct apfs_obj_phys *obj, u64 bno, u32 size)
{
struct apfs_checkpoint_mapping *map = NULL;
u32 cpm_count;
apfs_assert_in_transaction(sb, &cpm->cpm_o);
cpm_count = le32_to_cpu(cpm->cpm_count);
if (cpm_count >= apfs_max_maps_per_block(sb)) { /* TODO */
apfs_warn(sb, "creation of cpm blocks not yet supported");
err = -EOPNOTSUPP;
goto fail;
}
if (cpm_count >= apfs_max_maps_per_block(sb))
return -ENOSPC;
map = &cpm->cpm_map[cpm_count];
le32_add_cpu(&cpm->cpm_count, 1);
map->cpm_type = cpu_to_le32(APFS_OBJ_EPHEMERAL |
APFS_OBJECT_TYPE_BTREE_NODE);
map->cpm_subtype = cpu_to_le32(APFS_OBJECT_TYPE_SPACEMAN_FREE_QUEUE);
map->cpm_size = cpu_to_le32(sb->s_blocksize);
map->cpm_type = obj->o_type;
map->cpm_subtype = obj->o_subtype;
map->cpm_size = cpu_to_le32(size);
map->cpm_pad = 0;
map->cpm_fs_oid = 0;
map->cpm_oid = cpu_to_le64(oid);
map->cpm_oid = obj->o_oid;
map->cpm_paddr = cpu_to_le64(bno);
fail:
brelse(bh);
return err;
return 0;
}
/**
@@ -197,120 +195,23 @@ u64 apfs_data_index_to_bno(struct super_block *sb, u32 index)
}
/**
* apfs_remove_cpoint_map - Remove a checkpoint mapping
* @sb: filesystem superblock
* @bno: block number to delete
*
* Only mappings for free queue nodes are supported for now. Blocks that come
* after the deleted one are assumed to shift back one place. Returns 0 on
* success or a negative error code in case of failure.
*/
int apfs_remove_cpoint_map(struct super_block *sb, u64 bno)
{
struct buffer_head *bh;
struct apfs_checkpoint_map_phys *cpm;
struct apfs_checkpoint_mapping *map, *maps_start, *maps_end;
struct apfs_checkpoint_mapping *bno_map = NULL;
u32 cpm_count;
u32 bno_off;
int err = 0;
bh = apfs_read_cpm_block(sb);
if (!bh) {
apfs_err(sb, "failed to read cpm block");
return -EIO;
}
cpm = (struct apfs_checkpoint_map_phys *)bh->b_data;
apfs_assert_in_transaction(sb, &cpm->cpm_o);
/* TODO: multiple cpm blocks? */
cpm_count = le32_to_cpu(cpm->cpm_count);
if (cpm_count > apfs_max_maps_per_block(sb)) {
apfs_err(sb, "block has too many maps (%d)", cpm_count);
err = -EFSCORRUPTED;
goto fail;
}
maps_start = &cpm->cpm_map[0];
maps_end = &cpm->cpm_map[cpm_count];
bno_off = apfs_index_in_data_area(sb, bno);
for (map = maps_start; map < maps_end; ++map) {
u32 curr_off;
if (le64_to_cpu(map->cpm_paddr) == bno)
bno_map = map;
curr_off = apfs_index_in_data_area(sb, le64_to_cpu(map->cpm_paddr));
if (curr_off > bno_off)
map->cpm_paddr = cpu_to_le64(apfs_data_index_to_bno(sb, curr_off - 1));
}
if (!bno_map) {
apfs_err(sb, "no mapping for bno 0x%llx", bno);
err = -EFSCORRUPTED;
goto fail;
}
memmove(bno_map, bno_map + 1, (maps_end - bno_map - 1) * sizeof(*bno_map));
le32_add_cpu(&cpm->cpm_count, -1);
fail:
brelse(bh);
return err;
}
/**
* apfs_read_ephemeral_object - Find and map an ephemeral object
* apfs_ephemeral_object_lookup - Find an ephemeral object info in memory
* @sb: superblock structure
* @oid: ephemeral object id
*
* Returns the mapped buffer head for the object, or an error pointer in case
* Returns a pointer to the object info on success, or an error pointer in case
* of failure.
*/
struct buffer_head *apfs_read_ephemeral_object(struct super_block *sb, u64 oid)
struct apfs_ephemeral_object_info *apfs_ephemeral_object_lookup(struct super_block *sb, u64 oid)
{
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_nx_superblock *raw_sb = nxi->nx_raw;
u64 desc_base = le64_to_cpu(raw_sb->nx_xp_desc_base);
u32 desc_index = le32_to_cpu(raw_sb->nx_xp_desc_index);
u32 desc_blks = le32_to_cpu(raw_sb->nx_xp_desc_blocks);
u32 desc_len = le32_to_cpu(raw_sb->nx_xp_desc_len);
u32 i;
struct apfs_ephemeral_object_info *list = NULL;
int i;
if (!desc_blks || !desc_len) {
apfs_err(sb, "no blocks in checkpoint descriptor area");
return ERR_PTR(-EFSCORRUPTED);
}
/* Last block in the area is superblock; the rest are mapping blocks */
for (i = 0; i < desc_len - 1; ++i) {
struct buffer_head *bh;
struct apfs_checkpoint_map_phys *cpm;
u64 cpm_bno = desc_base + (desc_index + i) % desc_blks;
u64 obj_bno;
int err;
bh = apfs_sb_bread(sb, cpm_bno);
if (!bh) {
apfs_err(sb, "failed to read cpm block");
return ERR_PTR(-EIO);
}
cpm = (struct apfs_checkpoint_map_phys *)bh->b_data;
err = apfs_cpm_lookup_oid(sb, cpm, oid, &obj_bno);
brelse(bh);
cpm = NULL;
if (err == -EAGAIN) /* Search the next mapping block */
continue;
if (err) {
apfs_err(sb, "cpm lookup failed for oid 0x%llx", oid);
return ERR_PTR(err);
}
bh = apfs_sb_bread(sb, obj_bno);
if (!bh) {
apfs_err(sb, "failed to read ephemeral block");
return ERR_PTR(-EIO);
}
return bh;
list = nxi->nx_eph_list;
for (i = 0; i < nxi->nx_eph_count; ++i) {
if (list[i].oid == oid)
return &list[i];
}
apfs_err(sb, "no mapping for oid 0x%llx", oid);
return ERR_PTR(-EFSCORRUPTED);
+77 -23
View File
@@ -71,7 +71,6 @@ static int apfs_create_snap_metadata_rec(struct inode *mntpoint, struct apfs_nod
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_superblock *vsb_raw = sbi->s_vsb_raw;
struct apfs_query *query = NULL;
struct apfs_key key = {0};
struct apfs_snap_metadata_key raw_key;
struct apfs_snap_metadata_val *raw_val = NULL;
int val_len;
@@ -79,14 +78,12 @@ static int apfs_create_snap_metadata_rec(struct inode *mntpoint, struct apfs_nod
u64 xid = APFS_NXI(sb)->nx_xid;
int err;
apfs_init_snap_metadata_key(xid, &key);
query = apfs_alloc_query(snap_root, NULL /* parent */);
if (!query) {
err = -ENOMEM;
goto fail;
}
query->key = &key;
apfs_init_snap_metadata_key(xid, &query->key);
query->flags |= APFS_QUERY_SNAP_META | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
@@ -137,27 +134,24 @@ static int apfs_create_snap_name_rec(struct apfs_node *snap_root, const char *na
{
struct super_block *sb = snap_root->object.sb;
struct apfs_query *query = NULL;
struct apfs_key key = {0};
struct apfs_snap_name_key *raw_key = NULL;
struct apfs_snap_name_val raw_val;
int key_len;
int err;
apfs_init_snap_name_key(name, &key);
query = apfs_alloc_query(snap_root, NULL /* parent */);
if (!query) {
err = -ENOMEM;
goto fail;
}
query->key = &key;
apfs_init_snap_name_key(name, &query->key);
query->flags |= APFS_QUERY_SNAP_META | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
if (err == 0) {
/* TODO: avoid transaction abort here */
apfs_info(sb, "a snapshot with that name already exists");
err = -EEXIST;
/* This should never happen here, the caller has checked */
apfs_alert(sb, "snapshot name record already exists");
err = -EFSCORRUPTED;
goto fail;
}
if (err != -ENODATA) {
@@ -246,7 +240,6 @@ static int apfs_update_omap_snap_tree(struct super_block *sb, __le64 *oid_p)
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_node *root = NULL;
u64 oid = le64_to_cpup(oid_p);
struct apfs_key key = {0};
struct apfs_query *query = NULL;
__le64 raw_key;
struct apfs_omap_snapshot raw_val = {0};
@@ -268,14 +261,12 @@ static int apfs_update_omap_snap_tree(struct super_block *sb, __le64 *oid_p)
}
oid = 0;
apfs_init_omap_snap_key(nxi->nx_xid, &key);
query = apfs_alloc_query(root, NULL /* parent */);
if (!query) {
err = -ENOMEM;
goto fail;
}
query->key = &key;
apfs_init_omap_snap_key(nxi->nx_xid, &query->key);
query->flags = APFS_QUERY_OMAP_SNAP | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
@@ -341,6 +332,56 @@ static int apfs_update_omap_snapshots(struct super_block *sb)
return err;
}
/**
* apfs_snapshot_name_check - Check if a snapshot with the given name exists
* @sb: filesystem superblock
* @name: name to check
* @name_len: length of @name
* @eexist: on return, whether the name exists or not
*
* Returns 0 on success, or a negative error code in case of failure.
*/
static int apfs_snapshot_name_check(struct super_block *sb, const char *name, int name_len, bool *eexist)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_superblock *vsb_raw = sbi->s_vsb_raw;
struct apfs_node *snap_root = NULL;
struct apfs_query *query = NULL;
int err;
snap_root = apfs_read_node(sb, le64_to_cpu(vsb_raw->apfs_snap_meta_tree_oid), APFS_OBJ_PHYSICAL, false /* write */);
if (IS_ERR(snap_root)) {
apfs_err(sb, "failed to read snap meta root 0x%llx", le64_to_cpu(vsb_raw->apfs_snap_meta_tree_oid));
return PTR_ERR(snap_root);
}
vsb_raw = NULL;
query = apfs_alloc_query(snap_root, NULL /* parent */);
if (!query) {
err = -ENOMEM;
goto out;
}
apfs_init_snap_name_key(name, &query->key);
query->flags |= APFS_QUERY_SNAP_META | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
if (err == 0) {
*eexist = true;
} else if (err == -ENODATA) {
*eexist = false;
err = 0;
} else {
apfs_err(sb, "query failed (%s)", name);
}
out:
apfs_free_query(query);
query = NULL;
apfs_node_free(snap_root);
snap_root = NULL;
return err;
}
/**
* apfs_do_ioc_takesnapshot - Actual work for apfs_ioc_take_snapshot()
* @mntpoint: inode of the mount point to snapshot
@@ -357,12 +398,31 @@ static int apfs_do_ioc_take_snapshot(struct inode *mntpoint, const char *name, i
/* TODO: remember to update the maxops in the future */
struct apfs_max_ops maxops = {0};
u64 sblock_oid;
bool eexist = false;
int err;
err = apfs_transaction_start(sb, maxops);
if (err)
return err;
/*
* This check can fail in normal operation, so run it before making any
* changes and exit the transaction cleanly if necessary. The name
* lookup will have to be repeated later, but it's ok because I don't
* expect snapshot creation to be a bottleneck for anyone.
*/
err = apfs_snapshot_name_check(sb, name, name_len, &eexist);
if (err) {
apfs_err(sb, "snapshot name existence check failed");
goto fail;
}
if (eexist) {
err = apfs_transaction_commit(sb);
if (err)
goto fail;
return -EEXIST;
}
/*
* Flush the extent caches to the extenref tree before it gets moved to
* the snapshot. It seems safer in general to avoid big unpredictable
@@ -499,15 +559,12 @@ static int apfs_snapshot_name_to_xid(struct apfs_node *snap_root, const char *na
{
struct super_block *sb = snap_root->object.sb;
struct apfs_query *query = NULL;
struct apfs_key key = {0};
int err;
apfs_init_snap_name_key(name, &key);
query = apfs_alloc_query(snap_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_snap_name_key(name, &query->key);
query->flags |= APFS_QUERY_SNAP_META | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
@@ -545,15 +602,12 @@ static int apfs_snapshot_xid_to_sblock(struct apfs_node *snap_root, u64 xid, u64
{
struct super_block *sb = snap_root->object.sb;
struct apfs_query *query = NULL;
struct apfs_key key = {0};
int err;
apfs_init_snap_metadata_key(xid, &key);
query = apfs_alloc_query(snap_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_snap_metadata_key(xid, &query->key);
query->flags |= APFS_QUERY_SNAP_META | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
+289 -117
View File
File diff suppressed because it is too large. Load diff
+210 -97
View File
File diff suppressed because it is too large. Load diff
+505 -497
View File
File diff suppressed because it is too large. Load diff
+5 -18
View File
@@ -195,18 +195,16 @@ int apfs_xattr_get_compressed_data(struct inode *inode, const char *name, struct
{
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_xattr xattr;
u64 cnid = apfs_ino(inode);
int ret;
apfs_init_xattr_key(cnid, name, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_xattr_key(cnid, name, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -330,18 +328,15 @@ int ____apfs_xattr_get(struct inode *inode, const char *name, void *buffer,
{
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_xattr xattr;
u64 cnid = apfs_ino(inode);
int ret;
apfs_init_xattr_key(cnid, name, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_xattr_key(cnid, name, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -461,16 +456,13 @@ static int apfs_delete_any_xattr(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
int ret;
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
apfs_init_xattr_key(apfs_ino(inode), NULL /* name */, &key);
query->key = &key;
apfs_init_xattr_key(apfs_ino(inode), NULL /* name */, &query->key);
query->flags = APFS_QUERY_CAT | APFS_QUERY_ANY_NAME | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -715,7 +707,6 @@ int apfs_xattr_set(struct inode *inode, const char *name, const void *value,
{
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query = NULL;
u64 cnid = apfs_ino(inode);
int key_len, val_len;
@@ -733,14 +724,12 @@ int apfs_xattr_set(struct inode *inode, const char *name, const void *value,
}
}
apfs_init_xattr_key(cnid, name, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query) {
ret = -ENOMEM;
goto done;
}
query->key = &key;
apfs_init_xattr_key(cnid, name, &query->key);
query->flags = APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -874,7 +863,6 @@ ssize_t apfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_key key;
struct apfs_query *query;
u64 cnid = apfs_ino(inode);
size_t free = size;
@@ -889,8 +877,7 @@ ssize_t apfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
}
/* We want all the xattrs for the cnid, regardless of the name */
apfs_init_xattr_key(cnid, NULL /* name */, &key);
query->key = &key;
apfs_init_xattr_key(cnid, NULL /* name */, &query->key);
query->flags = APFS_QUERY_CAT | APFS_QUERY_MULTIPLE | APFS_QUERY_EXACT;
while (1) {