57 Commits
Author SHA1 Message Date
Ernesto A. Fernández 61d8e5b7c4 0.3.13
This release adds support for the upcoming 6.14 upstream kernel, and for
a handful of RHEL 9 kernels that have been broken for a while.

Some improvements have been made to ENOSPC behaviour, but the work here
isn't quite finished yet.

Read-only support is added for fusion drives. Writes will probably never
get implemented, though I might reconsider this if people actually ask
for the feature.

Read-only mounts of volumes with so-called "dataless snapshots" are also
tentatively allowed. I have no idea what this is about but there should
be no risk.

Free space as reported by statfs is now much more accurate. A few other
bugs were also fixed, but nothing big enough to deserve notice.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-03-21 22:30:49 -03:00
Ernesto A. Fernández 129ef5a7e8 Go read-only if we can't read ephemeral objects
Failing to read an ephemeral object does not abort the transaction, it
just returns an error. The problem is that, by this point, nx_eph_list
is already set: the next time a transaction starts the reads will get
skipped, and things will proceed as if there was no error.

I got this series of messages from xfstests (no idea which test, but
clearly one that fakes I/O errors):

  [18920.212351] Buffer I/O error on dev dm-0, logical block 3932144, async page read
  [18920.515341] APFS (dm-0:0): failed to read cpm block (apfs_read_single_cpm_block:231)
  [18920.520378] APFS (dm-0:0): failed to read cpm block 0 (apfs_read_ephemeral_objects:300)
  [18920.524371] APFS (dm-0:0): failed to read the ephemeral objects (apfs_transaction_start:384)
  [18920.529524] APFS (dm-0:0): no mapping for oid 0x400 (apfs_ephemeral_object_lookup:216)
  [18920.532784] APFS (dm-0:0): no spaceman object for oid 0x400 (apfs_read_spaceman:744)
  [18920.536222] APFS (dm-0:0): failed to read the spaceman (apfs_transaction_start:395)
  [18920.538634] APFS (dm-0:0): aborting transaction (apfs_transaction_abort:1001)
  [18920.646469] APFS (dm-0:0): no mapping for oid 0x400 (apfs_ephemeral_object_lookup:216)
  [18920.653922] APFS (dm-0:0): no spaceman object for oid 0x400 (apfs_spaceman_get_free_blkcnt:1468)
  [18920.766489] APFS (dm-0:0): no mapping for oid 0x400 (apfs_ephemeral_object_lookup:216)
  [18920.770446] APFS (dm-0:0): no spaceman object for oid 0x400 (apfs_spaceman_get_free_blkcnt:1468)

No harm was done because the next transaction still got aborted as soon
as we tried to read the spaceman. But, if at some point in the future we
stop fully flushing free queues on each start, we could run into more
subtle bugs involving missing fq nodes.

This all seems unlikely but, to be safe, make the mount read-only when
we fail to get the ephemeral objects. It's really the same situation as
any other abort, so it's not a problem.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-03-19 20:45:23 -03:00
Ernesto A. Fernández c012e6cdb9 Commit when the main free queue has too many nodes
As mentioned in the previous commit message, generic/269 of xfstests
makes the main free queue unbalanced enough that insertions fail.
Clearing the free queue when the transaction starts is not enough to
prevent it, because the whole disaster can happen without a single
commit.

So, start forcing commits when the main queue has too many nodes. I
don't think there is a problem with being too strict here, because a
truly full main queue would be gigantic for most normal filesystems.
Just force a commit when we reach half the limit (rounded up). Of course
make an exception for filesystems with a single main fq node, since
those can't even become unbalanced in the first place and we don't want
to commit nonstop.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-03-18 18:38:13 -03:00
Ernesto A. Fernández 3682c034b0 Fully flush free queues when a transaction starts
Like so many times before, I'm running out of space in the main free
queue. This time the reproducer is generic/269 of xfstests. The queue
isn't really that full, the problem is that it's very unbalanced. We
don't really have any mechanism in place to rebalance btrees, so the
easiest way forward is to just empty the free queue entirely whenever
possible, that is, when a transaction starts.

Of course this means that I no longer hold on to blocks from old
transactions, but I never had any real reason to do that. Also, there's
no longer any point in retrying fq insertions that fail, since it's just
not possible to safely flush any other blocks.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-03-18 16:29:06 -03:00
Ernesto A. Fernández 265f356558 Define macros for transaction space requirements
The previous patch reused the number "20" from
apfs_transaction_has_room() inside apfs_main_free(). The next commits
might need to tune that value, so it's better to just define a shared
macro for both.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-03-17 16:36:43 -03:00
Ernesto A. Fernández 434a595e71 Don't keep retrying orphan cleanups on error
Running generic/269 of xfstests quickly gets orphan cleanups to hit
ENOSPC, which prints an error message. The problem is that this doesn't
happen just once, but over and over again, completely flooding dmesg for
no good reason.

Retrying cleanups after ENOSPC does have some value, but wait until we
actually made some room. For other errors it's just a bad idea, since
something has gone seriously wrong; we'll usually just get EROFS anyway,
but just don't retry at all.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-03-14 23:08:24 -03:00
Ernesto A. Fernández bb291218a4 Log an error if we hit ENOSPC during a transaction
The intention of my ongoing ENOSPC work is that we should always catch
that error during transaction start (so that we don't need to abort),
but so far xfstests is still beating my checks somehow. That's a bug
though, so start logging an error message to make it easier to spot.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-03-13 17:38:49 -03:00
Ernesto A. Fernández 3509b5ed35 Report free space correctly on statfs
The statfs code was one of the first things I implemented (most likely
before I could even mount the filesystem in any meaningful sense), and I
never tested it much. It was bound to have bugs.

Now that we handle ENOSPC properly (or so I hope), I'm starting to try
my luck with the enospc group from xfstests. The first issue I've
noticed is that generic/275 complains that it "could not sufficiently
fill filesystem". The container is actually very much full, but statfs
reports used space incorrectly: it adds up the blocks allocated by each
volume, neglecting all the blocks that don't belong to any of them.

At the time I didn't know anything about the space manager, so I guess
this seemed like a good enough solution. Fix it now.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-03-11 21:13:09 -03:00
Ernesto A. Fernández 3174e363ad Simplify ENOSPC checks before transactions
If a function throws ENOSPC halfway through an operation, the
transaction will get aborted so that the filesystem isn't left in an
inconsistent state. This means that the mount will go read-only, and
that the user will lose any uncommitted changes.

So, it's better if we detect ENOSPC before starting an operation. I
tried this before with commit a52b73ed97 ("Check free space before
starting a transaction"), but the code was messy and it would have
required constant updates, which I neglected of course. The problem was
that I wanted to be as accurate as possible with my calculations of
required space, but I'm not sure there is any point.

Instead, require a large fixed amount of space (512 KiB) for most
operations, and a much smaller but still almost certainly sufficient
amount of space (80 KiB) for deletions. Hopefully this will be enough
for users to maneuver around ENOSPC situations. It's not perfect, but no
CoW filesystem can be truly perfect in this regard.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-03-11 16:15:41 -03:00
Ernesto A. Fernández afe9bac8f2 Actually add support for RHEL 9
The build is still broken for redhat kernels:

  https://github.com/linux-apfs/linux-apfs-rw/issues/89#issuecomment-2691955523

Fix the remaining issues. For the previous patch I was just looking at
the source, but this time I set up a rhel vm and tried a build and a
mount for each of the 9.x kernels, so hopefully it's over. I didn't know
you could get rhel for free as a developer, I guess it makes sense.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-03-03 19:43:17 -03:00
Ernesto A. Fernández 412c4c8f9c Add support for RHEL 9
The build is broken for redhat kernels:

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

This seems to have been the case for more than a year, but nobody had
reported it so far, so I guess the driver isn't getting much use over
there.

We had encountered a similar problem before in commit 1a0b9fb8af ("Fix
build for RHEL 9 kernels"), but back then I thought it was just a
one-off thing. I was wrong: redhat kernels often backport patches that
break internal apis. I don't know how other out-of-tree drivers handle
this, but for now I will have to rely on user reports.

So, go through all RHEL 9 kernels one by one, find out which api changes
were applied to each, and add the corresponding macro version checks to
the driver.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-02-28 19:45:33 -03:00
Ernesto A. Fernández 6a24fdcb2e Update readme with fusion drive support
Fusion drives now have read-only support, so update the readme.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-02-14 18:48:47 -03:00
Ernesto A. Fernández 21acd228b4 Check uuid of fusion drive devices
If the user mixes up the main and tier 2 devices, the mount fails with
confusing errors. We can do better here: the fusion uuid has a bit to
tell which is the main device, so check that early and fail with a good
explanation. While we are at it, also add checks to make sure what we
are mounting is indeed a fusion drive.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-02-13 20:34:45 -03:00
Ernesto A. Fernández c39f8a5a8f Log an error message if a mount option is invalid
While testing the new "tier2" mount option, I realized that the driver
gives no feedback to the user when a mount option is invalid. I have my
doubts about logging an error here because, while the apfs driver is
inserted, the message may show up for unrelated mounts as well. I think
this sort of issues are unavoidable though, and XFS does log a warning
for invalid options, so do the same thing.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-02-13 16:46:34 -03:00
Ernesto A. Fernández 9921ef9205 Implement reads for fusion drives
So far I haven't been able to figure out fusion drives to an extent that
would allow me to implement writes. I'm tired of it, and I think the
effort is probably pointless because apfs fusion drives aren't all that
popular. I might reconsider if people request the feature, but for now
just add read-only support, which is almost trivial.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-02-12 18:27:02 -03:00
Ernesto A. Fernández 3604300b2b Allocate block device info independently of nxi
Make the block device info field of the container superblock into a
pointer. This will make it easier to tell if we are working with a
fusion drive.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-02-10 19:57:40 -03:00
Ernesto A. Fernández 98f07c1262 Keep track of device mode for older kernels
For kernels before 6.5, blkdev_put() needs to be told the mode of the
block device. This information is stored in the vfs superblock, but I
can't access that from apfs_free_main_super() because that function may
be needed early on mount.

My somewhat hacky solution so far has been to guess the mode from the
setting of the APFS_READWRITE flag. This isn't accurate because the mode
is actually determined by SB_RDONLY, but it still seems to work - I
really haven't checked what happens to the mode inside blkdev_put().

For fusion drives, I will soon need to reuse apfs_blkdev_cleanup()
inside apfs_attach_nxi(). In this case the mode is available, so working
with the different definitions of it for each kernel version would make
a huge mess.

Since we now have apfs_blkdev_info for portablity, just keep the mode in
there and let apfs_blkdev_cleanup() retrieve it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-02-10 15:46:19 -03:00
Ernesto A. Fernández 65b2bc9342 Prepare block device code for reuse
Put all information about the container's block device into a single
struct, and move all related code into wrapper functions that work with
that struct. Hopefully this will make it easier to add a second block
device for the tier 2 of a fusion drive.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-02-07 21:37:07 -03:00
Ernesto A. Fernández 55e30b926c Never pick the blockdev directly for buffer heads
When obtaining a buffer head, always let some wrapper specify the block
device. This is just a cosmetic thing for now, but it will be important
when dealing with fusion drives.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-02-07 17:15:47 -03:00
Ernesto A. Fernández e16e7f4f8a Update apfs_dentry_revalidate() arguments for 6.14
The build is broken for the 6.14 release candidate:

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

The problem is that ->d_revalidate() now takes two more arguments.
Update our implementation accordingly.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-02-06 16:09:14 -03:00
Ernesto A. Fernández 7a3457bdd0 Update header comment for apfs_query
The header comment from apfs_query says that it only gets used for the
catalog and the object map. That claim is extremely out of date, of
course. Get rid of it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-12-04 22:39:44 -03:00
Ernesto A. Fernández 6fcc69c05d Don't refer to record values as "data"
The paper I followed in the early days of the driver (before the
official reference was published) referred to record values as "data",
so some of my oldest code uses that name as well. This is inconsistent,
and not very clear because the word "data" shows up everywhere. Reword
all instances of this I can identify.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-12-04 21:04:23 -03:00
Ernesto A. Fernández 82324fb015 Make record key size checks more strict
For safety, I think it's always better for a filesystem to reject any
malformed data it can notice. Omap and free queue keys have a fixed
size, so the size check we run to prevent out-of-bounds reads, while
correct, should be made more strict. This is what we already do for most
record types these days, both keys and values.

Take this opportunity to improve the code style here and make it more
consistent.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-12-04 18:28:20 -03:00
Ernesto A. Fernández 41f133bff6 Fix header comment for put/take_single_extent()
There is no "paddr" argument to either apfs_take_single_extent() or
apfs_put_single_extent(). Fix this.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-12-04 16:16:53 -03:00
Ernesto A. Fernández 0fa8eaa812 Properly explain EEXIST during snapshot creation
Provide a proper error message on dmesg when a user attempts to create a
snapshot with a name that's already in use, otherwise "File exists" may
be a bit confusing.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-12-03 21:07:20 -03:00
Ernesto A. Fernández f78e6c876b Allow read-only mounts of dataless snapshots
A user has encountered a volume with the dataless snapshots flag:

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

I'm not really sure what "dataless snapshots" are, and so far I have
failed to trigger them under macos. I can set the
SNAP_META_PENDING_DATALESS flag in a snapshot myself, and macos will
call it "dataless", but their fsck doesn't complain no matter what I do
with the other fields, so I can't tell what is expected.

I can't imagine any risk in mounting such a volume read-only, so allow
that much from now on. Keep writes banned just in case, and avoid
mounting the dataless snapshots themselves, since I have no idea what
that entails.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-12-03 20:33:18 -03:00
Ernesto A. Fernández 7f9ae821f2 Improve the superblock id reported in the logs
While testing eda21f33e9 ("Check the superblock magic before the block
size"), I noticed that early log messages such as those don't include
device information, just the filesystem type. The problem is that the
s_id field of the vfs superblock is only getting set after the call to
apfs_read_main_super(); there is no real reason for this, so invert it.

While paying attention to this field for the first time, I realized that
I'm making a huge mess of it. Most filesystems use a "%pg" format
specifier: this prints the name of the block_device pointer passed as an
argument, so the logs are very clear. Meanwhile I was just printing the
dev_t... and putting a "g" afterwards? I guess I somehow got confused
while trying to copy the others. Do this right.

That said, an apfs superblock is not uniquely identified by its device:
we have volumes and snapshots as well. The comments in the super_block
structure claim that s_id is just an "Informational name", so I guess I
can set it to whatever I find useful. Provide the volume number and
snapshot name (if any) as well.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-11-10 11:56:05 -03:00
Ernesto A. Fernández 1643ae2688 Get rid of the rst note from the last patch
Github doesn't support rst notes, so it just looks silly over there.
Make it into a regular paragraph.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-11-08 20:35:46 -03:00
Ernesto A. Fernández 7e6babba01 Mention the distro packages in the readme
Some users seem unaware of the existence of packages for the driver, and
I guess that's my fault because the (outdated) readme doesn't mention
them at all. Add a small note about it in the beginning of the "build"
section.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-11-08 20:19:31 -03:00
Ernesto A. Fernández c1cae76fb3 Rework the second paragraph of the readme
These days I'm far more careful about testing multiple kernel versions,
so the warning in the second paragraph of the readme ("only a few of
those have actually been tested") is a bit excessive. Simplify that
stuff. Don't bother mentioning the support for 4.9-4.11 kernels, it has
some performance issues and they are old enough that it probably doesn't
matter much.

The rest of the paragraph is about bug reporting. Move this into a
separate section, so that it's easier to find, and to give me room to
explain the use of dmesg.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-11-08 20:02:16 -03:00
Ernesto A. Fernández 240702b9f6 Tone down corruption warnings in the readme
The driver is not that bad these days. Corruption is still a real risk
of course, but when it happens it's a bug related to some untested
corner case (like the recent issue with filesystems above 1.32 TiB), not
expected behaviour. So tone down the warnings, but make it clear that
it's not all that safe yet.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-11-08 18:26:05 -03:00
Ernesto A. Fernández d9e50e76cf Allow choice of mountable writes by default
A distro packager has requested writable mounts by default, and is
willing to take responsibility for any potential harm to their users:

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

Add a build flag to enable this, but leave it undocumented to avoid
confusing anyone.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-11-08 17:20:47 -03:00
Ernesto A. Fernández eda21f33e9 Check the superblock magic before the block size
When an attempt is made to mount something other than an apfs container,
the driver currently throws a confusing "bad blocksize" error to dmesg.
I would expect this to be encountered fairly often, when users try to
mount the disk as a whole instead of the apfs partition.

Start checking the superblock magic before the block size so that we can
give a more helpful error message.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-11-07 21:14:43 -03:00
Ernesto A. Fernández 5a2db9bd68 0.3.12
This release fixes a serious data corruption bug for containers bigger
than ~1.32 TiB, which is immediately triggered by a writable mount. This
goes to show why mounts are still read-only by default.

Another much smaller data loss bug for much bigger containers (~7 TiB)
is also addressed.

Support is added for the 6.12 kernel release candidate. A bit early this
time, so let's hope the build doesn't break again for the final release.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-11-01 20:06:39 -03:00
Ernesto A. Fernández 96e6d41bb7 Silence gcc warnings due to switch to min()
Building an old kernel (5.3) with an old version of gcc (4.9.2), I get
some warnings about "comparison of distinct pointer types". I'm pretty
sure this doesn't matter, and I don't even know if the blame lies with
the old compiler or the old kernel, but just silence this by making the
integer literals of the warning unsigned like the other argument.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-10-31 17:01:30 -03:00
Woody Suwalski 6c2ce34a67 Fix whitespace damage in apfs_aops definition
This has been around for a while so I guess checkpatch isn't picking up
on it for some reason.

[eafer: took this from a bigger patch, added commit message]
Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-10-31 16:46:20 -03:00
Ernesto A. Fernández a34220c93d Switch ->write_begin() and ->write_end() to folios
The build is broken for the 6.12 release candidate. The problem is that
->write_begin() and ->write_end() now take folios as parameters instead
of pages. The same is true, of course, of the associated
__block_write_begin() and generic_write_end(). Update the driver.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-10-31 16:02:06 -03:00
Ernesto A. Fernández 49507c65bc Implement ip bitmap rotation correctly
I never fully understood how the ip bitmap list was supposed to work,
but now I'm noticing that the difference between what I do and what the
official driver does are bad enough to quickly corrupt the list when a
container moves back and forth. Of course this is only an issue for
filesystems above 1.32 TiB or so, but it's still pretty serious.

So, I thought about it harder and I'm pretty sure I finally figured it
out: the array entries for each free bitmap are actually kept in a
linked list, that starts in sm_ip_bm_free_head and ends in
sm_ip_bm_free_tail. All entries outside the list (that is, the entries
for the used blocks) are marked with the invalid index 0xFFFF. The tail
itself also gets 0xFFFF even though it's free, because it has no next
entry - so that accounts for the constant off-by-one weirdness in the
number of free blocks.

So, implement all this stuff correctly. Stop assuming that all ip
bitmaps in use are consecutive, and stop rotating them all in lockstep
as soon as the transaction begins. Instead, read all ip bitmaps into
memory during the first transaction and keep them there, only tracking
if they have been dirtied. At commit time, allocate new blocks for the
dirty bitmaps and write them back to disk.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-10-29 21:13:46 -03:00
Ernesto A. Fernández dfdafd2a07 Wrap ephemeral objects around if there is no room
Currently, the driver doesn't allow multiblock ephemeral objects to wrap
around. When an object doesn't fit in the checkpoint data area, we just
leave some empty space and adjust the mappings accordingly. This seemed
like a simple and reasonable solution to me, but the official driver
actually demands that all ephemeral objects are kept consecutive (and in
the same order as their mappings, which we already do).

In practice this means that if you are working with a container with a
multiblock spaceman (say 7T in size), and if the unmount transaction
just so happens to be the one where the spaceman doesn't fit, then you
will be left with a container that is seen as corrupt by the official
driver. To make matters worse, checkpoint integrity is checked on mount,
so macos will actually mount the container no problem, but silently
discarding my final transaction.

This is all, of course, extremely unlikely. Such large containers are
rare, and checkpoint data areas for them are huge so a wrap-around takes
a while to happen. And it needs to happen precisely during the unmount
transaction, and that same transaction has to involve important data the
user doesn't want to lose. All in all it's not a serious issue, but get
it fixed.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-10-22 17:34:51 -03:00
Integral 2920180ed6 Remove unused macro MIN()/MAX() & replace them with min()/max() (#78) 2024-10-04 16:04:05 -03:00
Ernesto A. Fernández 0667dbff17 0.3.11
This release prevents deadlock if a single volume gets mounted and
unmounted at the same time. It also fixes two other (smaller) mount bugs
found while testing this one.

Support is added for the upcoming 6.11 kernel release, with a patch by
Woody Suwalski and a later patch of my own because things broke a second
time.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-09-12 20:20:49 -03:00
Ernesto A. Fernández af2ee526c0 Avoid double parsing of mount options
Originally all mount options were parsed inside apfs_fill_super(),
because I was following the example of other filesystems (such as ext2)
that have no subvolumes or snapshots. When I implemented those features,
I realized that some mount options would be needed early on mount, so
that sget() could use them to decide if the superblock is already
mounted.

My lazy solution at the time was to leave parse_options() as it was, but
also add some redundant code early on mount to read the needed options.
Just looking at the amount of TODOs in those functions makes it clear
that this was always very silly. And now that the locking during mount
has been simplified, this is causing a race, and I want to take that
stuff seriously.

The bug is that while parse_options() rereads the mount options needed
by sget(), there is nothing preventing another concurrent mount from
testing the superblock. Say the first mount is for volume 1: the volume
number gets briefly reset to the default value of zero, and if the other
mount actually wants volume 0, sget() will consider this a match.

Anyway, split parse_options() into two equally clean functions, one for
early mount and another to get called within apfs_fill_super(). I even
considered simply parsing all the options early during mount, but that
would require a few more changes because the nxi is not attached at that
point. Maybe some other time.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-09-11 16:54:47 -03:00
Ernesto A. Fernández 44f4b4f631 Don't redefine MAX() and MIN() for the 6.11 kernel
The build has become broken once again for the 6.11 kernel:

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

The problem is that the second release candidate introduced generic
MAX() and MIN() macros for everyone to use, and that crashes with our
own definitions. So, only define them for older kernels.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-08-30 20:25:44 -03:00
Ernesto A. Fernández b44d2a37bf Don't forget to initialize the omap cache spinlock
The omap cache has been in place for years, and it seems that its
spinlock was never actually initialized before use. I have no idea what
the consequences of this are, but for one it does cause lockdep to turn
itself off after every mount. Fix it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-08-15 16:01:07 -03:00
Ernesto A. Fernández 4881d7c707 Don't return 0 on mount if sget() fails
For the previous patch I had to carefully test all the possible errors
during mount and unmount. I noticed that injecting an error inside
apfs_set_super() triggers npd. The cause is that the error code returned
by sget() gets swallowed and the kernel is allowed to think that the
mount succeeded.

This is not a new issue, it's been around since apfs_mount() was first
implemented. I never noticed because apfs_set_super() would only fail if
we ran out of anonymous devices. So fix it in a new commit.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-08-15 16:01:07 -03:00
Ernesto A. Fernández 2982c8655f Don't use the mount mutex so much
Repeatedly running generic/604 of xfstests eventually hangs the kernel.
This is a test that forces a mount to race with an unmount. The problem
is a standard deadlock: ->put_super() gets called while the s_umount
lock is held, and then starts a transaction, which takes nxs_mutex;
meanwhile ->mount() first takes nxs_mutex and then calls sget(), which
takes s_umount.

Before this patch, I had never really put much thought as to where
exactly nxs_mutex is needed. It's obviously the lock for the list of
mounted containers, but it's also used as a generic way to avoid
modifications to a container while another of its subvolumes is getting
mounted. It's always been obvious that I'm holding it for too long and
in too many places, so I want to finally put some effort into figuring
this out now, and leave a record here so that I don't forget.

The first obvious point is that the mutex can be dropped before calling
sget(), and retaken later. There should be no problem if another mount
called it at the same time, and it's enough to fix the bug. But putting
some thought here exposes an existing issue with this code: sget() may
actually test some superblock that is already half-unmounted and had its
info freed, so apfs_test_super() is risking npd or use-after-free.

Looking at other filesystems I realize that my mistake was freeing the
info inside ->put_super(), because at that point the super is still
listed in the fs_supers list for apfs. Instead ->kill_sb() should free
the info itself, after generic_shutdown_super() is done (which calls
->put_super()), and after kill_super_notify() has taken care of the
list. Since kill_super_notify() can't be called from inside a driver, a
little hack is needed to call it through kill_anon_super(). The cleanups
for apfs_mount() also need to be reconsidered, since ->put_super() is
only called by deactivate_locked_super() after the root dentry is set,
but the rest of ->kill_sb() runs either way.

Going back to the mutex: even if a race for sget() itself is not an
issue, dropping it there does have other consequences. The reference
count for the container superblock is increased before sget(), and it's
later checked on some initializations to see if this is the first mount.
That won't do anymore, because a second mount could increase the refcnt
before the mutex is retaken, and the first mount would never complete
its work. Use other fields for this check: in apfs_read_main_super() use
the blocksize (which is zero before the first mount sets it); for
apfs_set_nx_flags(), define a new flag to tell if the other flags have
been set.

This already tells us two functions that definitely need to be protected
by the mutex, so that they can complete their initializations before any
other mount comes to check them. The situation is similar for
apfs_first_read_omap(), which goes through the container's volume list
to check for previous reads. After going line by line through the mount
code, I'm fairly confident that the mutex only needs to be held for
apfs_attach_nxi() and these three functions. For unmount it's the same:
apfs_free_main_super() and apfs_put_omap() require the mutex. The second
will now unset the omap pointer for the volume atomically, so that it
can be checked by apfs_first_read_omap(); it is renamed accordingly.

So far this is all about the races between different mounts and umounts;
but we also don't want the mount to read inconsistent data from disk
because some existing mount happened to write to it. Back when the
driver could only mount one volume at a time, apfs_fill_super() didn't
need any locking, so when multiple mounts got implemented I decided to
just ban transactions during mounts entirely, by taking the nxs_mutex.
This double lock looks very awkward and is a factor on the deadlock we
are fixing, so let's clean that up now: use the read semaphore itself to
protect apfs_fill_super(). Of course this means that two mounts for the
same container can now run through this function at the same time, so be
careful with changes to the shared nxi: we've already dealt with this
issue for apfs_set_nx_flags(), but apfs_set_trans_buffer_limit() might
also be unsafe. Just in case, move it to apfs_read_main_super(), it
makes more sense that way anyway.

By the way, apfs_read_main_super() also reads from disk, but it only
does it on the first mount, so there is no risk of racing against a
transaction. There is one function that writes without a transaction,
namely apfs_make_super_copy(), but that function takes the nxs_mutex to
ensure that no mount will begin during the superblock update.

I think that covers everything? I'm really not sure if this writeup was
clear at all, I have a hard time reasoning about locks. I also put a
simpler list of the uses of nxs_mutex in a comment next to its
declaration, which should be more practical as a reference.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-08-15 16:01:06 -03:00
Ernesto A. Fernández 707d91b486 Undo code reordering in previous patch
I just picked up a patch to add support for the 6.11 kernel. I only did
a shallow review, and now I notice that the code was reordered and a
null buffer head is getting dereferenced. Revert that.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-07-30 17:55:14 -03:00
Woody Suwalski 3e5c902a9a Kernel 6.11: use folio for page ops (#74) 2024-07-30 17:23:17 -03:00
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
21 changed files with 1670 additions and 971 deletions

No files matched your search

+5
View File
@@ -13,6 +13,11 @@ apfs-y := btree.o compress.o dir.o extents.o file.o inode.o key.o libzbitmap.o \
lzfse/lzvn_decode_base.o message.o namei.o node.o object.o snapshot.o \
spaceman.o super.o symlink.o transaction.o unicode.o xattr.o xfield.o
# If you want mounts to be writable by default, run the build as:
# make CONFIG=-DCONFIG_APFS_RW_ALWAYS
# This is risky and not generally recommended.
ccflags-y += $(CONFIG)
default:
./genver.sh
make -C $(KERNEL_DIR) M=$(PWD)
+26 -12
View File
@@ -4,13 +4,8 @@ Apple File System for Linux
The Apple File System (APFS) is the copy-on-write filesystem currently used on
all Apple devices. This module provides a degree of experimental support on
Linux.
It's supposed to work with a range of kernel versions starting at 4.9 or before,
but only a few of those have actually been tested. Also, kernel versions below
4.12 will be slower for some operations. If you run into any problem, please
send a report to <linux-apfs@googlegroups.com> or file a github issue at
https://github.com/eafer/linux-apfs-rw/issues.
Linux. It's intended to work with all kernel versions since 4.12, but testing
is focused on 5.18 and above.
To help test write support, a set of userland tools is also under development.
The git tree can be retrieved from https://github.com/eafer/apfsprogs.git.
@@ -19,15 +14,32 @@ Known limitations
=================
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.
If you make use of the write support, there is a real risk of data corruption,
so mounts are read-only by default. Please report any issues that you find.
Encryption is not yet implemented even in read-only mode, and neither are
fusion drives.
Writes to fusion drives will likely never be supported, though I might
reconsider this if people actually need it. Encryption is not yet implemented
even in read-only mode.
Reporting bugs
==============
If you encounter any problem, the first thing you should do is run (as root)::
dmesg | grep -i apfs
to see all the error messages. If that doesn't help you, please file a github
issue at https://github.com/eafer/linux-apfs-rw/issues. Or send me an email if
you prefer.
Build
=====
Note that at this point the driver has been packaged for several distros, so
it may be more convenient to install it from there instead of building it
yourself. The distro packages are often outdated though, so be sure to check
the version.
In order to build a module out-of-tree, you will first need the Linux kernel
headers. Depending on your distro, you can get them by running (as root)::
@@ -65,12 +77,14 @@ vol=n Volume number to mount. The default is volume 0.
snap=label Volume snapshot to mount (in read-only mode).
tier2=path Path to the tier 2 device. For fusion drives only.
uid=n, gid=n Override on-disk inode ownership data with given uid/gid.
cknodes Verify the checksum on all metadata nodes. Right now this has a
severe performance cost, so it's not recommended.
readwrite Enable the experimental write support. This **will** corrupt your
readwrite Enable the experimental write support. This may corrupt your
container.
============ =================================================================
+160 -54
View File
@@ -11,6 +11,21 @@
#include <linux/list.h>
#include <linux/types.h>
#include <linux/version.h>
/*
* Redhat kernels regularly break the api, so they need their own version
* checks. Make sure they always fail for non-rhel kernels.
*
* By the way, if rhel keeps picking up breaking patches for old majors after
* a new one is out, this check could break down and things could get more
* complicated. I don't think they do that though...
*/
#ifdef RHEL_RELEASE
#define RHEL_VERSION_GE(a, b) (RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(a, b))
#else
#define RHEL_VERSION_GE(a, b) 0
#endif
#include "apfs_raw.h"
#define EFSBADCRC EBADMSG /* Bad CRC detected */
@@ -29,10 +44,10 @@ static inline bool sb_rdonly(const struct super_block *sb) { return sb->s_flags
#endif
/* Compatibility wrapper around submit_bh() */
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 0, 0)
#define apfs_submit_bh(op, op_flags, bh) submit_bh(op, op_flags, bh)
#else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) || RHEL_VERSION_GE(9, 2)
#define apfs_submit_bh(op, op_flags, bh) submit_bh(op | op_flags, bh)
#else
#define apfs_submit_bh(op, op_flags, bh) submit_bh(op, op_flags, bh)
#endif
/*
@@ -81,7 +96,7 @@ struct apfs_node {
int key; /* Offset of the key area in the block */
int free; /* Offset of the free area in the block */
int data; /* Offset of the data area in the block */
int val; /* Offset of the value area in the block */
int key_free_list_len; /* Length of the fragmented free key space */
int val_free_list_len; /* Length of the fragmented free value space */
@@ -117,6 +132,11 @@ static inline bool apfs_node_has_fixed_kv_size(struct apfs_node *node)
return (node->flags & APFS_BTNODE_FIXED_KV_SIZE) != 0;
}
struct apfs_ip_bitmap_block_info {
bool dirty; /* Do we need to commit this block to disk? */
void *block; /* In-memory address of the bitmap block */
};
/*
* Space manager data in memory.
*/
@@ -132,6 +152,7 @@ struct apfs_spaceman {
u32 sm_cib_count; /* Number of chunk-info blocks */
u64 sm_free_count; /* Number of free blocks */
u32 sm_addr_offset; /* Offset of cib addresses in @sm_raw */
u64 sm_main_fq_nodes; /* Number of nodes in the main fq */
/*
* A range of freed blocks not yet put in the free queue. Extend this as
@@ -147,12 +168,12 @@ struct apfs_spaceman {
/* Number of ip bitmaps */
u32 sm_ip_bmaps_count;
/* List of ip bitmaps, in order */
struct buffer_head *sm_ip_bmaps[];
struct apfs_ip_bitmap_block_info sm_ip_bmaps[];
};
#define TRANSACTION_MAIN_QUEUE_MAX 4096
#define TRANSACTION_BUFFERS_MAX 65536
#define TRANSACTION_STARTS_MAX 65536
#define APFS_TRANS_MAIN_QUEUE_MAX 10000
#define APFS_TRANS_BUFFERS_MAX 65536
#define APFS_TRANS_STARTS_MAX 65536
/* Possible states for the container transaction structure */
#define APFS_NX_TRANS_FORCE_COMMIT 1 /* Commit guaranteed */
@@ -160,28 +181,35 @@ struct apfs_spaceman {
#define APFS_NX_TRANS_COMMITTING 4 /* Commit ongoing */
#define APFS_NX_TRANS_INCOMPLETE_BLOCK 8 /* A data block is not written in full */
/*
* ENOSPC checks before transactions are more or less coarse depending on this.
*/
enum apfs_trans_kind {
APFS_TRANS_REG, /* Most transactions */
APFS_TRANS_DEL, /* Transactions that usually free space */
APFS_TRANS_SYNC, /* Sync (or unmount) transaction */
};
/* The space requirements for each kind of transaction, in blocks */
#define APFS_REG_ROOM 128
#define APFS_DEL_ROOM 20
#define APFS_SYNC_ROOM 6
/*
* Structure that keeps track of a container transaction.
*/
struct apfs_nx_transaction {
unsigned int t_state;
struct delayed_work t_work; /* Work task for transaction commits */
struct super_block *t_work_sb; /* sb that last queued a commit */
struct list_head t_inodes; /* List of inodes in the transaction */
struct list_head t_buffers; /* List of buffers in the transaction */
size_t t_buffers_count; /* Count of items on the list */
int t_starts_count; /* Count of starts for transaction */
};
/*
* Structure that keeps track of a volume transaction.
*/
struct apfs_vol_transaction {
struct buffer_head *t_old_vsb; /* Volume superblock being replaced */
struct apfs_node t_old_omap_root; /* Omap root node being replaced */
struct apfs_node t_old_cat_root; /* Catalog root node being replaced */
};
/* State bits for buffer heads in a transaction */
#define BH_TRANS BH_PrivateStart /* Attached to a transaction */
#define BH_CSUM (BH_PrivateStart + 1) /* Requires checksum update */
@@ -196,14 +224,6 @@ struct apfs_bh_info {
struct list_head list; /* List of buffers in the transaction */
};
/*
* Used to report how many operations may be needed for a transaction
*/
struct apfs_max_ops {
int cat; /* Maximum catalog records that may need changing */
int blks; /* Maximum extent blocks that may need changing */
};
/*
* List entry for an in-memory ephemeral object
*/
@@ -224,17 +244,44 @@ struct apfs_ephemeral_object_info {
/* Mount option flags for a container */
#define APFS_CHECK_NODES 1
#define APFS_READWRITE 2
/*
* Mount options for a container are decided on its first mount. This flag lets
* future mounts know that has happened already.
*/
#define APFS_FLAGS_SET 4
/*
* Wrapper around block devices for portability.
*/
struct apfs_blkdev_info {
struct block_device *blki_bdev;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0) || RHEL_VERSION_GE(9, 5)
struct file *blki_bdev_file;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
struct bdev_handle *blki_bdev_handle;
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0) && !RHEL_VERSION_GE(9, 4)
fmode_t blki_mode;
#endif
/*
* For tier 2, we need to remember the mount path for show_options().
* We don't need this for the main device of course, but better to
* keep things consistent.
*/
char *blki_path;
};
/*
* Container superblock data in memory
*/
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
/* Block device info for the container */
struct apfs_blkdev_info *nx_blkdev_info;
struct apfs_blkdev_info *nx_tier2_info;
u64 nx_tier2_bno; /* Offset for tier 2 block numbers */
struct apfs_nx_superblock *nx_raw; /* On-disk main sb */
u64 nx_bno; /* Current block number for the checkpoint superblock */
u64 nx_xid; /* Latest transaction id */
@@ -254,6 +301,7 @@ struct apfs_nxsb_info {
struct apfs_spaceman *nx_spaceman;
struct apfs_nx_transaction nx_transaction;
int nx_trans_buffers_max;
/* For now, a single semaphore for every operation */
struct rw_semaphore nx_big_sem;
@@ -318,6 +366,8 @@ struct apfs_sb_info {
dev_t s_anon_dev; /* Anonymous device for this volume-snapshot */
char *s_tier2_path; /* Path to the tier 2 device */
char *s_snap_name; /* Label for the mounted snapshot */
u64 s_snap_xid; /* Transaction id for mounted snapshot */
@@ -333,11 +383,9 @@ 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;
atomic_t s_orphan_cleanup_err; /* Error from last orphan cleanup */
};
static inline struct apfs_sb_info *APFS_SB(struct super_block *sb)
@@ -646,8 +694,7 @@ static inline u64 apfs_cat_cnid(struct apfs_key_header *key)
#define APFS_QUERY_PREV 010000 /* Find previous record */
/*
* Structure used to retrieve data from an APFS B-Tree. For now only used
* on the calalog and the object map.
* Structure used to retrieve data from an APFS B-Tree.
*/
struct apfs_query {
struct apfs_node *node; /* Node being searched */
@@ -660,8 +707,8 @@ struct apfs_query {
int index; /* Index of the entry in the node */
int key_off; /* Offset of the key in the node */
int key_len; /* Length of the key */
int off; /* Offset of the data in the node */
int len; /* Length of the data */
int off; /* Offset of the value in the node */
int len; /* Length of the value */
int depth; /* Put a limit on recursion */
};
@@ -925,7 +972,6 @@ extern int apfs_link(struct dentry *old_dentry, struct inode *dir,
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 */
@@ -940,7 +986,6 @@ extern int apfs_flush_extent_cache(struct apfs_dstream_info *dstream);
extern int apfs_dstream_get_new_bno(struct apfs_dstream_info *dstream, u64 dsblock, u64 *bno);
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)
@@ -959,7 +1004,7 @@ extern int apfs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
/* inode.c */
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_schedule_orphan_cleanup(struct super_block *sb);
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,
@@ -967,7 +1012,6 @@ extern struct inode *apfs_new_inode(struct inode *dir, umode_t mode,
extern int apfs_create_inode_rec(struct super_block *sb, struct inode *inode,
struct dentry *dentry);
extern int apfs_inode_create_exclusive_dstream(struct inode *inode);
extern int APFS_CREATE_INODE_REC_MAXOPS(void);
extern int __apfs_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned int len, unsigned int flags, struct page **pagep, void **fsdata);
extern int __apfs_write_end(struct file *file, struct address_space *mapping, loff_t pos, unsigned int len, unsigned int copied, struct page *page, void *fsdata);
extern int apfs_dstream_adj_refcnt(struct apfs_dstream_info *dstream, u32 delta);
@@ -1007,7 +1051,6 @@ extern int apfs_getattr(struct mnt_idmap *idmap,
#endif
extern int apfs_crypto_adj_refcnt(struct super_block *sb, u64 crypto_id, int delta);
extern int APFS_CRYPTO_ADJ_REFCNT_MAXOPS(void);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0)
extern int apfs_fileattr_get(struct dentry *dentry, struct fileattr *fa);
@@ -1070,6 +1113,8 @@ extern int apfs_read_spaceman(struct super_block *sb);
extern int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count);
extern int apfs_free_queue_insert(struct super_block *sb, u64 bno, u64 count);
extern int apfs_spaceman_allocate_block(struct super_block *sb, u64 *bno, bool backwards);
extern int apfs_write_ip_bitmaps(struct super_block *sb);
extern int apfs_spaceman_get_free_blkcnt(struct super_block *sb, u64 *blkcnt);
/* super.c */
extern int apfs_map_volume_super_bno(struct super_block *sb, u64 bno, bool check);
@@ -1081,13 +1126,15 @@ extern int apfs_sync_fs(struct super_block *sb, int wait);
/* transaction.c */
extern int apfs_cpoint_data_free(struct super_block *sb, u64 bno);
extern int apfs_transaction_start(struct super_block *sb, struct apfs_max_ops maxops);
extern void apfs_transaction_init(struct apfs_nx_transaction *trans);
extern int apfs_transaction_start(struct super_block *sb, enum apfs_trans_kind kind);
extern int apfs_transaction_commit(struct super_block *sb);
extern void apfs_inode_join_transaction(struct super_block *sb, struct inode *inode);
extern int apfs_transaction_join(struct super_block *sb,
struct buffer_head *bh);
void apfs_transaction_abort(struct super_block *sb);
extern int apfs_transaction_flush_all_inodes(struct super_block *sb);
extern int apfs_read_ephemeral_objects(struct super_block *sb);
/* xattr.c */
extern int ____apfs_xattr_get(struct inode *inode, const char *name, void *buffer,
@@ -1097,7 +1144,6 @@ extern int __apfs_xattr_get(struct inode *inode, const char *name, void *buffer,
extern int apfs_delete_all_xattrs(struct inode *inode);
extern int apfs_xattr_set(struct inode *inode, const char *name, const void *value,
size_t size, int flags);
extern int APFS_XATTR_SET_MAXOPS(void);
extern ssize_t apfs_listxattr(struct dentry *dentry, char *buffer, size_t size);
extern int apfs_xattr_get_compressed_data(struct inode *inode, const char *name, struct apfs_compressed_data *cdata);
extern void apfs_release_compressed_data(struct apfs_compressed_data *cdata);
@@ -1157,8 +1203,29 @@ static inline void apfs_assert_query_is_valid(const struct apfs_query *query)
static inline void
apfs_map_bh(struct buffer_head *bh, struct super_block *sb, sector_t block)
{
struct apfs_nxsb_info *nxi = NULL;
struct apfs_blkdev_info *info = NULL;
nxi = APFS_NXI(sb);
info = nxi->nx_blkdev_info;
/*
* Block numbers above s_tier2_bno are for the tier 2 device of a
* fusion drive. Don't bother returning an error code if a regular
* drive gets corrupted and reports a tier 2 block number: just treat
* it the same as any other out-of-range block.
*/
if (block >= nxi->nx_tier2_bno) {
if (nxi->nx_tier2_info) {
info = nxi->nx_tier2_info;
block -= nxi->nx_tier2_bno;
} else {
apfs_err(sb, "block number in tier 2 range (0x%llx)", (unsigned long long)block);
}
}
set_buffer_mapped(bh);
bh->b_bdev = APFS_NXI(sb)->nx_bdev;
bh->b_bdev = info->blki_bdev;
bh->b_blocknr = block;
bh->b_size = sb->s_blocksize;
}
@@ -1166,20 +1233,59 @@ apfs_map_bh(struct buffer_head *bh, struct super_block *sb, sector_t block)
static inline struct buffer_head *
apfs_sb_bread(struct super_block *sb, sector_t block)
{
return __bread_gfp(APFS_NXI(sb)->nx_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
struct apfs_nxsb_info *nxi = NULL;
struct apfs_blkdev_info *info = NULL;
nxi = APFS_NXI(sb);
info = nxi->nx_blkdev_info;
if (block >= nxi->nx_tier2_bno) {
if (!nxi->nx_tier2_info) {
/* Not really a fusion drive, so it's corrupted */
apfs_err(sb, "block number in tier 2 range (0x%llx)", (unsigned long long)block);
return NULL;
}
info = nxi->nx_tier2_info;
block -= nxi->nx_tier2_bno;
}
return __bread_gfp(info->blki_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
}
/* Like apfs_getblk(), but doesn't mark the buffer uptodate */
static inline struct buffer_head *
__apfs_getblk(struct super_block *sb, sector_t block)
{
struct apfs_nxsb_info *nxi = NULL;
struct apfs_blkdev_info *info = NULL;
nxi = APFS_NXI(sb);
info = nxi->nx_blkdev_info;
if (block >= nxi->nx_tier2_bno) {
if (!nxi->nx_tier2_info) {
/* Not really a fusion drive, so it's corrupted */
apfs_err(sb, "block number in tier 2 range (0x%llx)", (unsigned long long)block);
return NULL;
}
info = nxi->nx_tier2_info;
block -= nxi->nx_tier2_bno;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
return __getblk_gfp(info->blki_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
#else
return bdev_getblk(info->blki_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
#endif
}
/* Use instead of apfs_sb_bread() for blocks that will just be overwritten */
static inline struct buffer_head *
apfs_getblk(struct super_block *sb, sector_t block)
{
struct buffer_head *bh;
struct buffer_head *bh = NULL;
#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
bh = __apfs_getblk(sb, block);
if (bh)
set_buffer_uptodate(bh);
return bh;
+8
View File
@@ -1474,6 +1474,8 @@ struct apfs_compress_rsrc_data {
} __packed block[];
} __packed;
#define APFS_FUSION_TIER2_DEVICE_BYTE_ADDR 0x4000000000000000ULL
/*
* TODO: this is a placeholder, create some snapshots with the official
* implementation to find the actual limit.
@@ -1495,6 +1497,12 @@ struct apfs_snap_metadata_val {
u8 name[];
} __packed;
/* Snapshot metadata record flags */
enum {
APFS_SNAP_META_PENDING_DATALESS = 0x00000001,
APFS_SNAP_MERGE_IN_PROGRESS = 0x00000002,
};
/*
* Structure of the value of a snapshot name record
*/
+1 -1
View File
@@ -50,7 +50,7 @@ static int apfs_child_from_query(struct apfs_query *query, u64 *child)
index_val = (struct apfs_btn_index_node_val *)(raw + query->off);
*child = le64_to_cpu(index_val->binv_child_oid) + apfs_catalog_base_oid(query);
} else {
if (query->len != 8) { /* The data on a nonleaf node is the child id */
if (query->len != 8) { /* The value on a nonleaf node is the child id */
apfs_err(sb, "bad index value length (%d)", query->len);
return -EFSCORRUPTED;
}
+2 -2
View File
@@ -409,7 +409,7 @@ static ssize_t apfs_compress_file_read_page(struct file *filp, char *buf, loff_t
return step;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
static int apfs_compress_read_folio(struct file *filp, struct folio *folio)
{
struct page *page = &folio->page;
@@ -438,7 +438,7 @@ static int apfs_compress_readpage(struct file *filp, struct page *page)
}
const struct address_space_operations apfs_compress_aops = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
.read_folio = apfs_compress_read_folio,
#else
.readpage = apfs_compress_readpage,
+4 -51
View File
@@ -414,7 +414,6 @@ fail:
apfs_free_query(query);
return ret;
}
#define APFS_CREATE_DENTRY_REC_MAXOPS 1
/**
* apfs_build_sibling_val - Allocate and initialize a sibling link's value
@@ -491,7 +490,6 @@ fail:
apfs_free_query(query);
return ret;
}
#define APFS_CREATE_SIBLING_LINK_REC_MAXOPS 1
/**
* apfs_create_sibling_map_rec - Create a sibling map record for a dentry
@@ -534,7 +532,6 @@ fail:
apfs_free_query(query);
return ret;
}
#define APFS_CREATE_SIBLING_MAP_REC_MAXOPS 1
/**
* apfs_create_sibling_recs - Create sibling link and map records for a dentry
@@ -567,8 +564,6 @@ static int apfs_create_sibling_recs(struct dentry *dentry,
*sibling_id = cnid;
return 0;
}
#define APFS_CREATE_SIBLING_RECS_MAXOPS (APFS_CREATE_SIBLING_LINK_REC_MAXOPS + \
APFS_CREATE_SIBLING_MAP_REC_MAXOPS)
/**
* apfs_create_dentry - Create all records for a new dentry
@@ -613,9 +608,6 @@ static int apfs_create_dentry(struct dentry *dentry, struct inode *inode)
apfs_inode_join_transaction(parent->i_sb, parent);
return 0;
}
#define APFS_CREATE_DENTRY_MAXOPS (APFS_CREATE_SIBLING_RECS_MAXOPS + \
APFS_CREATE_DENTRY_REC_MAXOPS + \
APFS_UPDATE_INODE_MAXOPS())
/**
* apfs_undo_create_dentry - Clean up apfs_create_dentry()
@@ -643,15 +635,9 @@ int apfs_mkany(struct inode *dir, struct dentry *dentry, umode_t mode,
{
struct super_block *sb = dir->i_sb;
struct inode *inode;
struct apfs_max_ops maxops;
int err;
maxops.cat = APFS_CREATE_INODE_REC_MAXOPS() + APFS_CREATE_DENTRY_MAXOPS;
if (symname)
maxops.cat += APFS_XATTR_SET_MAXOPS();
maxops.blks = 0;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
return err;
@@ -818,8 +804,6 @@ static int apfs_prepare_dentry_for_link(struct dentry *dentry)
return apfs_create_dentry_rec(d_inode(dentry), &dentry->d_name,
apfs_ino(parent), sibling_id);
}
#define APFS_PREPARE_DENTRY_FOR_LINK_MAXOPS (1 + APFS_CREATE_SIBLING_RECS_MAXOPS + \
APFS_CREATE_DENTRY_REC_MAXOPS)
/**
* __apfs_undo_link - Clean up __apfs_link()
@@ -875,22 +859,15 @@ fail:
drop_nlink(inode);
return err;
}
#define __APFS_LINK_MAXOPS (APFS_UPDATE_INODE_MAXOPS() + \
APFS_PREPARE_DENTRY_FOR_LINK_MAXOPS + \
APFS_CREATE_DENTRY_MAXOPS)
int apfs_link(struct dentry *old_dentry, struct inode *dir,
struct dentry *dentry)
{
struct super_block *sb = dir->i_sb;
struct inode *inode = d_inode(old_dentry);
struct apfs_max_ops maxops;
int err;
maxops.cat = __APFS_LINK_MAXOPS;
maxops.blks = 0;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
return err;
@@ -954,7 +931,6 @@ fail:
apfs_free_query(query);
return ret;
}
#define APFS_DELETE_SIBLING_LINK_REC_MAXOPS 1
/**
* apfs_delete_sibling_map_rec - Delete the sibling map record for a dentry
@@ -995,7 +971,6 @@ fail:
apfs_free_query(query);
return ret;
}
#define APFS_DELETE_SIBLING_MAP_REC_MAXOPS 1
/**
* apfs_delete_dentry - Delete all records for a dentry
@@ -1048,9 +1023,6 @@ static int apfs_delete_dentry(struct dentry *dentry)
apfs_inode_join_transaction(sb, parent);
return err;
}
#define APFS_DELETE_DENTRY_MAXOPS (1 + APFS_DELETE_SIBLING_LINK_REC_MAXOPS + \
APFS_DELETE_SIBLING_MAP_REC_MAXOPS + \
APFS_UPDATE_INODE_MAXOPS())
/**
* apfs_undo_delete_dentry - Clean up apfs_delete_dentry()
@@ -1217,8 +1189,6 @@ fail:
kfree(qname.name);
return err;
}
#define APFS_CREATE_ORPHAN_LINK_MAXOPS (APFS_CREATE_DENTRY_REC_MAXOPS + \
APFS_UPDATE_INODE_MAXOPS())
/**
* apfs_delete_orphan_link - Delete the link for an orphan inode
@@ -1269,10 +1239,6 @@ fail:
kfree(qname.name);
return err;
}
int APFS_DELETE_ORPHAN_LINK_MAXOPS(void)
{
return 1 + APFS_UPDATE_INODE_MAXOPS();
}
/**
* __apfs_undo_unlink - Clean up __apfs_unlink()
@@ -1368,20 +1334,13 @@ fail:
__apfs_undo_unlink(dentry);
return err;
}
#define __APFS_UNLINK_MAXOPS (APFS_DELETE_DENTRY_MAXOPS + \
APFS_CREATE_ORPHAN_LINK_MAXOPS + \
APFS_UPDATE_INODE_MAXOPS())
int apfs_unlink(struct inode *dir, struct dentry *dentry)
{
struct super_block *sb = dir->i_sb;
struct apfs_max_ops maxops;
int err;
maxops.cat = __APFS_UNLINK_MAXOPS;
maxops.blks = 0;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_DEL);
if (err)
return err;
@@ -1427,7 +1386,6 @@ int apfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
struct super_block *sb = old_dir->i_sb;
struct inode *old_inode = d_inode(old_dentry);
struct inode *new_inode = d_inode(new_dentry);
struct apfs_max_ops maxops;
int err;
if (new_inode && APFS_I(new_inode)->i_nchildren)
@@ -1436,12 +1394,7 @@ int apfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
if (flags & ~RENAME_NOREPLACE) /* TODO: support RENAME_EXCHANGE */
return -EINVAL;
maxops.cat = __APFS_UNLINK_MAXOPS + __APFS_LINK_MAXOPS;
if (new_inode)
maxops.cat += __APFS_UNLINK_MAXOPS;
maxops.blks = 0;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
return err;
+1 -1
View File
@@ -1,5 +1,5 @@
PACKAGE_NAME="linux-apfs-rw"
PACKAGE_VERSION="0.3.9"
PACKAGE_VERSION="0.3.13"
BUILT_MODULE_NAME[0]="apfs"
DEST_MODULE_LOCATION[0]="/extra"
+9 -27
View File
@@ -8,8 +8,6 @@
#include <linux/blk_types.h>
#include "apfs.h"
#define MAX(X, Y) ((X) <= (Y) ? (Y) : (X))
/**
* apfs_ext_is_hole - Does this extent represent a hole in a sparse file?
* @extent: the extent to check
@@ -760,7 +758,6 @@ static int apfs_update_extent(struct apfs_dstream_info *dstream, const struct ap
}
return apfs_update_mid_extent(dstream, extent);
}
#define APFS_UPDATE_EXTENTS_MAXOPS (1 + 2 * APFS_CRYPTO_ADJ_REFCNT_MAXOPS())
static int apfs_extend_phys_extent(struct apfs_query *query, u64 bno, u64 blkcnt, u64 dstream_id)
{
@@ -1138,7 +1135,6 @@ int apfs_flush_extent_cache(struct apfs_dstream_info *dstream)
dstream->ds_ext_dirty = false;
return 0;
}
#define APFS_FLUSH_EXTENT_CACHE APFS_UPDATE_EXTENTS_MAXOPS
/**
* apfs_create_hole - Create and insert a hole extent for the dstream
@@ -1453,10 +1449,6 @@ static int apfs_dstream_get_new_block(struct apfs_dstream_info *dstream, u64 dsb
dstream->ds_ext_dirty = true;
return 0;
}
int APFS_GET_NEW_BLOCK_MAXOPS(void)
{
return APFS_FLUSH_EXTENT_CACHE;
}
/**
* apfs_dstream_get_new_bno - Allocate a new block inside a dstream
@@ -1651,7 +1643,7 @@ static int apfs_dstream_delete_front(struct super_block *sb, u64 ds_id)
int ret;
fq = &sm_raw->sm_fq[APFS_SFQ_MAIN];
if (le64_to_cpu(fq->sfq_count) > TRANSACTION_MAIN_QUEUE_MAX)
if (le64_to_cpu(fq->sfq_count) > APFS_TRANS_MAIN_QUEUE_MAX)
return -EAGAIN;
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
@@ -1710,7 +1702,7 @@ next_extent:
}
}
if (le64_to_cpu(fq->sfq_count) <= TRANSACTION_MAIN_QUEUE_MAX)
if (le64_to_cpu(fq->sfq_count) <= APFS_TRANS_MAIN_QUEUE_MAX)
goto next_extent;
ret = -EAGAIN;
out:
@@ -1763,8 +1755,6 @@ int apfs_clone_file_range(struct file *src_file, loff_t off, struct file *dst_fi
struct apfs_dstream_info *dst_ds = &dst_ai->i_dstream;
struct super_block *sb = src_inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
/* TODO: remember to update the maxops in the future */
struct apfs_max_ops maxops = {0};
const u64 xfield_flags = APFS_INODE_MAINTAIN_DIR_STATS | APFS_INODE_IS_SPARSE | APFS_INODE_HAS_PURGEABLE_FLAGS;
int err;
@@ -1798,7 +1788,7 @@ int apfs_clone_file_range(struct file *src_file, loff_t off, struct file *dst_fi
return -EOPNOTSUPP;
}
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
return err;
apfs_inode_join_transaction(sb, src_inode);
@@ -1911,7 +1901,7 @@ out:
* @paddr_end: first block after the extent to put
* @paddr_min: don't put references before this block
*
* Puts a reference to the physical extent range that ends in paddr. Sets
* Puts a reference to the physical extent range that ends in @paddr_end. Sets
* @paddr_end to the beginning of the extent, so that the caller can continue
* with the previous one. Returns 0 on success, or a negative error code in
* case of failure.
@@ -1973,7 +1963,7 @@ restart:
prev_end = prev_ext.bno + prev_ext.blkcount;
if (prev_end < *paddr_end) {
/* The extent to put is part of a snapshot */
tmp.phys_block_num = MAX(prev_end, paddr_min);
tmp.phys_block_num = max(prev_end, paddr_min);
tmp.len = (*paddr_end - tmp.phys_block_num) << sb->s_blocksize_bits;
ret = apfs_create_update_pext(query, &tmp, -1);
*paddr_end = tmp.phys_block_num;
@@ -2053,7 +2043,7 @@ static int apfs_range_put_reference(struct super_block *sb, u64 paddr, u64 lengt
* @paddr_end: first block after the extent to take
* @paddr_min: don't take references before this block
*
* Takes a reference to the physical extent range that ends in paddr. Sets
* Takes a reference to the physical extent range that ends in @paddr_end. Sets
* @paddr_end to the beginning of the extent, so that the caller can continue
* with the previous one. Returns 0 on success, or a negative error code in
* case of failure.
@@ -2113,7 +2103,7 @@ restart:
prev_end = prev_ext.bno + prev_ext.blkcount;
if (prev_end < *paddr_end) {
/* The extent to take is part of a snapshot */
tmp.phys_block_num = MAX(prev_end, paddr_min);
tmp.phys_block_num = max(prev_end, paddr_min);
tmp.len = (*paddr_end - tmp.phys_block_num) << sb->s_blocksize_bits;
ret = apfs_create_update_pext(query, &tmp, +1);
*paddr_end = tmp.phys_block_num;
@@ -2297,11 +2287,7 @@ 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
bhs[idx] = __apfs_getblk(sb, bno);
if (!bhs[idx]) {
apfs_err(sb, "failed to map block 0x%llx", bno);
ret = -EIO;
@@ -2373,11 +2359,7 @@ 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
bh = __apfs_getblk(sb, bno);
if (!bh)
return;
if (!buffer_uptodate(bh)) {
+3 -10
View File
@@ -28,8 +28,6 @@ static vm_fault_t apfs_page_mkwrite(struct vm_fault *vmf)
struct super_block *sb = inode->i_sb;
struct buffer_head *bh, *head;
vm_fault_t ret = VM_FAULT_LOCKED;
struct apfs_max_ops maxops;
int blkcount = PAGE_SIZE >> inode->i_blkbits;
unsigned int blocksize, block_start, len;
u64 size;
int err = 0;
@@ -37,12 +35,7 @@ static vm_fault_t apfs_page_mkwrite(struct vm_fault *vmf)
sb_start_pagefault(inode->i_sb);
file_update_time(vma->vm_file);
/* Placeholder values, I need to get back to this in the future */
maxops.cat = APFS_UPDATE_INODE_MAXOPS() +
blkcount * APFS_GET_NEW_BLOCK_MAXOPS();
maxops.blks = blkcount;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
goto out;
apfs_inode_join_transaction(sb, inode);
@@ -113,7 +106,7 @@ out_abort:
apfs_transaction_abort(sb);
out:
if (err)
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) && !RHEL_VERSION_GE(9, 5)
ret = block_page_mkwrite_return(err);
#else
ret = vmf_fs_error(err);
@@ -132,7 +125,7 @@ int apfs_file_mmap(struct file *file, struct vm_area_struct *vma)
{
struct address_space *mapping = file->f_mapping;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
if (!mapping->a_ops->read_folio)
#else
if (!mapping->a_ops->readpage)
+97 -95
View File
@@ -10,7 +10,7 @@
#include <linux/blk_types.h>
#include "apfs.h"
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
#include <linux/sched/mm.h>
#endif
@@ -20,7 +20,7 @@
#define MAX_PFK_LEN 512
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
static int apfs_read_folio(struct file *file, struct folio *folio)
{
@@ -90,7 +90,6 @@ out:
apfs_free_query(query);
return ret;
}
#define APFS_CREATE_DSTREAM_REC_MAXOPS 1
static int apfs_check_dstream_refcnt(struct inode *inode);
static int apfs_put_dstream_rec(struct apfs_dstream_info *dstream);
@@ -308,7 +307,6 @@ out:
apfs_free_query(query);
return ret;
}
#define APFS_CREATE_CRYPTO_REC_MAXOPS 1
/**
* apfs_dflt_key_class - Returns default key class for files in volume
@@ -371,10 +369,6 @@ out:
apfs_free_query(query);
return ret;
}
int APFS_CRYPTO_ADJ_REFCNT_MAXOPS(void)
{
return 1;
}
/**
* apfs_crypto_set_key - Modify content of crypto state record
@@ -423,7 +417,6 @@ out:
apfs_free_query(query);
return ret;
}
#define APFS_CRYPTO_SET_KEY_MAXOPS 1
/**
* apfs_crypto_get_key - Retrieve content of crypto state record
@@ -502,7 +495,7 @@ int __apfs_write_begin(struct file *file, struct address_space *mapping, loff_t
}
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
flags = memalloc_nofs_save();
page = grab_cache_page_write_begin(mapping, index);
memalloc_nofs_restore(flags);
@@ -565,7 +558,11 @@ int __apfs_write_begin(struct file *file, struct address_space *mapping, loff_t
}
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
err = __block_write_begin(page_folio(page), pos, len, apfs_get_new_block);
#else
err = __block_write_begin(page, pos, len, apfs_get_new_block);
#endif
if (err) {
apfs_err(sb, "CoW failed in inode 0x%llx", apfs_ino(inode));
goto out_put_page;
@@ -580,7 +577,11 @@ out_put_page:
return err;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
static int apfs_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned int len,
struct folio **foliop, void **fsdata)
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
static int apfs_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned int len,
struct page **pagep, void **fsdata)
@@ -592,29 +593,28 @@ static int apfs_write_begin(struct file *file, struct address_space *mapping,
{
struct inode *inode = mapping->host;
struct super_block *sb = inode->i_sb;
int blkcount = (len + sb->s_blocksize - 1) >> inode->i_blkbits;
struct apfs_max_ops maxops;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
struct page *page = NULL;
struct page **pagep = &page;
#endif
int err;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
unsigned int flags = 0;
#endif
if (unlikely(pos >= APFS_MAX_FILE_SIZE))
return -EFBIG;
maxops.cat = APFS_CREATE_DSTREAM_REC_MAXOPS +
APFS_CREATE_CRYPTO_REC_MAXOPS +
APFS_UPDATE_INODE_MAXOPS() +
blkcount * APFS_GET_NEW_BLOCK_MAXOPS();
maxops.blks = blkcount;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
return err;
err = __apfs_write_begin(file, mapping, pos, len, flags, pagep, fsdata);
if (err)
goto fail;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
*foliop = page_folio(page);
#endif
return 0;
fail:
@@ -628,7 +628,11 @@ int __apfs_write_end(struct file *file, struct address_space *mapping, loff_t po
struct apfs_dstream_info *dstream = &APFS_I(inode)->i_dstream;
int ret, err;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
ret = generic_write_end(file, mapping, pos, len, copied, page_folio(page), fsdata);
#else
ret = generic_write_end(file, mapping, pos, len, copied, page, fsdata);
#endif
dstream->ds_size = i_size_read(inode);
if (ret < len && pos + len > inode->i_size) {
truncate_pagecache(inode, inode->i_size);
@@ -641,13 +645,22 @@ int __apfs_write_end(struct file *file, struct address_space *mapping, loff_t po
return ret;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
static int apfs_write_end(struct file *file, struct address_space *mapping,
loff_t pos, unsigned int len, unsigned int copied,
struct folio *folio, void *fsdata)
#else
static int apfs_write_end(struct file *file, struct address_space *mapping,
loff_t pos, unsigned int len, unsigned int copied,
struct page *page, void *fsdata)
#endif
{
struct inode *inode = mapping->host;
struct super_block *sb = inode->i_sb;
struct apfs_nx_transaction *trans = &APFS_NXI(sb)->nx_transaction;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
struct page *page = &folio->page;
#endif
int ret, err;
ret = __apfs_write_end(file, mapping, pos, len, copied, page, fsdata);
@@ -670,42 +683,42 @@ fail:
return err;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 18, 0)
static void apfs_noop_invalidatepage(struct page *page, unsigned int offset, unsigned int length)
#else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) || RHEL_VERSION_GE(9, 3)
static void apfs_noop_invalidate_folio(struct folio *folio, size_t offset, size_t length)
#else
static void apfs_noop_invalidatepage(struct page *page, unsigned int offset, unsigned int length)
#endif
{
}
/* bmap is not implemented to avoid issues with CoW on swapfiles */
static const struct address_space_operations apfs_aops = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) || RHEL_VERSION_GE(9, 2)
.dirty_folio = block_dirty_folio,
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0)
.set_page_dirty = __set_page_dirty_buffers,
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
.read_folio = apfs_read_folio,
#else
.readpage = apfs_readpage,
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
.readahead = apfs_readahead,
.readahead = apfs_readahead,
#else
.readpages = apfs_readpages,
.readpages = apfs_readpages,
#endif
.write_begin = apfs_write_begin,
.write_end = apfs_write_end,
/* The intention is to keep bhs around until the transaction is over */
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 18, 0)
.invalidatepage = apfs_noop_invalidatepage,
#else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) || RHEL_VERSION_GE(9, 3)
.invalidate_folio = apfs_noop_invalidate_folio,
#else
.invalidatepage = apfs_noop_invalidatepage,
#endif
};
@@ -1225,7 +1238,6 @@ fail:
kfree(new_val);
return err;
}
#define APFS_INODE_RENAME_MAXOPS 1
/**
* apfs_create_dstream_xfield - Create the inode xfield for a new data stream
@@ -1280,7 +1292,6 @@ fail:
kfree(new_val);
return err;
}
#define APFS_CREATE_DSTREAM_XFIELD_MAXOPS 1
/**
* apfs_inode_resize - Update the sizes reported in an inode record
@@ -1331,7 +1342,6 @@ static int apfs_inode_resize(struct inode *inode, struct apfs_query *query)
/* This inode has no dstream xfield, so we need to create it */
return apfs_create_dstream_xfield(inode, query);
}
#define APFS_INODE_RESIZE_MAXOPS (1 + APFS_CREATE_DSTREAM_XFIELD_MAXOPS)
/**
* apfs_create_sparse_xfield - Create an inode xfield to count sparse bytes
@@ -1535,10 +1545,6 @@ fail:
apfs_free_query(query);
return err;
}
int APFS_UPDATE_INODE_MAXOPS(void)
{
return APFS_INODE_RENAME_MAXOPS + APFS_INODE_RESIZE_MAXOPS + 1;
}
/**
* apfs_delete_inode - Delete an inode record
@@ -1622,7 +1628,6 @@ static int apfs_delete_inode(struct inode *inode)
ai->i_cleaned = true;
return ret;
}
#define APFS_DELETE_INODE_MAXOPS 1
/**
* apfs_clean_single_orphan - Clean the given orphan file
@@ -1634,12 +1639,11 @@ static int apfs_delete_inode(struct inode *inode)
static int apfs_clean_single_orphan(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct apfs_max_ops maxops = {0}; /* TODO: rethink this stuff */
u64 ino = apfs_ino(inode);
bool eagain = false;
int err;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_DEL);
if (err)
return err;
err = apfs_delete_inode(inode);
@@ -1718,6 +1722,32 @@ out:
return err;
}
/**
* apfs_schedule_orphan_cleanup - Schedule cleanup for orphan inodes
* @sb: filesystem superblock
*/
void apfs_schedule_orphan_cleanup(struct super_block *sb)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
/*
* Don't schedule cleanups during unmount: completing all of it could
* take a while so just leave future mounts to handle the orphans.
*/
if (atomic_read(&sb->s_active) == 0)
return;
/*
* Don't keep retrying orphan cleanups nonstop when they run into an
* unexpected error: it won't do any good and it will flood dmesg. We
* will retry eventually for ENOSPC, but that's handled elsewhere.
*/
if (atomic_read(&sbi->s_orphan_cleanup_err))
return;
schedule_work(&sbi->s_orphan_cleanup_work);
}
/**
* apfs_clean_orphans - Delete as many orphan files as is reasonable
* @sb: filesystem superblock
@@ -1726,26 +1756,27 @@ out:
*/
static int apfs_clean_orphans(struct super_block *sb)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
int ret, i;
for (i = 0; i < 100; ++i) {
ret = apfs_clean_any_orphan(sb);
if (ret) {
if (ret == -ENODATA)
return 0;
if (ret == -EAGAIN)
break;
apfs_err(sb, "failed to delete an orphan file");
return ret;
}
if (ret == 0)
continue;
if (ret == -ENODATA)
return 0;
if (ret == -EAGAIN)
break;
apfs_err(sb, "failed to delete an orphan file");
atomic_set(&sbi->s_orphan_cleanup_err, ret);
return ret;
}
/*
* If a file is too big, or if there are too many files, take a break
* and continue later.
*/
if (atomic_read(&sb->s_active) != 0)
schedule_work(&APFS_SB(sb)->s_orphan_cleanup_work);
apfs_schedule_orphan_cleanup(sb);
return 0;
}
@@ -1761,18 +1792,15 @@ void apfs_evict_inode(struct inode *inode)
if (!ai->i_has_dstream || ai->i_dstream.ds_size == 0) {
/* For files with no extents, scheduled cleanup wastes time */
err = apfs_clean_single_orphan(inode);
if (err)
if (err) {
apfs_err(sb, "failed to clean orphan 0x%llx (err:%d)", apfs_ino(inode), err);
atomic_set(&APFS_SB(sb)->s_orphan_cleanup_err, err);
}
goto out;
}
/*
* If the inode still has extents then schedule cleanup for the rest
* of it. Not during unmount though: completing all cleanup could take
* a while so just leave future mounts to handle the orphans.
*/
if (atomic_read(&sb->s_active))
schedule_work(&APFS_SB(sb)->s_orphan_cleanup_work);
/* If the inode still has extents then schedule cleanup for the rest */
apfs_schedule_orphan_cleanup(sb);
out:
truncate_inode_pages_final(&inode->i_data);
clear_inode(inode);
@@ -1951,10 +1979,6 @@ fail:
apfs_free_query(query);
return ret;
}
int APFS_CREATE_INODE_REC_MAXOPS(void)
{
return 1;
}
/**
* apfs_setsize - Change the size of a regular file
@@ -2009,12 +2033,14 @@ int apfs_setattr(struct mnt_idmap *idmap,
{
struct inode *inode = d_inode(dentry);
struct super_block *sb = inode->i_sb;
struct apfs_max_ops maxops;
bool resizing = S_ISREG(inode->i_mode) && (iattr->ia_valid & ATTR_SIZE);
bool shrinking = false;
int err;
if (resizing && iattr->ia_size > APFS_MAX_FILE_SIZE)
return -EFBIG;
if (resizing && iattr->ia_size < inode->i_size)
shrinking = true;
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0)
err = setattr_prepare(dentry, iattr);
@@ -2026,11 +2052,8 @@ int apfs_setattr(struct mnt_idmap *idmap,
if (err)
return err;
maxops.cat = APFS_UPDATE_INODE_MAXOPS();
maxops.blks = 0;
/* TODO: figure out why ->write_inode() isn't firing */
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, shrinking ? APFS_TRANS_DEL : APFS_TRANS_REG);
if (err)
return err;
apfs_inode_join_transaction(sb, inode);
@@ -2070,13 +2093,9 @@ int apfs_update_time(struct inode *inode, int flags)
#endif
{
struct super_block *sb = inode->i_sb;
struct apfs_max_ops maxops;
int err;
maxops.cat = APFS_UPDATE_INODE_MAXOPS();
maxops.blks = 0;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
return err;
apfs_inode_join_transaction(sb, inode);
@@ -2137,7 +2156,6 @@ static int apfs_ioc_set_dir_class(struct file *file, u32 __user *user_class)
struct inode *inode = file_inode(file);
struct apfs_inode_info *ai = APFS_I(inode);
struct super_block *sb = inode->i_sb;
struct apfs_max_ops maxops;
u32 class;
int err;
@@ -2146,10 +2164,7 @@ static int apfs_ioc_set_dir_class(struct file *file, u32 __user *user_class)
ai->i_key_class = class;
maxops.cat = APFS_UPDATE_INODE_MAXOPS();
maxops.blks = 0;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
return err;
apfs_inode_join_transaction(sb, inode);
@@ -2171,7 +2186,6 @@ static int apfs_ioc_set_pfk(struct file *file, void __user *user_pfk)
struct apfs_crypto_state_val *pfk;
struct apfs_inode_info *ai = APFS_I(inode);
struct apfs_dstream_info *dstream = &ai->i_dstream;
struct apfs_max_ops maxops;
unsigned int key_len, key_class;
int err;
@@ -2189,10 +2203,7 @@ static int apfs_ioc_set_pfk(struct file *file, void __user *user_pfk)
}
pfk->refcnt = cpu_to_le32(1);
maxops.cat = APFS_CRYPTO_SET_KEY_MAXOPS + APFS_UPDATE_INODE_MAXOPS();
maxops.blks = 0;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err) {
kfree(pfk);
return err;
@@ -2359,7 +2370,6 @@ static int apfs_ioc_getflags(struct file *file, int __user *arg)
static int apfs_do_ioc_setflags(struct inode *inode, unsigned int newflags)
{
struct super_block *sb = inode->i_sb;
struct apfs_max_ops maxops;
unsigned int oldflags;
int err;
@@ -2370,9 +2380,7 @@ static int apfs_do_ioc_setflags(struct inode *inode, unsigned int newflags)
if (err)
return err;
maxops.cat = APFS_UPDATE_INODE_MAXOPS();
maxops.blks = 0;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
return err;
@@ -2442,7 +2450,6 @@ int apfs_fileattr_set(struct user_namespace *mnt_userns, struct dentry *dentry,
{
struct inode *inode = d_inode(dentry);
struct super_block *sb = inode->i_sb;
struct apfs_max_ops maxops;
int err;
if (sb->s_flags & SB_RDONLY)
@@ -2455,9 +2462,7 @@ int apfs_fileattr_set(struct user_namespace *mnt_userns, struct dentry *dentry,
lockdep_assert_held_write(&inode->i_rwsem);
maxops.cat = APFS_UPDATE_INODE_MAXOPS();
maxops.blks = 0;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
return err;
@@ -2485,7 +2490,6 @@ int apfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, struct fil
{
struct inode *inode = d_inode(dentry);
struct super_block *sb = inode->i_sb;
struct apfs_max_ops maxops;
int err;
if (sb->s_flags & SB_RDONLY)
@@ -2498,9 +2502,7 @@ int apfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, struct fil
lockdep_assert_held_write(&inode->i_rwsem);
maxops.cat = APFS_UPDATE_INODE_MAXOPS();
maxops.blks = 0;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
return err;
+7 -4
View File
@@ -180,7 +180,7 @@ int apfs_read_free_queue_key(void *raw, int size, struct apfs_key *key)
{
struct apfs_spaceman_free_queue_key *raw_key;
if (size < sizeof(struct apfs_spaceman_free_queue_key)) {
if (size != sizeof(*raw_key)) {
apfs_err(NULL, "bad key length (%d)", size);
return -EFSCORRUPTED;
}
@@ -204,14 +204,17 @@ int apfs_read_free_queue_key(void *raw, int size, struct apfs_key *key)
*/
int apfs_read_omap_key(void *raw, int size, struct apfs_key *key)
{
if (size < sizeof(struct apfs_omap_key)) {
struct apfs_omap_key *raw_key;
if (size != sizeof(*raw_key)) {
apfs_err(NULL, "bad key length (%d)", size);
return -EFSCORRUPTED;
}
raw_key = raw;
key->id = le64_to_cpu(((struct apfs_omap_key *)raw)->ok_oid);
key->id = le64_to_cpu(raw_key->ok_oid);
key->type = 0;
key->number = le64_to_cpu(((struct apfs_omap_key *)raw)->ok_xid);
key->number = le64_to_cpu(raw_key->ok_xid);
key->name = NULL;
return 0;
-2
View File
@@ -12,8 +12,6 @@
#include <linux/string.h>
#include "libzbitmap.h"
#define MIN(x, y) ((x) > (y) ? (y) : (x))
#define ZBM_MAGIC "ZBM\x09"
#define ZBM_MAGIC_SZ 4
+4
View File
@@ -114,7 +114,11 @@ static int apfs_dentry_compare(const struct dentry *dentry, unsigned int len,
return apfs_filename_cmp(dentry->d_sb, name->name, name->len, str, len);
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 14, 0)
static int apfs_dentry_revalidate(struct dentry *dentry, unsigned int flags)
#else
static int apfs_dentry_revalidate(struct inode *dir, const struct qstr *name, struct dentry *dentry, unsigned int flags)
#endif
{
struct super_block *sb = dentry->d_sb;
+45 -45
View File
@@ -15,7 +15,7 @@
* Verifies that the node index fits in a single block, and that the number
* of records fits in the index. Without this check a crafted filesystem could
* pretend to have too many records, and calls to apfs_node_locate_key() and
* apfs_node_locate_data() would read beyond the limits of the node.
* apfs_node_locate_value() would read beyond the limits of the node.
*/
static bool apfs_node_is_valid(struct super_block *sb,
struct apfs_node *node)
@@ -142,7 +142,7 @@ struct apfs_node *apfs_read_node(struct super_block *sb, u64 oid, u32 storage,
node->key = sizeof(*raw) + le16_to_cpu(raw->btn_table_space.off)
+ le16_to_cpu(raw->btn_table_space.len);
node->free = node->key + le16_to_cpu(raw->btn_free_space.off);
node->data = node->free + le16_to_cpu(raw->btn_free_space.len);
node->val = node->free + le16_to_cpu(raw->btn_free_space.len);
free_head = &raw->btn_key_free_list;
node->key_free_list_len = le16_to_cpu(free_head->len);
@@ -559,7 +559,7 @@ void apfs_update_node(struct apfs_node *node)
toc_off = sizeof(*raw) + le16_to_cpu(raw->btn_table_space.off);
raw->btn_table_space.len = cpu_to_le16(node->key - toc_off);
raw->btn_free_space.off = cpu_to_le16(node->free - node->key);
raw->btn_free_space.len = cpu_to_le16(node->data - node->free);
raw->btn_free_space.len = cpu_to_le16(node->val - node->free);
/* Reset the lists on zero length, a defragmentation is taking place */
free_head = &raw->btn_key_free_list;
@@ -584,8 +584,8 @@ void apfs_update_node(struct apfs_node *node)
* @off: on return will hold the offset in the block
*
* Returns the length of the key, or 0 in case of failure. The function checks
* that this length fits within the block; callers must use the returned value
* to make sure they never operate outside its bounds.
* that this length fits within the block; callers must use it to make sure
* they never operate outside its bounds.
*/
int apfs_node_locate_key(struct apfs_node *node, int index, int *off)
{
@@ -613,7 +613,7 @@ int apfs_node_locate_key(struct apfs_node *node, int index, int *off)
/* Translate offset in key area to offset in block */
*off = node->key + le16_to_cpu(entry->k);
} else {
/* These node types have variable length keys and data */
/* These node types have variable length keys and values */
struct apfs_kvloc *entry;
entry = (struct apfs_kvloc *)raw->btn_data + index;
@@ -630,17 +630,17 @@ int apfs_node_locate_key(struct apfs_node *node, int index, int *off)
}
/**
* apfs_node_locate_data - Locate the data of a node record
* apfs_node_locate_value - Locate the value of a node record
* @node: node to be searched
* @index: number of the entry to locate
* @off: on return will hold the offset in the block
*
* Returns the length of the data, which may be 0 in case of corruption or if
* Returns the length of the value, which may be 0 in case of corruption or if
* the record is a ghost. The function checks that this length fits within the
* block; callers must use the returned value to make sure they never operate
* outside its bounds.
* block; callers must use it to make sure they never operate outside its
* bounds.
*/
static int apfs_node_locate_data(struct apfs_node *node, int index, int *off)
static int apfs_node_locate_value(struct apfs_node *node, int index, int *off)
{
struct super_block *sb = node->object.sb;
struct apfs_btree_node_phys *raw;
@@ -653,7 +653,7 @@ static int apfs_node_locate_data(struct apfs_node *node, int index, int *off)
raw = (struct apfs_btree_node_phys *)node->object.data;
if (apfs_node_has_fixed_kv_size(node)) {
/* These node types have fixed length keys and data */
/* These node types have fixed length keys and values */
struct apfs_kvoff *entry;
entry = (struct apfs_kvoff *)raw->btn_data + index;
@@ -669,7 +669,7 @@ static int apfs_node_locate_data(struct apfs_node *node, int index, int *off)
len = apfs_node_is_leaf(node) ? 16 : 8;
}
/*
* Data offsets are counted backwards from the end of the
* Value offsets are counted backwards from the end of the
* block, or from the beginning of the footer when it exists
*/
if (apfs_node_is_root(node)) /* has footer */
@@ -678,13 +678,13 @@ static int apfs_node_locate_data(struct apfs_node *node, int index, int *off)
else
*off = sb->s_blocksize - le16_to_cpu(entry->v);
} else {
/* These node types have variable length keys and data */
/* These node types have variable length keys and values */
struct apfs_kvloc *entry;
entry = (struct apfs_kvloc *)raw->btn_data + index;
len = le16_to_cpu(entry->v.len);
/*
* Data offsets are counted backwards from the end of the
* Value offsets are counted backwards from the end of the
* block, or from the beginning of the footer when it exists
*/
if (apfs_node_is_root(node)) /* has footer */
@@ -831,7 +831,7 @@ static int apfs_node_prev(struct super_block *sb, struct apfs_query *query)
apfs_err(sb, "bad key for index %d", query->index);
return -EFSCORRUPTED;
}
query->len = apfs_node_locate_data(node, query->index, &query->off);
query->len = apfs_node_locate_value(node, query->index, &query->off);
if (query->len == 0) {
apfs_err(sb, "bad value for index %d", query->index);
return -EFSCORRUPTED;
@@ -881,7 +881,7 @@ static int apfs_node_next(struct super_block *sb, struct apfs_query *query)
query->flags & APFS_QUERY_EXACT)
return -ENODATA;
query->len = apfs_node_locate_data(node, query->index, &query->off);
query->len = apfs_node_locate_value(node, query->index, &query->off);
if (query->len == 0) {
apfs_err(sb, "bad value for index %d", query->index);
return -EFSCORRUPTED;
@@ -913,7 +913,7 @@ static int apfs_node_next(struct super_block *sb, struct apfs_query *query)
* as soon as we return to this level. The function may also return -EAGAIN,
* to signal that the search should go on in a different branch.
*
* On success returns 0; the offset of the data within the block will be saved
* On success returns 0; the offset of the value within the block will be saved
* in @query->off, and its length in @query->len. The function checks that this
* length fits within the block; callers must use the returned value to make
* sure they never operate outside its bounds.
@@ -979,7 +979,7 @@ int apfs_node_query(struct super_block *sb, struct apfs_query *query)
query->flags |= APFS_QUERY_NEXT;
}
query->len = apfs_node_locate_data(node, query->index, &query->off);
query->len = apfs_node_locate_value(node, query->index, &query->off);
return 0;
}
@@ -993,7 +993,7 @@ void apfs_node_query_first(struct apfs_query *query)
query->index = 0;
query->key_len = apfs_node_locate_key(node, query->index, &query->key_off);
query->len = apfs_node_locate_data(node, query->index, &query->off);
query->len = apfs_node_locate_value(node, query->index, &query->off);
}
/**
@@ -1071,7 +1071,7 @@ static int apfs_btree_inc_height(struct apfs_query *query)
new_node->records = root->records;
new_node->key = root->key;
new_node->free = root->free;
new_node->data = root->data + sizeof(*info);
new_node->val = root->val + sizeof(*info);
new_node->key_free_list_len = root->key_free_list_len;
new_node->val_free_list_len = root->val_free_list_len;
new_raw = (void *)new_node->object.data;
@@ -1079,9 +1079,9 @@ static int apfs_btree_inc_height(struct apfs_query *query)
memcpy((void *)new_raw + sizeof(new_raw->btn_o),
(void *)root_raw + sizeof(root_raw->btn_o),
root->free - sizeof(new_raw->btn_o));
memcpy((void *)new_raw + new_node->data,
(void *)root_raw + root->data,
sb->s_blocksize - new_node->data);
memcpy((void *)new_raw + new_node->val,
(void *)root_raw + root->val,
sb->s_blocksize - new_node->val);
query->off += sizeof(*info);
apfs_update_node(new_node);
@@ -1112,10 +1112,10 @@ static int apfs_btree_inc_height(struct apfs_query *query)
/* The new root is a nonleaf node; the record value is the child id */
root->flags &= ~APFS_BTNODE_LEAF;
root->data = sb->s_blocksize - sizeof(*info) - sizeof(*raw_oid);
raw_oid = (void *)root_raw + root->data;
root->val = sb->s_blocksize - sizeof(*info) - sizeof(*raw_oid);
raw_oid = (void *)root_raw + root->val;
*raw_oid = cpu_to_le64(new_node->object.oid);
root_query->off = root->data;
root_query->off = root->val;
root_query->len = sizeof(*raw_oid);
/* With the key and value in place, set the table-of-contents */
@@ -1173,9 +1173,9 @@ static int apfs_copy_record_range(struct apfs_node *dest_node,
toc_size = toc_entry_size * round_up(end - start, APFS_BTREE_TOC_ENTRY_INCREMENT);
dest_node->key = sizeof(*dest_raw) + toc_size;
dest_node->free = dest_node->key;
dest_node->data = sb->s_blocksize;
dest_node->val = sb->s_blocksize;
if (apfs_node_is_root(dest_node))
dest_node->data -= sizeof(struct apfs_btree_info);
dest_node->val -= sizeof(struct apfs_btree_info);
/* We'll use a temporary query structure to move the records around */
query = apfs_alloc_query(dest_node, NULL /* parent */);
@@ -1199,15 +1199,15 @@ static int apfs_copy_record_range(struct apfs_node *dest_node,
query->key_len = len;
dest_node->free += len;
len = apfs_node_locate_data(src_node, i, &off);
dest_node->data -= len;
if (dest_node->data < 0) {
len = apfs_node_locate_value(src_node, i, &off);
dest_node->val -= len;
if (dest_node->val < 0) {
apfs_err(sb, "value of length %d doesn't fit", len);
goto fail;
}
memcpy((char *)dest_raw + dest_node->data,
memcpy((char *)dest_raw + dest_node->val,
(char *)src_raw + off, len);
query->off = dest_node->data;
query->off = dest_node->val;
query->len = len;
query->index = i - start;
@@ -1483,7 +1483,7 @@ static void apfs_node_free_list_add(struct apfs_node *node, u16 off, u16 len)
apfs_assert_in_transaction(sb, &node_raw->btn_o);
if (off >= node->data) { /* Value area */
if (off >= node->val) { /* Value area */
off_to_rel = apfs_off_to_val_off;
head = &node_raw->btn_val_free_list;
node->val_free_list_len += len;
@@ -1519,8 +1519,8 @@ void apfs_node_free_range(struct apfs_node *node, u16 off, u16 len)
apfs_assert_in_transaction(sb, &raw->btn_o);
if (off == node->data)
node->data += len;
if (off == node->val)
node->val += len;
else if (off + len == node->free)
node->free -= len;
else
@@ -1616,7 +1616,7 @@ static int apfs_node_alloc_key(struct apfs_node *node, u16 len)
{
int off;
if (node->free + len <= node->data) {
if (node->free + len <= node->val) {
off = node->free;
node->free += len;
return off;
@@ -1636,9 +1636,9 @@ static int apfs_node_alloc_val(struct apfs_node *node, u16 len)
{
int off;
if (node->free + len <= node->data) {
off = node->data - len;
node->data -= len;
if (node->free + len <= node->val) {
off = node->val - len;
node->val -= len;
return off;
}
return apfs_node_free_list_alloc(node, len, true /* value */);
@@ -1650,7 +1650,7 @@ static int apfs_node_alloc_val(struct apfs_node *node, u16 len)
*/
static int apfs_node_total_room(struct apfs_node *node)
{
return node->data - node->free + node->key_free_list_len + node->val_free_list_len;
return node->val - node->free + node->key_free_list_len + node->val_free_list_len;
}
/**
@@ -1856,7 +1856,7 @@ defrag:
defragged = true;
/* The record to replace probably moved around */
query->len = apfs_node_locate_data(query->node, query->index, &query->off);
query->len = apfs_node_locate_value(query->node, query->index, &query->off);
query->key_len = apfs_node_locate_key(query->node, query->index, &query->key_off);
goto retry;
}
@@ -1900,7 +1900,7 @@ retry:
new_key_base += inc;
new_free_base += inc;
if (new_free_base > node->data)
if (new_free_base > node->val)
goto defrag;
memmove((void *)node_raw + new_key_base,
(void *)node_raw + node->key, node->free - node->key);
@@ -2021,7 +2021,7 @@ int apfs_create_single_rec_node(struct apfs_query *query, void *key, int key_len
new_node->key_free_list_len = 0;
new_node->val_free_list_len = 0;
new_node->key = new_node->free = sizeof(*new_raw);
new_node->data = sb->s_blocksize; /* Nonroot */
new_node->val = sb->s_blocksize; /* Nonroot */
prev_raw = (void *)prev_node->object.data;
new_raw = (void *)new_node->object.data;
+82 -7
View File
@@ -149,9 +149,9 @@ static int apfs_create_snap_name_rec(struct apfs_node *snap_root, const char *na
err = apfs_btree_query(sb, &query);
if (err == 0) {
/* TODO: avoid transaction abort here */
apfs_info(sb, "a snapshot with that name already exists");
err = -EEXIST;
/* This should never happen here, the caller has checked */
apfs_alert(sb, "snapshot name record already exists");
err = -EFSCORRUPTED;
goto fail;
}
if (err != -ENODATA) {
@@ -332,6 +332,56 @@ static int apfs_update_omap_snapshots(struct super_block *sb)
return err;
}
/**
* apfs_snapshot_name_check - Check if a snapshot with the given name exists
* @sb: filesystem superblock
* @name: name to check
* @name_len: length of @name
* @eexist: on return, whether the name exists or not
*
* Returns 0 on success, or a negative error code in case of failure.
*/
static int apfs_snapshot_name_check(struct super_block *sb, const char *name, int name_len, bool *eexist)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_superblock *vsb_raw = sbi->s_vsb_raw;
struct apfs_node *snap_root = NULL;
struct apfs_query *query = NULL;
int err;
snap_root = apfs_read_node(sb, le64_to_cpu(vsb_raw->apfs_snap_meta_tree_oid), APFS_OBJ_PHYSICAL, false /* write */);
if (IS_ERR(snap_root)) {
apfs_err(sb, "failed to read snap meta root 0x%llx", le64_to_cpu(vsb_raw->apfs_snap_meta_tree_oid));
return PTR_ERR(snap_root);
}
vsb_raw = NULL;
query = apfs_alloc_query(snap_root, NULL /* parent */);
if (!query) {
err = -ENOMEM;
goto out;
}
apfs_init_snap_name_key(name, &query->key);
query->flags |= APFS_QUERY_SNAP_META | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
if (err == 0) {
*eexist = true;
} else if (err == -ENODATA) {
*eexist = false;
err = 0;
} else {
apfs_err(sb, "query failed (%s)", name);
}
out:
apfs_free_query(query);
query = NULL;
apfs_node_free(snap_root);
snap_root = NULL;
return err;
}
/**
* apfs_do_ioc_takesnapshot - Actual work for apfs_ioc_take_snapshot()
* @mntpoint: inode of the mount point to snapshot
@@ -345,15 +395,33 @@ static int apfs_do_ioc_take_snapshot(struct inode *mntpoint, const char *name, i
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_superblock *vsb_raw = NULL;
struct apfs_omap *omap = sbi->s_omap;
/* 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);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
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;
apfs_warn(sb, "snapshot name already in use (%s)", name);
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
@@ -516,15 +584,22 @@ fail:
static int apfs_snap_sblock_from_query(struct apfs_query *query, u64 *sblock_oid)
{
struct super_block *sb = query->node->object.sb;
char *raw = query->node->object.data;
struct apfs_snap_metadata_val *val = NULL;
if (query->len < sizeof(*val)) {
apfs_err(query->node->object.sb, "bad value length (%d)", query->len);
apfs_err(sb, "bad value length (%d)", query->len);
return -EFSCORRUPTED;
}
val = (struct apfs_snap_metadata_val *)(raw + query->off);
/* I don't know anything about these snapshots, can they be mounted? */
if (le32_to_cpu(val->flags) & APFS_SNAP_META_PENDING_DATALESS) {
apfs_warn(sb, "the requested snapshot is dataless");
return -EOPNOTSUPP;
}
*sblock_oid = le64_to_cpu(val->sblock_oid);
return 0;
}
+360 -186
View File
File diff suppressed because it is too large. Load diff
+655 -320
View File
File diff suppressed because it is too large. Load diff
+197 -141
View File
File diff suppressed because it is too large. Load diff
+3 -4
View File
@@ -12,10 +12,9 @@
#include <linux/types.h>
#include <linux/nls.h>
#include <linux/ctype.h>
#include <linux/version.h>
#include "unicode.h"
#define MIN(X, Y) ((X) <= (Y) ? (X) : (Y))
/* The arrays of unicode data are defined at the bottom of the file */
/* TODO: would a single trie with all the data be more efficient? */
static u16 apfs_nfd_trie[];
@@ -234,7 +233,7 @@ static int apfs_get_normalization_length(const char *utf8str, unsigned int total
while (1) {
if (!total_len || !*utf8str)
return norm_len;
utf8len = utf8_to_utf32(utf8str, MIN(total_len, 4), &utf32char);
utf8len = utf8_to_utf32(utf8str, min(total_len, 4U), &utf32char);
if (utf8len < 0) /* Invalid unicode; don't normalize anything */
return 0;
@@ -306,7 +305,7 @@ new_starter:
unicode_t utf32char;
int utf8len, pos;
utf8len = utf8_to_utf32(utf8str, MIN(total_len, 4), &utf32char);
utf8len = utf8_to_utf32(utf8str, min(total_len, 4U), &utf32char);
for (pos = 0;; pos++, str_pos++) {
unicode_t utf32norm;
u8 ccc;
Loaded 20 of 21 files, more files were not shown because too many files have changed in this diff. Show more