55 Commits
Author SHA1 Message Date
Ernesto A. Fernández 10557edf23 0.3.6
This release heavily reworks the node defragmentation and split logic to
fix a handful of rarely triggered data corruption bugs. The new version
is easier to understand and hopefully more robust.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-11-03 20:49:35 -03:00
Ernesto A. Fernández 620d6257f8 0.3.5
This release adds support for reading compressed files of arbitrary
on-disk size, which is becoming more and more necessary in recent ipsw
images.

A number of bugs are addressed for systems with little ram, reported by
Gilgamesh via email.

Support is added for the 6.6 kernel version, mainly thanks to Woody
Suwalski. I'm afraid the driver sometimes triggers a warning for recent
kernels, but I didn't manage to fix it in time for the new upstream
release, and it's better to at least get things to build.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-30 13:58:24 -03:00
Ernesto A. Fernández 225a17aa77 Set splice read/write methods explicitly
Several of xfstests (generic/249 for example) are failing on kernel 6.6
because of sendfile issues. Starting with upstream commit 36e2c7421f02
("fs: don't allow splice read/write without explicit ops"), the
->splice_read() and ->splice_write() methods need to be set explicitly,
or else we just get EINVAL when sendfile needs them. This change
happened in the 5.10 release, but I never noticed because I usually only
run tests on 5.3.

So, set the methods as required, taking into consideration the kernel
version because there was another related change in commit 2cb1e08985e3
("splice: Use filemap_splice_read() instead of
generic_file_splice_read()").

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-27 22:13:05 -03:00
Ernesto A. Fernández 6813d7e614 Ignore return value for generic_update_time()
Attempts to run xfstests for the 6.6 kernel fail brutally. Transactions
are getting aborted inside apfs_update_time(), which has never happened
before. The problem comes from upstream commit 541d4c798a59 ("fs: drop
the timespec64 arg from generic_update_time"): generic_update_time()
can't fail and used to always return a symbolic 0, but now instead
returns some flags - which my code mistakes for an error. We don't care
about the flags, so just ignore the return value for all kernel
versions.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-27 19:24:13 -03:00
Ernesto A. Fernández 4dab24ef6b Stop triggering kernel panics for silly reasons
I was reading the list of deprecated interfaces at [0] and it mentions
that the use of BUG() and BUG_ON() is discouraged in the kernel, and
that we should be prepared to fail gracefully even for impossible
situations.

I definitely agree with this: so far I've used BUG_ON() to implement
ASSERT(), but in practice I always turn that into a WARN_ON() while
debugging. Just leave it as WARN_ON() permanently.

I also use BUG() in apfs_query_storage() to mark an impossible query
tree flag. I can't imagine how this would ever be reached, but still
keep going and return an invalid storage type. This way the error can
get reported later, after calling either apfs_create_node() or
apfs_read_node(), which have been modified to fail gracefully and
complain with apfs_alert() in this situation. I also considered putting
an assertion in apfs_query_storage(), but that would require reordering
apfs.h and it seems unnecessarily messy.

Warnings should not be abused either, because of the panic_on_warn
option. Replace our one call to WARN_ON(), inside apfs_btree_replace(),
with another apfs_alert(). This function was already failing gracefully.

[0]: https://docs.kernel.org/process/deprecated.html#zero-length-and-one-element-arrays

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-27 15:44:47 -03:00
Ernesto A. Fernández 1d9630b194 Get rid of zero-length arrays
According to checkpatch.pl, the use of zero-length arrays is now
deprecated in the kernel. Replace them all with C99 flexible array
members.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-26 21:56:56 -03:00
Ernesto A. Fernández 2d6cdba8f6 Fix trivial style issues
Some code that wasn't originally written by me has a style that differs
slightly from the rest of the module. Since I've been refactoring
compress.c quite a lot lately, I have made the inconsistencies more
visible, so I think it's a good time to switch everything to the typical
kernel style.

For this purpose I'm running checkpatch.pl for the first time in years,
and it has detected a handful of other issues that I introduced myself.
Fix most of the issues now, but I prefer to leave the zero-length array
stuff for a separate patch.

For the record, at the moment I'm choosing to ignore style inconsistency
in the compression libraries, even in libzbitmap which I wrote myself. I
prefer to keep them as close to upstream as possible.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-26 21:44:30 -03:00
Woody Suwalski 1bca725a63 Fix for the kernel 6.6 build errors
[eafer: removed unnecessary setting of some timestamps]
Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-25 22:43:27 -03:00
Ernesto A. Fernández 12f526f2de Perform locking for compressed reads
For some reason I never remembered to take the big lock during reads of
compressed files. Do it. As always, my locking is coarse and hacky, and
I will have get back to it at some point.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-24 21:30:08 -03:00
Ernesto A. Fernández 2ec72ff747 Simplify gotos in apfs_compress_file_open()
After the recent refactoring of the compression code, the fail_enomem
label feels a little unnecessary. Get rid of it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-24 20:42:15 -03:00
Ernesto A. Fernández 133ed4fbf3 Don't swallow errors in compressed ->readpage()
While debugging the previous patch, I noticed that my implementation of
compressed ->readpage() from f9ca9061bb ("Implement mmap for
compressed files") has a serious issue: if something goes wrong during
the read, the error code is swallowed and stale garbage becomes visible
to the user.

I messed this up because I was following ext4_read_inline_page(), which
first calls ext4_read_inline_data() and then, without checking the
return code, calls SetPageUptodate(). But ext4_read_inline_data() can't
actually fail, so this is not a problem. In my version the function
called is apfs_compress_file_read_page(), which does fail sometimes,
leaving a stale page marked as updated. Fix it, and also return the
error code correctly.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-19 23:33:21 -03:00
Ernesto A. Fernández 71b11b377b Unify read of attr and rsrc compressed files
Currently, compressed files that are stored directly in the decmpfs
xattr are handled separately: they get entirely uncompressed on open,
and on reads the contents simply get copied. To get rid of this code
duplication, use a new apfs_compressed_data struct to read from both
inline and dstream xattrs, and let the regular resource-compressed code
handle reads for both types.

These changes weren't made exclusively for code quality. A compressed
file may be too big to fit inside decmpfs, but still small enough to fit
inside a xattr. If that happens, the resulting resource fork will be an
inline xattr, and the dstream read code implemented with e4bb06ecef
("Avoid reading whole compressed files into memory") will break down. So
the apfs_compressed_data struct is strictly necessary for the resource
case; using the same struct for both cases simplifies testing, because
inline resource compressed files are rare (I've seen them, but right now
I don't even have an image with one of them).

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-19 23:33:21 -03:00
Ernesto A. Fernández e6370242fa Try to preread the dstream for compressed files
After commit e4bb06ecef ("Avoid reading whole compressed files into
memory"), performance for compressed reads worsened by about 50%. The
problem is that, when we stopped reading the xattr all at once, we lost
the advantage of letting the kernel batch and schedule the buffer reads.

The ideal way to handle this would be to make a fake inode for the xattr
dstream, and let the kernel do readahead in the usual manner. I don't
feel very confident that I can get that right though, so I'm leaving it
for some other time.

For now, just request all the blocks as soon as a read begins (I would
have preferred to do it on open, but that's slower for some reason), and
ignore the results. Compressed data is supposed to be small so, as long
as there is enough memory, this has all the performance benefits of the
original implementation (in fact it's somewhat faster), but it still
avoids the problems with large virtual memory allocation.

Of course, unlike normal readahead, this will be useless if memory is
scarce, or if we don't read the whole file sequentially.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-13 22:01:15 -03:00
Ernesto A. Fernández 040358bd4a Check for failure in buffer head array allocation
I've neglected to check for failure after allocating an array in
apfs_nonsparse_dstream_read(). This is not a new bug: it got carried
over from the original xattr code, which has been around since
84c3ecebac ("Don't read dstream xattr blocks one by one"). Fix it now.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-11 20:28:54 -03:00
Ernesto A. Fernández e537eccc88 Fix apfs_compress_file_read_block() return code
In commit e4bb06ecef ("Avoid reading whole compressed files into
memory"), I moved part of the code of apfs_compress_file_read_block()
into a separate apfs_compress_file_read_from_block() that must call it.
I made a mistake, though: the original version of the function would
reset the return value before returning, but this version no longer does
it. This doesn't play well with the decompression functions for some of
the algorithms, which return decompressed lengths. So we get a "failed
to read block into buffer" message and the decompression is abandoned.
Fix this.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-10 19:04:34 -03:00
Ernesto A. Fernández c6de394653 Make all compressed reads use the page cache
When implementing compressed mmap I was forced to duplicate the code for
regular compressed reads, so that one version could copy to a user
buffer and the other to the kernel. To simplify this, get rid of the
regular version and instead use the generic read iterator, which will
make use of the same mmap code.

This should also allow regular compressed reads to take advantage of the
page cache.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-06 21:21:14 -03:00
Ernesto A. Fernández e99d7b4d51 Get rid of apfs_compress_file_mmap()
When compressed mmap was implemented, apfs_compress_file_mmap() was
added with the purpose of ignoring calls to ->page_mkwrite(). With the
last patch, I no longer allow compressed files to be opened for writing
at all, so I can just use regular ->mmap() for all files.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-06 20:18:31 -03:00
Ernesto A. Fernández a55f0effc7 Ban the opening of compressed files for writing
In commit 31df49c6d8 ("Ban writes to compressed files explicitly"), I
blocked writes to compressed files when they actually happen, not when
the file gets opened. My fear was that banning rw open entirely could
break other people's code, since I'm not sure who is using this driver
and how.

Now I've implemented compressed mmap, and blocking writes when they
happen is becoming a little too hacky. Do this properly, and wait to see
if anyone complains.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-06 19:08:10 -03:00
Ernesto A. Fernández f9ca9061bb Implement mmap for compressed files
Attempts to mmap compressed files currently fail with ENODEV. I guess I
had forgotten about compressed mmap. Implement it now, in read-only mode
like all compressed operations so far.

Since the existing read code works with userland buffers, it can't be
reused to copy stuff into the kernel. For now I've been forced to
duplicate a lot of code, but I think it might be a good idea to use the
generic read implementations instead. I'll look into that soon.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-10-05 22:36:52 -03:00
Ernesto A. Fernández 131953ffd8 Always lock pages before making them clean
Gilgamesh reports via email that his kernel often crashes on my call to
page_mkclean() due to "BUG_ON(!PageLocked(page))".

I should have expected problems such as this, since the locking in
apfs_end_buffer_write_sync() is not sane at all. Apparently back when I
wrote this, I only realized that I needed to lock the page because of
the above-mentioned panic. So I tried to take the lock, but this caused
a deadlock in generic/013 of xfstests. Instead of figuring out what the
problem was, I just replaced lock_page() with trylock_page(), to silence
the panic and still avoid deadlock. Of course this leaves us open to a
race, which in this case became visible because whatever was holding the
page released the lock before the check happened in page_mkclean().

In my defense, most of the locking in the driver is insane at this
point, so I don't usually put much thought into it unless I see actual
problems, as in this case.

So, replace trylock_page() with lock_page(), and figure out what was
causing the generic/013 deadlock in the first place. This was actually
very simple: a check of the contents of the problem page showed that
it's always a chunk-info block, and those are the only block types that
both get dirtied and added to the transaction. This is unnecessary, and
it causes some conflict on commit with whatever the kernel does to the
dirty blocks. Stop dirtying the cib blocks.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-09-29 20:54:45 -03:00
Gilgamesh 12dde7a08f Only commit in apfs_sync_fs() if wait == 1
I've been ignoring the 'wait' argument to apfs_sync_fs() so far, but
this means that transactions get committed twice in a row whenever a
sync is requested. Instead, just do nothing when wait == 0. If the
caller actually needs a sync, they will have to try again with wait == 1
later anyway.

[eafer: added comment and commit message]
Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-09-27 19:41:26 -03:00
Ernesto A. Fernández 19430cbdb2 Improve error messages in apfs_xattr_get_dstream()
I wrote apfs_xattr_get_dstream() long before error messages were changed
in d4797823ea ("Improve error reporting"), but the code wasn't
actually merged until recently, and I forgot to update the error
messages to our current standards. Fix that now.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-09-25 20:03:05 -03:00
Ernesto A. Fernández 1756ff18d3 Limit transaction size on systems without much ram
Gilgamesh reports via email that there is a kernel crash when you try to
copy an 800Mb file on a system with 512Mb of memory. The reason is that
I don't allow buffer heads to be reclaimed before commit time (I tried
it at some point and it was trickier than it seemed), and since commit
c5dfaa2de9 ("Allow bigger transactions"), each transaction can get as
big as 256Mb.

So, until I can get around to the reclaim issue (or hopefully get rid of
buffer heads), make transactions smaller for systems with little memory.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-09-22 16:28:05 -03:00
Ernesto A. Fernández 31df49c6d8 Ban writes to compressed files explicitly
The official implementation doesn't seem to truly support writes to
compressed files: when it's attempted, the file gets decompressed
transparently instead. I don't intend to that in this driver, unless
someone actually requests it. Users can just make a decompressed copy of
the file and replace the original.

By mistake, truncating compressed files is currently allowed, but since
compressed reads don't depend on the vfs-reported size, nothing seems to
happen. Other writes fail, but the error code is confusing and there is
no output on dmesg. Ban all writes explicitly, and explain the problem
in the console.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-09-21 19:50:09 -03:00
Ernesto A. Fernández e4bb06ecef Avoid reading whole compressed files into memory
Currently, compressed files saved in xattrs are read into memory all at
once, and later decompressed as needed. This has always required a large
memory allocation, which can be a problem in general, but especially for
32-bit archs which can't request more than 128Mb from vmalloc(). As
mentioned in commit 1e3fa28c4f ("Increase limit for compressed size
of files"), we are beginning to encounter files bigger than that limit.

Using the new apfs_nonsparse_dstream_read() to read from arbitrary xattr
offsets, rework the compressed reads so that we only allocate memory for
one compressed block at a time. For now the change is only for resource
xattrs, since I don't expect inline xattrs to ever be this big.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-09-20 15:41:49 -03:00
Ernesto A. Fernández a68325ef9e Spin xattr dstream reads into a new function
Lately I'm encountering more and more compressed files that are too
large for their xattrs to be read into memory all at once, so I'll need
to be able to read xattrs from arbitrary offsets. The code for this is
very similar to what's already inside apfs_xattr_extents_read(), so spin
it into a new apfs_nonsparse_dstream_read() with a generic interface
that can later be reused from compress.c.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-09-20 15:34:00 -03:00
Ernesto A. Fernández af0671f58f 0.3.4
This release fixes the build for the Linux 6.5 release candidate, and it
should also fix a long-standing mmap bug in Linux 5.14 and above, which
resulted in null pointer dereference.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-08-14 23:50:19 -03:00
Ernesto A. Fernández 740d7db44b Fix mmap for kernels 5.14 and above
Adamantinum reports trouble while attempting to use apfs as root with
kernel version 6.4.8:

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

His callstack shows an npd in mmap, and the bug can be easily reproduced
by generic/029, but not in kernel version 5.3, where I usually run my
tests. Via bisection I tracked the problem back to kernel commit
0af573780b0b ("mm: require ->set_page_dirty to be explicitly wired up").
Starting with kernel 5.14, the ->set_page_dirty() address space method
gets called unconditionally, without checking for null first, so set it
explicitly.

Kernel 5.18 replaced ->set_page_dirty() with ->dirty_folio(), so take
care of that as well.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-08-14 19:50:22 -03:00
Ernesto A. Fernández bc2be91bb4 Update FMODE_* stuff for the 6.5 kernel
The build is broken for the Linux 6.5 release candidate:

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

The fmode_t type can no longer be used for block open flags, and we must
switch to blk_mode_t instead. FMODE_EXCL is no longer needed, since just
having a holder already implies it, but this requires a change to the
blkdev_put() api. There is also a new argument to blkdev_get_by_path()
which can be safely ignored. And finally, sb->s_mode is gone, but it
doesn't seem to make any difference.

Update all the code to the new apis.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-08-08 20:58:11 -03:00
Ernesto A. Fernández 5c4773c036 Convert all strcpy() to strscpy()
The build appears to be broken for some configurations on the Linux 6.5
release candidate:

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

Some sort of static overflow check is going on for strcpy(), and several
of my calls are triggering false positives. Just be safe and switch them
all to strscpy(). The qstr stuff in particular looked a bit too weird
anyway.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-08-08 19:14:10 -03:00
Ernesto A. Fernández 1e3fa28c4f Increase limit for compressed size of files
A recent cryptex image contains a file with a compressed size above the
current 128Mb limit. This limit exists because of vmalloc(), and it can
be increased (at least for 64-bit archs). Do that for now, but I really
should try to restructure the code to use smaller kmalloc() allocations
instead.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-06-07 20:40:53 -03:00
17 changed files with 1957 additions and 1190 deletions

No files matched your search

+1
View File
@@ -9,3 +9,4 @@
*.a
*.mod
tags
version.h
+3
View File
@@ -6,6 +6,7 @@
KERNELRELEASE ?= $(shell uname -r)
KERNEL_DIR ?= /lib/modules/$(KERNELRELEASE)/build
PWD := $(shell pwd)
GIT_COMMIT = $(shell git describe HEAD | tail -c 9)
obj-m = apfs.o
apfs-y := btree.o compress.o dir.o extents.o file.o inode.o key.o libzbitmap.o \
@@ -14,8 +15,10 @@ apfs-y := btree.o compress.o dir.o extents.o file.o inode.o key.o libzbitmap.o \
spaceman.o super.o symlink.o transaction.o unicode.o xattr.o xfield.o
default:
@printf '#define GIT_COMMIT\t"%s"\n' $(GIT_COMMIT) > version.h
make -C $(KERNEL_DIR) M=$(PWD)
install:
make -C $(KERNEL_DIR) M=$(PWD) modules_install
clean:
rm -f version.h
make -C $(KERNEL_DIR) M=$(PWD) clean
+65 -12
View File
@@ -13,8 +13,6 @@
#include <linux/version.h>
#include "apfs_raw.h"
#define APFS_MODULE_ID_STRING "linux-apfs by EA Fernández"
#define EFSBADCRC EBADMSG /* Bad CRC detected */
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
@@ -124,7 +122,7 @@ static inline bool apfs_node_has_fixed_kv_size(struct apfs_node *node)
struct apfs_spaceman {
struct apfs_spaceman_phys *sm_raw; /* On-disk spaceman structure */
struct buffer_head *sm_bh; /* Buffer head for @sm_raw */
struct buffer_head *sm_ip; /* Current internal pool */
struct apfs_nxsb_info *sm_nxi; /* Container superblock */
u32 sm_blocks_per_chunk; /* Blocks covered by a bitmap block */
u32 sm_chunks_per_cib; /* Chunk count in a chunk-info block */
@@ -140,6 +138,15 @@ struct apfs_spaceman {
*/
u64 sm_free_cache_base;
u64 sm_free_cache_blkcnt;
/* Shift to match an ip block with its bitmap in the array */
int sm_ip_bmaps_shift;
/* Mask to find an ip block's offset inside its ip bitmap */
u32 sm_ip_bmaps_mask;
/* Number of ip bitmaps */
u32 sm_ip_bmaps_count;
/* List of ip bitmaps, in order */
struct buffer_head *sm_ip_bmaps[];
};
#define TRANSACTION_MAIN_QUEUE_MAX 4096
@@ -219,7 +226,7 @@ struct apfs_nxsb_info {
unsigned long nx_blocksize;
unsigned char nx_blocksize_bits;
struct apfs_spaceman nx_spaceman;
struct apfs_spaceman *nx_spaceman;
struct apfs_nx_transaction nx_transaction;
/* For now, a single semaphore for every operation */
@@ -283,6 +290,8 @@ struct apfs_sb_info {
struct list_head list; /* List of mounted volumes in container */
struct apfs_superblock *s_vsb_raw; /* On-disk volume sb */
dev_t s_anon_dev; /* Anonymous device for this volume-snapshot */
char *s_snap_name; /* Label for the mounted snapshot */
u64 s_snap_xid; /* Transaction id for mounted snapshot */
@@ -299,6 +308,7 @@ 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 */
};
@@ -319,8 +329,10 @@ static inline bool apfs_is_sealed(struct super_block *sb)
* apfs_vol_is_encrypted - Check if a volume is encrypting files
* @sb: superblock
*/
static inline bool apfs_vol_is_encrypted(struct super_block *sb) {
static inline bool apfs_vol_is_encrypted(struct super_block *sb)
{
struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
return (vsb_raw->apfs_fs_flags & cpu_to_le64(APFS_FS_UNENCRYPTED)) == 0;
}
@@ -339,7 +351,7 @@ static inline struct apfs_nxsb_info *APFS_NXI(struct super_block *sb)
*/
static inline struct apfs_spaceman *APFS_SM(struct super_block *sb)
{
return &APFS_NXI(sb)->nx_spaceman;
return APFS_NXI(sb)->nx_spaceman;
}
static inline bool apfs_is_case_insensitive(struct super_block *sb)
@@ -611,7 +623,7 @@ static inline u64 apfs_cat_cnid(struct apfs_key_header *key)
*/
struct apfs_query {
struct apfs_node *node; /* Node being searched */
struct apfs_key *key; /* What the query is looking for */
struct apfs_key key; /* What the query is looking for */
struct apfs_query *parent; /* Query for parent node */
unsigned int flags;
@@ -646,7 +658,9 @@ static inline u32 apfs_query_storage(struct apfs_query *query)
return APFS_OBJ_PHYSICAL;
if (query->flags & APFS_QUERY_OMAP_SNAP)
return APFS_OBJ_PHYSICAL;
BUG();
/* Absurd, but don't panic: let the callers fail and report it */
return -1;
}
/*
@@ -774,6 +788,15 @@ struct apfs_xattr {
bool has_dstream;
};
struct apfs_compressed_data {
bool has_dstream;
u64 size;
union {
struct apfs_dstream_info *dstream;
void *data;
};
};
/*
* Report function name and line number for the message types that are likely
* to signal a bug, to make things easier for reporters. Don't do this for the
@@ -788,7 +811,7 @@ struct apfs_xattr {
#define apfs_info(sb, fmt, ...) apfs_msg(sb, KERN_INFO, NULL, 0, fmt, ##__VA_ARGS__)
#ifdef CONFIG_APFS_DEBUG
#define ASSERT(expr) BUG_ON(!(expr))
#define ASSERT(expr) WARN_ON(!(expr))
#define apfs_debug(sb, fmt, ...) apfs_msg(sb, KERN_DEBUG, __func__, __LINE__, fmt, ##__VA_ARGS__)
#else
#define ASSERT(expr) ((void)0)
@@ -808,15 +831,17 @@ do { \
} while (0)
/* btree.c */
extern struct apfs_node *apfs_query_root(const struct apfs_query *query);
extern struct apfs_query *apfs_alloc_query(struct apfs_node *node,
struct apfs_query *parent);
extern void apfs_free_query(struct apfs_query *query);
extern int apfs_btree_query(struct super_block *sb, struct apfs_query **query);
extern int apfs_omap_lookup_block(struct super_block *sb, struct apfs_omap *omap,
u64 id, u64 *block, bool write);
extern int apfs_omap_lookup_block(struct super_block *sb, struct apfs_omap *omap, u64 id, u64 *block, bool write);
extern int apfs_omap_lookup_newest_block(struct super_block *sb, struct apfs_omap *omap, u64 id, u64 *block, bool write);
extern int apfs_create_omap_rec(struct super_block *sb, u64 oid, u64 bno);
extern int apfs_delete_omap_rec(struct super_block *sb, u64 oid);
extern int apfs_query_join_transaction(struct apfs_query *query);
extern int __apfs_btree_insert(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
extern int apfs_btree_insert(struct apfs_query *query, void *key, int key_len,
void *val, int val_len);
extern int apfs_btree_remove(struct apfs_query *query);
@@ -891,8 +916,11 @@ extern loff_t apfs_remap_file_range(struct file *src_file, loff_t off, struct fi
extern int apfs_clone_file_range(struct file *src_file, loff_t off, struct file *dst_file, loff_t destoff, u64 len);
#endif
extern int apfs_clone_extents(struct apfs_dstream_info *dstream, u64 new_id);
extern int apfs_nonsparse_dstream_read(struct apfs_dstream_info *dstream, void *buf, size_t count, u64 offset);
extern void apfs_nonsparse_dstream_preread(struct apfs_dstream_info *dstream);
/* file.c */
extern int apfs_file_mmap(struct file *file, struct vm_area_struct *vma);
extern int apfs_fsync(struct file *file, loff_t start, loff_t end, int datasync);
/* inode.c */
@@ -920,7 +948,11 @@ extern int apfs_setattr(struct mnt_idmap *idmap,
struct dentry *dentry, struct iattr *iattr);
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
extern int apfs_update_time(struct inode *inode, struct timespec64 *time, int flags);
#else
extern int apfs_update_time(struct inode *inode, int flags);
#endif
long apfs_dir_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
long apfs_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
@@ -969,7 +1001,7 @@ extern __printf(5, 6) void apfs_msg(struct super_block *sb, const char *prefix,
extern struct apfs_node *apfs_read_node(struct super_block *sb, u64 oid,
u32 storage, bool write);
extern void apfs_update_node(struct apfs_node *node);
extern int apfs_delete_node(struct apfs_query *query);
extern int apfs_delete_node(struct apfs_node *node, int type);
extern int apfs_node_query(struct super_block *sb, struct apfs_query *query);
extern void apfs_node_query_first(struct apfs_query *query);
extern int apfs_omap_map_from_query(struct apfs_query *query, struct apfs_omap_map *map);
@@ -977,6 +1009,7 @@ extern int apfs_node_split(struct apfs_query *query);
extern int apfs_node_locate_key(struct apfs_node *node, int index, int *off);
extern void apfs_node_free(struct apfs_node *node);
extern void apfs_node_free_range(struct apfs_node *node, u16 off, u16 len);
extern bool apfs_node_has_room(struct apfs_node *node, int length, bool replace);
extern int apfs_node_replace(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
extern int apfs_node_insert(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
extern int apfs_create_single_rec_node(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
@@ -1033,6 +1066,9 @@ extern int apfs_xattr_set(struct inode *inode, const char *name, const void *val
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);
extern int apfs_compressed_data_read(struct apfs_compressed_data *cdata, void *buf, size_t count, u64 offset);
/* xfield.c */
extern int apfs_find_xfield(u8 *xfields, int len, u8 xtype, char **xval);
@@ -1046,6 +1082,7 @@ extern int apfs_insert_xfield(u8 *buffer, int buflen,
*/
/* compress.c */
extern const struct address_space_operations apfs_compress_aops;
extern const struct file_operations apfs_compress_file_operations;
/* dir.c */
@@ -1066,6 +1103,18 @@ extern const struct inode_operations apfs_symlink_inode_operations;
/* xattr.c */
extern const struct xattr_handler *apfs_xattr_handlers[];
/**
* apfs_assert_query_is_valid - Assert that all of a query's ancestors are set
* @query: the query to check
*
* A query may lose some of its ancestors during a node split, but nothing
* should be done to such a query until it gets refreshed.
*/
static inline void apfs_assert_query_is_valid(const struct apfs_query *query)
{
ASSERT(apfs_node_is_root(apfs_query_root(query)));
}
/*
* TODO: the following are modified variants of buffer head functions that will
* work with the shared block device for the container. The correct approach
@@ -1093,7 +1142,11 @@ apfs_getblk(struct super_block *sb, sector_t block)
{
struct buffer_head *bh;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
bh = __getblk_gfp(APFS_NXI(sb)->nx_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
#else
bh = bdev_getblk(APFS_NXI(sb)->nx_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
#endif
if (bh)
set_buffer_uptodate(bh);
return bh;
+14 -14
View File
@@ -306,7 +306,7 @@ struct apfs_wrapped_crypto_state {
__le32 key_os_version;
__le16 key_revision;
__le16 key_len;
u8 persistent_key[0];
u8 persistent_key[];
} __packed;
/*
@@ -503,7 +503,7 @@ struct apfs_dir_stats_val {
struct apfs_sibling_val {
__le64 parent_id;
__le16 name_len;
u8 name[0];
u8 name[];
} __packed;
/*
@@ -622,7 +622,7 @@ enum {
struct apfs_drec_key {
struct apfs_key_header hdr;
__le16 name_len;
u8 name[0];
u8 name[];
} __packed;
/*
@@ -632,7 +632,7 @@ struct apfs_drec_key {
struct apfs_drec_hashed_key {
struct apfs_key_header hdr;
__le32 name_len_and_hash;
u8 name[0];
u8 name[];
} __packed;
/*
@@ -641,7 +641,7 @@ struct apfs_drec_hashed_key {
struct apfs_xattr_key {
struct apfs_key_header hdr;
__le16 name_len;
u8 name[0];
u8 name[];
} __packed;
/*
@@ -660,7 +660,7 @@ struct apfs_snap_metadata_key {
struct apfs_snap_name_key {
struct apfs_key_header hdr;
__le16 name_len;
u8 name[0];
u8 name[];
} __packed;
/*
@@ -1278,7 +1278,7 @@ enum {
struct apfs_xattr_val {
__le16 flags;
__le16 xdata_len;
u8 xdata[0];
u8 xdata[];
} __packed;
/*
@@ -1372,7 +1372,7 @@ struct apfs_file_info_key {
struct apfs_file_data_hash_val {
__le16 hashed_len;
u8 hash_size;
u8 hash[0];
u8 hash[];
} __packed;
#define APFS_FILE_INFO_DATA_HASH 1
@@ -1435,7 +1435,7 @@ struct apfs_compress_rsrc_data {
struct apfs_compress_rsrc_block {
__le32 offs;
__le32 size;
} __packed block[0];
} __packed block[];
} __packed;
/*
@@ -1456,7 +1456,7 @@ struct apfs_snap_metadata_val {
__le32 extentref_tree_type;
__le32 flags;
__le16 name_len;
u8 name[0];
u8 name[];
} __packed;
/*
@@ -1475,7 +1475,7 @@ struct apfs_snap_meta_ext {
__le32 sme_version;
__le32 sme_flags;
__le64 sme_snap_xid;
char sme_uuid[UUID_SIZE];
char sme_uuid[UUID_SIZE];
__le64 sme_token;
} __packed;
@@ -1503,11 +1503,11 @@ enum {
* Structure of a single entry in the keybag
*/
struct apfs_keybag_entry {
char ke_uuid[UUID_SIZE];
char ke_uuid[UUID_SIZE];
__le16 ke_tag;
__le16 ke_keylen;
__le32 padding;
u8 ke_keydata[0];
u8 ke_keydata[];
} __packed;
#define APFS_KEYBAG_VERSION 2
@@ -1520,7 +1520,7 @@ struct apfs_kb_locker {
__le16 kl_nkeys;
__le32 kl_nbytes;
__le64 padding;
struct apfs_keybag_entry kl_entries[0];
struct apfs_keybag_entry kl_entries[];
} __packed;
#endif /* _APFS_RAW_H */
+346 -199
View File
File diff suppressed because it is too large. Load diff
+318 -355
View File
File diff suppressed because it is too large. Load diff
+62 -37
View File
@@ -99,24 +99,25 @@ static struct apfs_query *apfs_dentry_lookup(struct inode *dir,
{
struct super_block *sb = dir->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
u64 cnid = apfs_ino(dir);
bool hashed = apfs_is_normalization_insensitive(sb);
int err;
apfs_init_drec_key(sb, cnid, child->name, child->len, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return ERR_PTR(-ENOMEM);
query->key = &key;
apfs_init_drec_key(sb, cnid, child->name, child->len, &query->key);
/*
* Distinct filenames in the same directory may (rarely) share the same
* hash. The query code cannot handle that because their order in the
* b-tree would depend on their unnormalized original names. Just get
* hash. The query code cannot handle that because their order in the
* b-tree would depend on their unnormalized original names. Just get
* all the candidates and check them one by one.
*
* This is very wasteful for normalization-sensitive filesystems: there
* are no hashes so we just check every single file in the directory for
* no reason. This would be easy to avoid but does it matter? (TODO)
*/
query->flags |= APFS_QUERY_CAT | APFS_QUERY_ANY_NAME | APFS_QUERY_EXACT;
do {
@@ -128,6 +129,14 @@ static struct apfs_query *apfs_dentry_lookup(struct inode *dir,
goto fail;
} while (unlikely(apfs_filename_cmp(sb, child->name, child->len, drec->name, drec->name_len)));
/*
* We may need to refresh the query later, but the refresh code doesn't
* know how to deal with hash collisions. Instead set the key to the
* unnormalized name and pretend that this was never a multiple query
* in the first place.
*/
query->key.name = drec->name;
query->flags &= ~(APFS_QUERY_MULTIPLE | APFS_QUERY_DONE | APFS_QUERY_NEXT);
return query;
fail:
@@ -173,7 +182,6 @@ static int apfs_readdir(struct file *file, struct dir_context *ctx)
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_key key;
struct apfs_query *query;
u64 cnid = apfs_ino(inode);
loff_t pos;
@@ -193,8 +201,7 @@ static int apfs_readdir(struct file *file, struct dir_context *ctx)
}
/* We want all the children for the cnid, regardless of the name */
apfs_init_drec_key(sb, cnid, NULL /* name */, 0 /* name_len */, &key);
query->key = &key;
apfs_init_drec_key(sb, cnid, NULL /* name */, 0 /* name_len */, &query->key);
query->flags = APFS_QUERY_CAT | APFS_QUERY_MULTIPLE | APFS_QUERY_EXACT;
pos = ctx->pos - 2;
@@ -268,7 +275,7 @@ static int apfs_build_dentry_unhashed_key(struct qstr *qname, u64 parent_id,
apfs_key_set_hdr(APFS_TYPE_DIR_REC, parent_id, key);
key->name_len = cpu_to_le16(namelen);
strcpy(key->name, qname->name);
strscpy(key->name, qname->name, namelen);
*key_p = key;
return key_len;
@@ -297,7 +304,7 @@ static int apfs_build_dentry_hashed_key(struct qstr *qname, u64 hash, u64 parent
apfs_key_set_hdr(APFS_TYPE_DIR_REC, parent_id, key);
key->name_len_and_hash = cpu_to_le32(namelen | hash);
strcpy(key->name, qname->name);
strscpy(key->name, qname->name, namelen);
*key_p = key;
return key_len;
@@ -361,7 +368,6 @@ static int apfs_create_dentry_rec(struct inode *inode, struct qstr *qname,
{
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
void *raw_key = NULL;
struct apfs_drec_val *raw_val = NULL;
@@ -369,21 +375,20 @@ static int apfs_create_dentry_rec(struct inode *inode, struct qstr *qname,
bool hashed = apfs_is_normalization_insensitive(sb);
int ret;
apfs_init_drec_key(sb, parent_id, qname->name, qname->len, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_drec_key(sb, parent_id, qname->name, qname->len, &query->key);
query->flags |= APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
if (ret && ret != -ENODATA) {
apfs_err(sb, "query failed in dir 0x%llx (hash 0x%llx)", parent_id, key.number);
apfs_err(sb, "query failed in dir 0x%llx (hash 0x%llx)", parent_id, query->key.number);
goto fail;
}
if (hashed)
key_len = apfs_build_dentry_hashed_key(qname, key.number, parent_id,
key_len = apfs_build_dentry_hashed_key(qname, query->key.number, parent_id,
(struct apfs_drec_hashed_key **)&raw_key);
else
key_len = apfs_build_dentry_unhashed_key(qname, parent_id,
@@ -401,7 +406,7 @@ static int apfs_create_dentry_rec(struct inode *inode, struct qstr *qname,
/* TODO: deal with hash collisions */
ret = apfs_btree_insert(query, raw_key, key_len, raw_val, val_len);
if (ret)
apfs_err(sb, "insertion failed in dir 0x%llx (hash 0x%llx)", parent_id, key.number);
apfs_err(sb, "insertion failed in dir 0x%llx (hash 0x%llx)", parent_id, query->key.number);
fail:
kfree(raw_val);
@@ -434,7 +439,7 @@ static int apfs_build_sibling_val(struct dentry *dentry,
val->parent_id = cpu_to_le64(apfs_ino(parent));
val->name_len = cpu_to_le16(namelen);
strcpy(val->name, qname->name);
strscpy(val->name, qname->name, namelen);
*val_p = val;
return val_len;
@@ -453,18 +458,16 @@ static int apfs_create_sibling_link_rec(struct dentry *dentry,
{
struct super_block *sb = dentry->d_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query = NULL;
struct apfs_sibling_link_key raw_key;
struct apfs_sibling_val *raw_val;
int val_len;
int ret;
apfs_init_sibling_link_key(apfs_ino(inode), sibling_id, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_sibling_link_key(apfs_ino(inode), sibling_id, &query->key);
query->flags |= APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
@@ -503,17 +506,15 @@ static int apfs_create_sibling_map_rec(struct dentry *dentry,
{
struct super_block *sb = dentry->d_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query = NULL;
struct apfs_sibling_map_key raw_key;
struct apfs_sibling_map_val raw_val;
int ret;
apfs_init_sibling_map_key(sibling_id, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_sibling_map_key(sibling_id, &query->key);
query->flags |= APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
@@ -601,7 +602,13 @@ static int apfs_create_dentry(struct dentry *dentry, struct inode *inode)
}
/* Now update the parent inode */
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
parent->i_mtime = parent->i_ctime = current_time(inode);
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
parent->i_mtime = inode_set_ctime_current(parent);
#else
inode_set_mtime_to_ts(parent, inode_set_ctime_current(parent));
#endif
++APFS_I(parent)->i_nchildren;
apfs_inode_join_transaction(parent->i_sb, parent);
return 0;
@@ -841,7 +848,11 @@ static int __apfs_link(struct dentry *old_dentry, struct dentry *dentry)
/* First update the inode's link count */
inc_nlink(inode);
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
inode->i_ctime = current_time(inode);
#else
inode_set_ctime_current(inode);
#endif
apfs_inode_join_transaction(inode->i_sb, inode);
if (inode->i_nlink == 2) {
@@ -915,17 +926,15 @@ static int apfs_delete_sibling_link_rec(struct dentry *dentry, u64 sibling_id)
struct super_block *sb = dentry->d_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct inode *inode = d_inode(dentry);
struct apfs_key key;
struct apfs_query *query = NULL;
int ret;
ASSERT(sibling_id);
apfs_init_sibling_link_key(apfs_ino(inode), sibling_id, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_sibling_link_key(apfs_ino(inode), sibling_id, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -958,17 +967,15 @@ static int apfs_delete_sibling_map_rec(struct dentry *dentry, u64 sibling_id)
{
struct super_block *sb = dentry->d_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query = NULL;
int ret;
ASSERT(sibling_id);
apfs_init_sibling_map_key(sibling_id, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_sibling_map_key(sibling_id, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -1030,7 +1037,13 @@ static int apfs_delete_dentry(struct dentry *dentry)
}
/* Now update the parent inode */
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
parent->i_mtime = parent->i_ctime = current_time(parent);
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
parent->i_mtime = inode_set_ctime_current(parent);
#else
inode_set_mtime_to_ts(parent, inode_set_ctime_current(parent));
#endif
--APFS_I(parent)->i_nchildren;
apfs_inode_join_transaction(sb, parent);
return err;
@@ -1089,7 +1102,7 @@ static int apfs_sibling_link_from_query(struct apfs_query *query,
*name = kmalloc(namelen, GFP_KERNEL);
if (!*name)
return -ENOMEM;
strcpy(*name, siblink->name);
strscpy(*name, siblink->name, namelen);
*parent = le64_to_cpu(siblink->parent_id);
return 0;
}
@@ -1107,18 +1120,14 @@ static int apfs_find_primary_link(struct inode *inode, char **name, u64 *parent)
{
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
int err;
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
apfs_init_sibling_link_key(apfs_ino(inode), 0 /* sibling_id */, &key);
query->key = &key;
query->flags |= APFS_QUERY_CAT | APFS_QUERY_ANY_NUMBER |
APFS_QUERY_EXACT;
apfs_init_sibling_link_key(apfs_ino(inode), 0 /* sibling_id */, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_ANY_NUMBER | APFS_QUERY_EXACT;
/* The primary link is the one with the lowest sibling id */
*name = NULL;
@@ -1194,7 +1203,13 @@ static int apfs_create_orphan_link(struct inode *inode)
}
/* Now update the child count for private-dir */
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
priv_dir->i_mtime = priv_dir->i_ctime = current_time(priv_dir);
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
priv_dir->i_mtime = inode_set_ctime_current(priv_dir);
#else
inode_set_mtime_to_ts(priv_dir, inode_set_ctime_current(priv_dir));
#endif
++APFS_I(priv_dir)->i_nchildren;
apfs_inode_join_transaction(sb, priv_dir);
@@ -1239,7 +1254,13 @@ int apfs_delete_orphan_link(struct inode *inode)
}
/* Now update the child count for private-dir */
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
priv_dir->i_mtime = priv_dir->i_ctime = current_time(priv_dir);
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
priv_dir->i_mtime = inode_set_ctime_current(priv_dir);
#else
inode_set_mtime_to_ts(priv_dir, inode_set_ctime_current(priv_dir));
#endif
--APFS_I(priv_dir)->i_nchildren;
apfs_inode_join_transaction(sb, priv_dir);
@@ -1330,7 +1351,11 @@ static int __apfs_unlink(struct inode *dir, struct dentry *dentry)
if (err)
goto fail;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
inode->i_ctime = dir->i_ctime;
#else
inode_set_ctime_to_ts(inode, inode_get_ctime(dir));
#endif
/* TODO: defer write of the primary name? */
err = apfs_update_inode(inode, primary_name);
if (err)
+1 -1
View File
@@ -1,5 +1,5 @@
PACKAGE_NAME="linux-apfs-rw"
PACKAGE_VERSION="0.3.3"
PACKAGE_VERSION="0.3.6"
BUILT_MODULE_NAME[0]="apfs"
DEST_MODULE_LOCATION[0]="/extra"
+164 -28
View File
@@ -5,6 +5,7 @@
#include <linux/buffer_head.h>
#include <linux/slab.h>
#include <linux/blk_types.h>
#include "apfs.h"
#define MAX(X, Y) ((X) <= (Y) ? (Y) : (X))
@@ -46,7 +47,7 @@ int apfs_extent_from_query(struct apfs_query *query,
char *raw = query->node->object.data;
u64 ext_len;
if(!apfs_is_sealed(sb)) {
if (!apfs_is_sealed(sb)) {
struct apfs_file_extent_val *ext = NULL;
struct apfs_file_extent_key *ext_key = NULL;
@@ -138,7 +139,7 @@ static int apfs_extent_read(struct apfs_dstream_info *dstream, sector_t dsblock,
ret = -ENOMEM;
goto done;
}
query->key = &key;
query->key = key;
query->flags = apfs_is_sealed(sb) ? APFS_QUERY_FEXT : APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
@@ -412,7 +413,6 @@ static int apfs_update_tail_extent(struct apfs_dstream_info *dstream, const stru
{
struct super_block *sb = dstream->ds_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_file_extent_key raw_key;
struct apfs_file_extent_val raw_val;
@@ -430,13 +430,11 @@ static int apfs_update_tail_extent(struct apfs_dstream_info *dstream, const stru
new_crypto = 0;
raw_val.crypto_id = cpu_to_le64(new_crypto);
/* We want the last extent record */
apfs_init_file_extent_key(extent_id, -1, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
/* We want the last extent record */
apfs_init_file_extent_key(extent_id, -1, &query->key);
query->flags = APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
@@ -601,7 +599,7 @@ static int apfs_update_mid_extent(struct apfs_dstream_info *dstream, const struc
raw_key.logical_addr = cpu_to_le64(extent->logical_addr);
raw_val.len_and_flags = cpu_to_le64(extent->len);
raw_val.phys_block_num = cpu_to_le64(extent->phys_block_num);
if(apfs_vol_is_encrypted(sb))
if (apfs_vol_is_encrypted(sb))
new_crypto = extent_id;
else
new_crypto = 0;
@@ -613,7 +611,7 @@ search_and_insert:
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
query->key = key;
query->flags = APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
@@ -800,7 +798,6 @@ static int apfs_insert_phys_extent(struct apfs_dstream_info *dstream, const stru
struct super_block *sb = dstream->ds_sb;
struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
struct apfs_node *extref_root;
struct apfs_key key;
struct apfs_query *query = NULL;
struct apfs_phys_extent pext;
u64 blkcnt = extent->len >> sb->s_blocksize_bits;
@@ -829,8 +826,7 @@ static int apfs_insert_phys_extent(struct apfs_dstream_info *dstream, const stru
* one.
*/
last_bno = extent->phys_block_num + blkcnt - 1;
apfs_init_extent_key(last_bno, &key);
query->key = &key;
apfs_init_extent_key(last_bno, &query->key);
query->flags = APFS_QUERY_EXTENTREF;
ret = apfs_btree_query(sb, &query);
@@ -1152,7 +1148,6 @@ static int apfs_create_hole(struct apfs_dstream_info *dstream, u64 start, u64 en
{
struct super_block *sb = dstream->ds_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_file_extent_key raw_key;
struct apfs_file_extent_val raw_val;
@@ -1172,11 +1167,10 @@ static int apfs_create_hole(struct apfs_dstream_info *dstream, u64 start, u64 en
raw_val.phys_block_num = cpu_to_le64(0); /* It's a hole... */
raw_val.crypto_id = cpu_to_le64(apfs_vol_is_encrypted(sb) ? extent_id : 0);
apfs_init_file_extent_key(extent_id, start, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_file_extent_key(extent_id, start, &query->key);
query->flags = APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
@@ -1259,7 +1253,6 @@ static int apfs_range_in_snap(struct super_block *sb, u64 bno, u64 blkcnt, bool
{
struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
struct apfs_node *extref_root = NULL;
struct apfs_key key;
struct apfs_query *query = NULL;
struct apfs_phys_extent pext = {0};
int ret;
@@ -1286,8 +1279,7 @@ static int apfs_range_in_snap(struct super_block *sb, u64 bno, u64 blkcnt, bool
goto out;
}
apfs_init_extent_key(bno, &key);
query->key = &key;
apfs_init_extent_key(bno, &query->key);
query->flags = APFS_QUERY_EXTENTREF;
ret = apfs_btree_query(sb, &query);
@@ -1499,18 +1491,15 @@ static int apfs_shrink_dstream_last_extent(struct apfs_dstream_info *dstream, lo
{
struct super_block *sb = dstream->ds_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_file_extent tail;
u64 extent_id = dstream->ds_id;
int ret = 0;
apfs_init_file_extent_key(extent_id, -1, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_file_extent_key(extent_id, -1, &query->key);
query->flags = APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
@@ -1703,7 +1692,13 @@ int apfs_clone_file_range(struct file *src_file, loff_t off, struct file *dst_fi
}
src_ds->ds_shared = true;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
dst_inode->i_mtime = dst_inode->i_ctime = current_time(dst_inode);
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
dst_inode->i_mtime = inode_set_ctime_current(dst_inode);
#else
inode_set_mtime_to_ts(dst_inode, inode_set_ctime_current(dst_inode));
#endif
dst_inode->i_size = src_inode->i_size;
dst_ai->i_key_class = src_ai->i_key_class;
dst_ai->i_int_flags = src_ai->i_int_flags;
@@ -1755,18 +1750,15 @@ fail:
static int apfs_extent_create_record(struct super_block *sb, u64 dstream_id, struct apfs_file_extent *extent)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query = NULL;
struct apfs_file_extent_val raw_val;
struct apfs_file_extent_key raw_key;
int ret = 0;
apfs_init_file_extent_key(dstream_id, extent->logical_addr, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_file_extent_key(dstream_id, extent->logical_addr, &query->key);
query->flags = APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -1830,7 +1822,7 @@ restart:
ret = -ENOMEM;
goto out;
}
query->key = &key;
query->key = key;
query->flags = APFS_QUERY_EXTENTREF;
ret = apfs_btree_query(sb, &query);
@@ -1970,7 +1962,7 @@ restart:
ret = -ENOMEM;
goto out;
}
query->key = &key;
query->key = key;
query->flags = APFS_QUERY_EXTENTREF;
ret = apfs_btree_query(sb, &query);
@@ -2130,3 +2122,147 @@ int apfs_clone_extents(struct apfs_dstream_info *dstream, u64 new_id)
}
return 0;
}
/**
* apfs_nonsparse_dstream_read - Read from a dstream without holes
* @dstream: dstream to read
* @buf: destination buffer
* @count: exact number of bytes to read
* @offset: dstream offset to read from
*
* Returns 0 on success or a negative error code in case of failure.
*/
int apfs_nonsparse_dstream_read(struct apfs_dstream_info *dstream, void *buf, size_t count, u64 offset)
{
struct super_block *sb = dstream->ds_sb;
u64 logical_start_block, logical_end_block, log_bno, blkcnt, idx;
struct buffer_head **bhs = NULL;
int ret = 0;
/* Save myself from thinking about overflow here */
if (count >= APFS_MAX_FILE_SIZE || offset >= APFS_MAX_FILE_SIZE) {
apfs_err(sb, "dstream read overflow (0x%llx-0x%llx)", offset, (unsigned long long)count);
return -EFBIG;
}
if (offset + count > dstream->ds_size) {
apfs_err(sb, "reading past the end (0x%llx-0x%llx)", offset, (unsigned long long)count);
/* No caller is expected to legitimately read out-of-bounds */
return -EFSCORRUPTED;
}
logical_start_block = offset >> sb->s_blocksize_bits;
logical_end_block = (offset + count + sb->s_blocksize - 1) >> sb->s_blocksize_bits;
blkcnt = logical_end_block - logical_start_block;
bhs = kcalloc(blkcnt, sizeof(*bhs), GFP_KERNEL);
if (!bhs)
return -ENOMEM;
for (log_bno = logical_start_block; log_bno < logical_end_block; log_bno++) {
struct buffer_head *bh = NULL;
u64 bno = 0;
idx = log_bno - logical_start_block;
ret = apfs_logic_to_phys_bno(dstream, log_bno, &bno);
if (ret)
goto out;
if (bno == 0) {
apfs_err(sb, "nonsparse dstream has a hole");
ret = -EFSCORRUPTED;
goto out;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
bhs[idx] = __getblk_gfp(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
#else
bhs[idx] = bdev_getblk(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
#endif
if (!bhs[idx]) {
apfs_err(sb, "failed to map block 0x%llx", bno);
ret = -EIO;
goto out;
}
bh = bhs[idx];
if (!buffer_uptodate(bh)) {
get_bh(bh);
lock_buffer(bh);
bh->b_end_io = end_buffer_read_sync;
apfs_submit_bh(REQ_OP_READ, 0, bh);
}
}
for (log_bno = logical_start_block; log_bno < logical_end_block; log_bno++) {
int off_in_block, left_in_block;
idx = log_bno - logical_start_block;
wait_on_buffer(bhs[idx]);
if (!buffer_uptodate(bhs[idx])) {
apfs_err(sb, "failed to read a block");
ret = -EIO;
goto out;
}
if (log_bno == logical_start_block)
off_in_block = offset & (sb->s_blocksize - 1);
else
off_in_block = 0;
if (log_bno == logical_end_block - 1)
left_in_block = count + offset - (log_bno << sb->s_blocksize_bits) - off_in_block;
else
left_in_block = sb->s_blocksize - off_in_block;
memcpy(buf, bhs[idx]->b_data + off_in_block, left_in_block);
buf += left_in_block;
}
out:
if (bhs) {
for (idx = 0; idx < blkcnt; idx++)
brelse(bhs[idx]);
kfree(bhs);
}
return ret;
}
/**
* apfs_nonsparse_dstream_preread - Attempt to preread a dstream without holes
* @dstream: dstream to preread
*
* Requests reads for all blocks of a dstream, but doesn't wait for the result.
*/
void apfs_nonsparse_dstream_preread(struct apfs_dstream_info *dstream)
{
struct super_block *sb = dstream->ds_sb;
u64 logical_end_block, log_bno;
logical_end_block = (dstream->ds_size + sb->s_blocksize - 1) >> sb->s_blocksize_bits;
for (log_bno = 0; log_bno < logical_end_block; log_bno++) {
struct buffer_head *bh = NULL;
u64 bno = 0;
int ret;
ret = apfs_logic_to_phys_bno(dstream, log_bno, &bno);
if (ret || bno == 0)
return;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
bh = __getblk_gfp(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
#else
bh = bdev_getblk(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
#endif
if (!bh)
return;
if (!buffer_uptodate(bh)) {
get_bh(bh);
lock_buffer(bh);
bh->b_end_io = end_buffer_read_sync;
apfs_submit_bh(REQ_OP_READ, 0, bh);
}
brelse(bh);
bh = NULL;
}
}
+24 -2
View File
@@ -18,6 +18,9 @@ static vm_fault_t apfs_page_mkwrite(struct vm_fault *vmf)
struct vm_area_struct *vma = vmf->vma;
#endif
struct page *page = vmf->page;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
struct folio *folio;
#endif
struct inode *inode = file_inode(vma->vm_file);
struct super_block *sb = inode->i_sb;
struct buffer_head *bh, *head;
@@ -54,8 +57,16 @@ static vm_fault_t apfs_page_mkwrite(struct vm_fault *vmf)
goto out_unlock;
}
if (!page_has_buffers(page))
if (!page_has_buffers(page)) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
create_empty_buffers(page, sb->s_blocksize, 0);
#else
folio = page_folio(page);
bh = folio_buffers(folio);
if (!bh)
bh = create_empty_buffers(folio, sb->s_blocksize, 0);
#endif
}
size = i_size_read(inode);
if (page->index == size >> PAGE_SHIFT)
@@ -99,7 +110,11 @@ out_abort:
apfs_transaction_abort(sb);
out:
if (err)
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
ret = block_page_mkwrite_return(err);
#else
ret = vmf_fs_error(err);
#endif
sb_end_pagefault(inode->i_sb);
return ret;
}
@@ -110,7 +125,7 @@ static const struct vm_operations_struct apfs_file_vm_ops = {
.page_mkwrite = apfs_page_mkwrite,
};
static int apfs_file_mmap(struct file * file, struct vm_area_struct * vma)
int apfs_file_mmap(struct file *file, struct vm_area_struct *vma)
{
struct address_space *mapping = file->f_mapping;
@@ -155,6 +170,13 @@ const struct file_operations apfs_file_operations = {
#else
.clone_file_range = apfs_clone_file_range,
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0)
.splice_read = generic_file_splice_read,
#else
.splice_read = filemap_splice_read,
#endif
.splice_write = iter_file_splice_write,
};
#if LINUX_VERSION_CODE == KERNEL_VERSION(5, 3, 0)
+110 -50
View File
@@ -64,17 +64,15 @@ static int apfs_create_dstream_rec(struct apfs_dstream_info *dstream)
{
struct super_block *sb = dstream->ds_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_dstream_id_key raw_key;
struct apfs_dstream_id_val raw_val;
int ret;
apfs_init_dstream_id_key(dstream->ds_id, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_dstream_id_key(dstream->ds_id, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -190,7 +188,6 @@ int apfs_dstream_adj_refcnt(struct apfs_dstream_info *dstream, u32 delta)
{
struct super_block *sb = dstream->ds_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_dstream_id_val raw_val;
void *raw = NULL;
@@ -199,11 +196,10 @@ int apfs_dstream_adj_refcnt(struct apfs_dstream_info *dstream, u32 delta)
ASSERT(APFS_I(dstream->ds_inode)->i_has_dstream);
apfs_init_dstream_id_key(dstream->ds_id, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_dstream_id_key(dstream->ds_id, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -268,7 +264,6 @@ static int apfs_create_crypto_rec(struct inode *inode)
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_dstream_info *dstream = &APFS_I(inode)->i_dstream;
struct apfs_key key;
struct apfs_query *query;
struct apfs_crypto_state_key raw_key;
int ret;
@@ -276,11 +271,10 @@ static int apfs_create_crypto_rec(struct inode *inode)
if (inode->i_size || inode->i_blocks) /* Already has a dstream */
return 0;
apfs_init_crypto_state_key(dstream->ds_id, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_crypto_state_key(dstream->ds_id, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -288,14 +282,16 @@ static int apfs_create_crypto_rec(struct inode *inode)
goto out;
apfs_key_set_hdr(APFS_TYPE_CRYPTO_STATE, dstream->ds_id, &raw_key);
if(sbi->s_dflt_pfk) {
if (sbi->s_dflt_pfk) {
struct apfs_crypto_state_val *raw_val = sbi->s_dflt_pfk;
unsigned key_len = le16_to_cpu(raw_val->state.key_len);
unsigned int key_len = le16_to_cpu(raw_val->state.key_len);
ret = apfs_btree_insert(query, &raw_key, sizeof(raw_key), raw_val, sizeof(*raw_val) + key_len);
if (ret)
apfs_err(sb, "insertion failed for id 0x%llx", dstream->ds_id);
} else {
struct apfs_crypto_state_val raw_val;
raw_val.refcnt = cpu_to_le32(1);
raw_val.state.major_version = cpu_to_le16(APFS_WMCS_MAJOR_VERSION);
raw_val.state.minor_version = cpu_to_le16(APFS_WMCS_MINOR_VERSION);
@@ -318,7 +314,7 @@ out:
* apfs_dflt_key_class - Returns default key class for files in volume
* @sb: volume superblock
*/
static unsigned apfs_dflt_key_class(struct super_block *sb)
static unsigned int apfs_dflt_key_class(struct super_block *sb)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
@@ -341,7 +337,6 @@ static unsigned apfs_dflt_key_class(struct super_block *sb)
int apfs_crypto_adj_refcnt(struct super_block *sb, u64 crypto_id, int delta)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_crypto_state_val *raw_val;
char *raw;
@@ -350,11 +345,10 @@ int apfs_crypto_adj_refcnt(struct super_block *sb, u64 crypto_id, int delta)
if (!crypto_id)
return 0;
apfs_init_crypto_state_key(crypto_id, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_crypto_state_key(crypto_id, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -394,23 +388,21 @@ int APFS_CRYPTO_ADJ_REFCNT_MAXOPS(void)
static int apfs_crypto_set_key(struct super_block *sb, u64 crypto_id, struct apfs_crypto_state_val *new_val)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_crypto_state_val *raw_val;
char *raw;
int ret;
unsigned pfk_len;
unsigned int pfk_len;
if (!crypto_id)
return 0;
pfk_len = le16_to_cpu(new_val->state.key_len);
apfs_init_crypto_state_key(crypto_id, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_crypto_state_key(crypto_id, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -441,24 +433,22 @@ out:
* @max_len: maximum allowed value of val->state.key_len
*/
static int apfs_crypto_get_key(struct super_block *sb, u64 crypto_id, struct apfs_crypto_state_val *val,
unsigned max_len)
unsigned int max_len)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_crypto_state_val *raw_val;
char *raw;
int ret;
unsigned pfk_len;
unsigned int pfk_len;
if (!crypto_id)
return -ENOENT;
apfs_init_crypto_state_key(crypto_id, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_crypto_state_key(crypto_id, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -468,7 +458,7 @@ static int apfs_crypto_get_key(struct super_block *sb, u64 crypto_id, struct apf
raw_val = (void *)raw + query->off;
pfk_len = le16_to_cpu(raw_val->state.key_len);
if(pfk_len > max_len) {
if (pfk_len > max_len) {
ret = -ENOSPC;
goto out;
}
@@ -486,6 +476,9 @@ int __apfs_write_begin(struct file *file, struct address_space *mapping, loff_t
struct apfs_dstream_info *dstream = &APFS_I(inode)->i_dstream;
struct super_block *sb = inode->i_sb;
struct page *page;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
struct folio *folio;
#endif
struct buffer_head *bh, *head;
unsigned int blocksize, block_start, block_end, from, to;
pgoff_t index = pos >> PAGE_SHIFT;
@@ -501,7 +494,7 @@ int __apfs_write_begin(struct file *file, struct address_space *mapping, loff_t
return err;
}
if(apfs_vol_is_encrypted(sb)) {
if (apfs_vol_is_encrypted(sb)) {
err = apfs_create_crypto_rec(inode);
if (err) {
apfs_err(sb, "crypto creation failed for ino 0x%llx", apfs_ino(inode));
@@ -518,8 +511,16 @@ int __apfs_write_begin(struct file *file, struct address_space *mapping, loff_t
#endif
if (!page)
return -ENOMEM;
if (!page_has_buffers(page))
if (!page_has_buffers(page)) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
create_empty_buffers(page, sb->s_blocksize, 0);
#else
folio = page_folio(page);
bh = folio_buffers(folio);
if (!bh)
bh = create_empty_buffers(folio, sb->s_blocksize, 0);
#endif
}
/* CoW moves existing blocks, so read them but mark them as unmapped */
head = page_buffers(page);
@@ -679,6 +680,12 @@ static void apfs_noop_invalidate_folio(struct folio *folio, size_t offset, size_
/* 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)
.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)
.read_folio = apfs_read_folio,
#else
@@ -716,11 +723,13 @@ static void apfs_inode_set_ops(struct inode *inode, dev_t rdev, bool compressed)
switch (inode->i_mode & S_IFMT) {
case S_IFREG:
inode->i_op = &apfs_file_inode_operations;
if (compressed)
if (compressed) {
inode->i_fop = &apfs_compress_file_operations;
else
inode->i_mapping->a_ops = &apfs_compress_aops;
} else {
inode->i_fop = &apfs_file_operations;
inode->i_mapping->a_ops = &apfs_aops;
inode->i_mapping->a_ops = &apfs_aops;
}
break;
case S_IFDIR:
inode->i_op = &apfs_dir_inode_operations;
@@ -789,9 +798,19 @@ static int apfs_inode_from_query(struct apfs_query *query, struct inode *inode)
ai->i_nchildren = le32_to_cpu(inode_val->nchildren);
}
inode->i_atime = ns_to_timespec64(le64_to_cpu(inode_val->access_time));
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
inode->i_ctime = ns_to_timespec64(le64_to_cpu(inode_val->change_time));
#else
inode_set_ctime_to_ts(inode, ns_to_timespec64(le64_to_cpu(inode_val->change_time)));
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
inode->i_atime = ns_to_timespec64(le64_to_cpu(inode_val->access_time));
inode->i_mtime = ns_to_timespec64(le64_to_cpu(inode_val->mod_time));
#else
inode_set_atime_to_ts(inode, ns_to_timespec64(le64_to_cpu(inode_val->access_time)));
inode_set_mtime_to_ts(inode, ns_to_timespec64(le64_to_cpu(inode_val->mod_time)));
#endif
ai->i_crtime = ns_to_timespec64(le64_to_cpu(inode_val->create_time));
dstream->ds_size = inode->i_size = inode->i_blocks = 0;
@@ -854,16 +873,13 @@ static struct apfs_query *apfs_inode_lookup(const struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
int ret;
apfs_init_inode_key(apfs_ino(inode), &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return ERR_PTR(-ENOMEM);
query->key = &key;
apfs_init_inode_key(apfs_ino(inode), &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -924,7 +940,6 @@ static int apfs_check_dstream_refcnt(struct inode *inode)
struct apfs_dstream_info *dstream = &ai->i_dstream;
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query = NULL;
struct apfs_dstream_id_val raw_val;
void *raw = NULL;
@@ -936,11 +951,10 @@ static int apfs_check_dstream_refcnt(struct inode *inode)
return 0;
}
apfs_init_dstream_id_key(dstream->ds_id, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_dstream_id_key(dstream->ds_id, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -1030,6 +1044,7 @@ int apfs_getattr(struct vfsmount *mnt, struct dentry *dentry,
struct inode *inode = d_inode(dentry);
generic_fillattr(inode, stat);
stat->dev = APFS_SB(inode->i_sb)->s_anon_dev;
stat->ino = apfs_ino(inode);
return 0;
}
@@ -1073,10 +1088,13 @@ int apfs_getattr(struct mnt_idmap *idmap,
generic_fillattr(inode, stat);
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0)
generic_fillattr(mnt_userns, inode, stat);
#else
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
generic_fillattr(idmap, inode, stat);
#else
generic_fillattr(idmap, request_mask, inode, stat);
#endif
stat->dev = APFS_SB(inode->i_sb)->s_anon_dev;
stat->ino = apfs_ino(inode);
return 0;
}
@@ -1096,6 +1114,9 @@ static int apfs_build_inode_val(struct inode *inode, struct qstr *qname,
{
struct apfs_inode_val *val;
struct apfs_x_field xkey;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
struct timespec64 ts;
#endif
int total_xlen, val_len;
bool is_device = S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode);
__le32 rdev;
@@ -1114,7 +1135,12 @@ static int apfs_build_inode_val(struct inode *inode, struct qstr *qname,
val->parent_id = cpu_to_le64(APFS_I(inode)->i_parent_id);
val->private_id = cpu_to_le64(apfs_ino(inode));
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
val->mod_time = cpu_to_le64(timespec64_to_ns(&inode->i_mtime));
#else
ts = inode_get_mtime(inode);
val->mod_time = cpu_to_le64(timespec64_to_ns(&ts));
#endif
val->create_time = val->change_time = val->access_time = val->mod_time;
if (S_ISDIR(inode->i_mode))
@@ -1225,7 +1251,7 @@ static int apfs_create_dstream_xfield(struct inode *inode,
dstream_raw.size = cpu_to_le64(inode->i_size);
dstream_raw.alloced_size = cpu_to_le64(apfs_alloced_size(dstream));
if(apfs_vol_is_encrypted(inode->i_sb))
if (apfs_vol_is_encrypted(inode->i_sb))
dstream_raw.default_crypto_id = cpu_to_le64(dstream->ds_id);
/* TODO: can we assume that all inode records have an xfield blob? */
@@ -1476,9 +1502,22 @@ int apfs_update_inode(struct inode *inode, char *new_name)
if (gid_valid(sbi->s_gid))
inode_raw->group = cpu_to_le32(ai->i_saved_gid);
inode_raw->access_time = cpu_to_le64(timespec64_to_ns(&inode->i_atime));
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
inode_raw->change_time = cpu_to_le64(timespec64_to_ns(&inode->i_ctime));
#else
struct timespec64 ictime = inode_get_ctime(inode);
inode_raw->change_time = cpu_to_le64(timespec64_to_ns(&ictime));
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
inode_raw->access_time = cpu_to_le64(timespec64_to_ns(&inode->i_atime));
inode_raw->mod_time = cpu_to_le64(timespec64_to_ns(&inode->i_mtime));
#else
struct timespec64 ts = inode_get_mtime(inode);
inode_raw->mod_time = cpu_to_le64(timespec64_to_ns(&ts));
ts = inode_get_atime(inode);
inode_raw->access_time = cpu_to_le64(timespec64_to_ns(&ts));
#endif
inode_raw->create_time = cpu_to_le64(timespec64_to_ns(&ai->i_crtime));
if (S_ISDIR(inode->i_mode)) {
@@ -1657,7 +1696,14 @@ struct inode *apfs_new_inode(struct inode *dir, umode_t mode, dev_t rdev)
dstream->ds_shared = false;
now = current_time(inode);
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
inode->i_atime = inode->i_mtime = inode->i_ctime = ai->i_crtime = now;
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
inode_set_ctime_to_ts(inode, now);
inode->i_atime = inode->i_mtime = ai->i_crtime = now;
#else
ai->i_crtime = simple_inode_init_ts(inode);
#endif
vsb_raw->apfs_last_mod_time = cpu_to_le64(timespec64_to_ns(&now));
if (S_ISREG(mode))
@@ -1694,18 +1740,16 @@ int apfs_create_inode_rec(struct super_block *sb, struct inode *inode,
struct dentry *dentry)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_inode_key raw_key;
struct apfs_inode_val *raw_val;
int val_len;
int ret;
apfs_init_inode_key(apfs_ino(inode), &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_inode_key(apfs_ino(inode), &query->key);
query->flags |= APFS_QUERY_CAT;
ret = apfs_btree_query(sb, &query);
@@ -1751,7 +1795,13 @@ static int apfs_setsize(struct inode *inode, loff_t new_size)
if (new_size == inode->i_size)
return 0;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
inode->i_mtime = inode->i_ctime = current_time(inode);
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
inode->i_mtime = inode_set_ctime_current(inode);
#else
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
#endif
err = apfs_inode_create_dstream_rec(inode);
if (err) {
@@ -1837,7 +1887,11 @@ fail:
}
/* TODO: this only seems to be necessary because ->write_inode() isn't firing */
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
int apfs_update_time(struct inode *inode, struct timespec64 *time, int flags)
#else
int apfs_update_time(struct inode *inode, int flags)
#endif
{
struct super_block *sb = inode->i_sb;
struct apfs_max_ops maxops;
@@ -1851,9 +1905,11 @@ int apfs_update_time(struct inode *inode, struct timespec64 *time, int flags)
return err;
apfs_inode_join_transaction(sb, inode);
err = generic_update_time(inode, time, flags);
if (err)
goto fail;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
generic_update_time(inode, time, flags);
#else
generic_update_time(inode, flags);
#endif
err = apfs_transaction_commit(sb);
if (err)
@@ -1873,7 +1929,7 @@ static int apfs_ioc_set_dflt_pfk(struct file *file, void __user *user_pfk)
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_wrapped_crypto_state pfk_hdr;
struct apfs_crypto_state_val *pfk;
unsigned key_len;
unsigned int key_len;
if (__copy_from_user(&pfk_hdr, user_pfk, sizeof(pfk_hdr)))
return -EFAULT;
@@ -1940,7 +1996,7 @@ static int apfs_ioc_set_pfk(struct file *file, void __user *user_pfk)
struct apfs_inode_info *ai = APFS_I(inode);
struct apfs_dstream_info *dstream = &ai->i_dstream;
struct apfs_max_ops maxops;
unsigned key_len, key_class;
unsigned int key_len, key_class;
int err;
if (__copy_from_user(&pfk_hdr, user_pfk, sizeof(pfk_hdr)))
@@ -2007,7 +2063,7 @@ static int apfs_ioc_get_pfk(struct file *file, void __user *user_pfk)
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_wrapped_crypto_state pfk_hdr;
struct apfs_crypto_state_val *pfk;
unsigned max_len, key_len;
unsigned int max_len, key_len;
struct apfs_dstream_info *dstream = &APFS_I(inode)->i_dstream;
int err;
@@ -2274,7 +2330,11 @@ int apfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, struct fil
apfs_inode_join_transaction(sb, inode);
apfs_setflags(inode, fa->flags);
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
inode->i_ctime = current_time(inode);
#else
inode_set_ctime_current(inode);
#endif
err = apfs_transaction_commit(sb);
if (err)
+358 -260
View File
File diff suppressed because it is too large. Load diff
+7 -22
View File
@@ -71,7 +71,6 @@ static int apfs_create_snap_metadata_rec(struct inode *mntpoint, struct apfs_nod
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_superblock *vsb_raw = sbi->s_vsb_raw;
struct apfs_query *query = NULL;
struct apfs_key key = {0};
struct apfs_snap_metadata_key raw_key;
struct apfs_snap_metadata_val *raw_val = NULL;
int val_len;
@@ -79,14 +78,12 @@ static int apfs_create_snap_metadata_rec(struct inode *mntpoint, struct apfs_nod
u64 xid = APFS_NXI(sb)->nx_xid;
int err;
apfs_init_snap_metadata_key(xid, &key);
query = apfs_alloc_query(snap_root, NULL /* parent */);
if (!query) {
err = -ENOMEM;
goto fail;
}
query->key = &key;
apfs_init_snap_metadata_key(xid, &query->key);
query->flags |= APFS_QUERY_SNAP_META | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
@@ -116,7 +113,7 @@ static int apfs_create_snap_metadata_rec(struct inode *mntpoint, struct apfs_nod
raw_val->extentref_tree_type = vsb_raw->apfs_extentref_tree_type;
raw_val->flags = 0;
raw_val->name_len = cpu_to_le16(name_len + 1); /* Count the null byte */
strcpy(raw_val->name, name);
strscpy(raw_val->name, name, name_len + 1);
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
raw_val->inum = vsb_raw->apfs_next_obj_id;
@@ -137,20 +134,17 @@ static int apfs_create_snap_name_rec(struct apfs_node *snap_root, const char *na
{
struct super_block *sb = snap_root->object.sb;
struct apfs_query *query = NULL;
struct apfs_key key = {0};
struct apfs_snap_name_key *raw_key = NULL;
struct apfs_snap_name_val raw_val;
int key_len;
int err;
apfs_init_snap_name_key(name, &key);
query = apfs_alloc_query(snap_root, NULL /* parent */);
if (!query) {
err = -ENOMEM;
goto fail;
}
query->key = &key;
apfs_init_snap_name_key(name, &query->key);
query->flags |= APFS_QUERY_SNAP_META | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
@@ -173,7 +167,7 @@ static int apfs_create_snap_name_rec(struct apfs_node *snap_root, const char *na
}
apfs_key_set_hdr(APFS_TYPE_SNAP_NAME, APFS_SNAP_NAME_OBJ_ID, raw_key);
raw_key->name_len = cpu_to_le16(name_len + 1); /* Count the null byte */
strcpy(raw_key->name, name);
strscpy(raw_key->name, name, name_len + 1);
raw_val.snap_xid = cpu_to_le64(APFS_NXI(sb)->nx_xid);
@@ -246,7 +240,6 @@ static int apfs_update_omap_snap_tree(struct super_block *sb, __le64 *oid_p)
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_node *root = NULL;
u64 oid = le64_to_cpup(oid_p);
struct apfs_key key = {0};
struct apfs_query *query = NULL;
__le64 raw_key;
struct apfs_omap_snapshot raw_val = {0};
@@ -268,14 +261,12 @@ static int apfs_update_omap_snap_tree(struct super_block *sb, __le64 *oid_p)
}
oid = 0;
apfs_init_omap_snap_key(nxi->nx_xid, &key);
query = apfs_alloc_query(root, NULL /* parent */);
if (!query) {
err = -ENOMEM;
goto fail;
}
query->key = &key;
apfs_init_omap_snap_key(nxi->nx_xid, &query->key);
query->flags = APFS_QUERY_OMAP_SNAP | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
@@ -499,15 +490,12 @@ static int apfs_snapshot_name_to_xid(struct apfs_node *snap_root, const char *na
{
struct super_block *sb = snap_root->object.sb;
struct apfs_query *query = NULL;
struct apfs_key key = {0};
int err;
apfs_init_snap_name_key(name, &key);
query = apfs_alloc_query(snap_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_snap_name_key(name, &query->key);
query->flags |= APFS_QUERY_SNAP_META | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
@@ -545,15 +533,12 @@ static int apfs_snapshot_xid_to_sblock(struct apfs_node *snap_root, u64 xid, u64
{
struct super_block *sb = snap_root->object.sb;
struct apfs_query *query = NULL;
struct apfs_key key = {0};
int err;
apfs_init_snap_metadata_key(xid, &key);
query = apfs_alloc_query(snap_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_snap_metadata_key(xid, &query->key);
query->flags |= APFS_QUERY_SNAP_META | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
+195 -75
View File
@@ -5,6 +5,7 @@
#include <linux/buffer_head.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include "apfs.h"
/**
@@ -95,6 +96,25 @@ static int apfs_read_spaceman_dev(struct super_block *sb,
return 0;
}
/**
* apfs_spaceman_get_16 - Get a 16-bit value from an offset in the spaceman
* @sb: superblock structure
* @off: offset for the value
*
* Returns a pointer to the value, or NULL if it doesn't fit.
*/
static __le16 *apfs_spaceman_get_16(struct super_block *sb, size_t off)
{
struct apfs_spaceman *spaceman = APFS_SM(sb);
struct apfs_spaceman_phys *sm_raw = spaceman->sm_raw;
if (off > sb->s_blocksize)
return NULL;
if (off + sizeof(__le16) > sb->s_blocksize)
return NULL;
return (void *)sm_raw + off;
}
/**
* apfs_spaceman_get_64 - Get a 64-bit value from an offset in the spaceman
* @sb: superblock structure
@@ -124,7 +144,7 @@ static bool apfs_ip_bm_is_free(struct apfs_spaceman_phys *sm, u16 index)
u16 free_head = le16_to_cpu(sm->sm_ip_bm_free_head);
u16 free_tail = le16_to_cpu(sm->sm_ip_bm_free_tail);
u16 free_len, index_in_free;
u16 bmap_count = 16;
u16 bmap_count = le32_to_cpu(sm->sm_ip_bm_block_count);
free_len = (bmap_count + free_tail - free_head) % bmap_count;
index_in_free = (bmap_count + index - free_head) % bmap_count;
@@ -144,7 +164,7 @@ static int apfs_update_ip_bm_free_next(struct super_block *sb)
struct apfs_spaceman *spaceman = APFS_SM(sb);
struct apfs_spaceman_phys *raw = spaceman->sm_raw;
u32 free_next_off = le32_to_cpu(raw->sm_ip_bm_free_next_offset);
int bmap_count = 16;
int bmap_count = le32_to_cpu(raw->sm_ip_bm_block_count);
__le16 *free_next;
int i;
@@ -168,65 +188,61 @@ static int apfs_update_ip_bm_free_next(struct super_block *sb)
}
/**
* apfs_rotate_ip_bitmaps - Allocate a new ip bitmap from the circular buffer
* @sb: superblock structure
* apfs_rotate_single_ip_bitmap - Reallocate an ip bmap in the circular buffer
* @sb: filesystem superblock
* @idx: index of the ip bitmap to reallocate
*
* Returns 0 on success or a negative error code in case of failure.
*/
static int apfs_rotate_ip_bitmaps(struct super_block *sb)
static int apfs_rotate_single_ip_bitmap(struct super_block *sb, u32 idx)
{
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_spaceman *spaceman = APFS_SM(sb);
struct apfs_spaceman_phys *sm_raw = spaceman->sm_raw;
u64 bmap_base = le64_to_cpu(sm_raw->sm_ip_bm_base);
u32 bmap_length = le32_to_cpu(sm_raw->sm_ip_bm_block_count);
u16 free_head, free_tail;
__le64 *curr_bmap_off, *xid;
struct buffer_head *old_bh = NULL, *new_bh = NULL;
int err = 0;
apfs_assert_in_transaction(sb, &sm_raw->sm_o);
if (le32_to_cpu(sm_raw->sm_ip_bm_size_in_blocks) != 1) {
apfs_warn(sb, "Multiblock ip bitmaps not supported");
return -EOPNOTSUPP;
}
xid = apfs_spaceman_get_64(sb, le32_to_cpu(sm_raw->sm_ip_bm_xid_offset));
if (!xid) {
apfs_err(sb, "xid out of bounds (%u)", le32_to_cpu(sm_raw->sm_ip_bm_xid_offset));
return -EFSCORRUPTED;
}
*xid = cpu_to_le64(nxi->nx_xid);
u64 ring_base;
u32 ring_length;
u32 xid_off, ip_bitmap_off;
u64 ip_bitmap_bno;
u16 free_head;
__le64 *xid_p = NULL;
__le16 *ip_bitmap_p = NULL;
int err;
ring_base = le64_to_cpu(sm_raw->sm_ip_bm_base);
ring_length = le32_to_cpu(sm_raw->sm_ip_bm_block_count);
free_head = le16_to_cpu(sm_raw->sm_ip_bm_free_head);
free_tail = le16_to_cpu(sm_raw->sm_ip_bm_free_tail);
curr_bmap_off = apfs_spaceman_get_64(sb, le32_to_cpu(sm_raw->sm_ip_bitmap_offset));
if (!curr_bmap_off) {
apfs_err(sb, "bmap offset out of bounds (%u)", le32_to_cpu(sm_raw->sm_ip_bitmap_offset));
xid_off = le32_to_cpu(sm_raw->sm_ip_bm_xid_offset) + idx * sizeof(*xid_p);
xid_p = apfs_spaceman_get_64(sb, xid_off);
if (!xid_p) {
apfs_err(sb, "xid out of bounds (%u)", xid_off);
return -EFSCORRUPTED;
}
old_bh = apfs_sb_bread(sb, bmap_base + le64_to_cpup(curr_bmap_off));
*xid_p = cpu_to_le64(nxi->nx_xid);
ip_bitmap_off = le32_to_cpu(sm_raw->sm_ip_bitmap_offset) + idx * sizeof(*ip_bitmap_p);
ip_bitmap_p = apfs_spaceman_get_16(sb, ip_bitmap_off);
if (!ip_bitmap_p) {
apfs_err(sb, "bmap offset out of bounds (%u)", ip_bitmap_off);
return -EFSCORRUPTED;
}
ip_bitmap_bno = ring_base + le16_to_cpup(ip_bitmap_p);
old_bh = apfs_sb_bread(sb, ip_bitmap_bno);
if (!old_bh) {
apfs_err(sb, "failed to read current ip bitmap (0x%llx)", bmap_base + le64_to_cpup(curr_bmap_off));
apfs_err(sb, "failed to read current ip bitmap (0x%llx)", ip_bitmap_bno);
return -EIO;
}
*curr_bmap_off = cpu_to_le64(free_head);
free_head = (free_head + 1) % bmap_length;
free_tail = (free_tail + 1) % bmap_length;
*ip_bitmap_p = cpu_to_le16(free_head);
free_head = (free_head + 1) % ring_length;
sm_raw->sm_ip_bm_free_head = cpu_to_le16(free_head);
sm_raw->sm_ip_bm_free_tail = cpu_to_le16(free_tail);
err = apfs_update_ip_bm_free_next(sb);
if (err) {
apfs_err(sb, "failed to update bitmap ring");
goto out;
}
new_bh = apfs_getblk(sb, bmap_base + le64_to_cpup(curr_bmap_off));
ip_bitmap_bno = ring_base + le16_to_cpup(ip_bitmap_p);
new_bh = apfs_getblk(sb, ip_bitmap_bno);
if (!new_bh) {
apfs_err(sb, "failed to map block for CoW (0x%llx)", bmap_base + le64_to_cpup(curr_bmap_off));
apfs_err(sb, "failed to map block for CoW (0x%llx)", ip_bitmap_bno);
err = -EIO;
goto out;
}
@@ -234,7 +250,7 @@ static int apfs_rotate_ip_bitmaps(struct super_block *sb)
err = apfs_transaction_join(sb, new_bh);
if (err)
goto out;
spaceman->sm_ip = new_bh;
spaceman->sm_ip_bmaps[idx] = new_bh;
out:
brelse(old_bh);
@@ -243,6 +259,64 @@ out:
return err;
}
/**
* apfs_rotate_ip_bitmaps - Allocate new ip bitmaps from the circular buffer
* @sb: superblock structure
*
* Allocates bitmaps for the whole internal pool at once, meaning that each
* transaction is forced to allocate one bitmap for every ~1.32 TiB of container
* size, even if they won't be needed. This seems very reasonable to me, but the
* official implementation avoids it and they may have a good reason.
*
* Returns 0 on success or a negative error code in case of failure.
*/
static int apfs_rotate_ip_bitmaps(struct super_block *sb)
{
struct apfs_spaceman *spaceman = APFS_SM(sb);
struct apfs_spaceman_phys *sm_raw = spaceman->sm_raw;
u32 ring_length = le32_to_cpu(sm_raw->sm_ip_bm_block_count);
u32 bmaps_count = spaceman->sm_ip_bmaps_count;
u16 free_head, free_tail, free_len;
int err;
u32 i;
apfs_assert_in_transaction(sb, &sm_raw->sm_o);
free_head = le16_to_cpu(sm_raw->sm_ip_bm_free_head);
free_tail = le16_to_cpu(sm_raw->sm_ip_bm_free_tail);
/*
* Check that we have enough room before doing anything. If we run out
* I may need to compact the ring using the blocks marked as 0xFFFF in
* ip_bm_free_next (TODO).
*/
free_len = (ring_length + free_tail - free_head) % ring_length;
if (free_len < bmaps_count) {
apfs_alert(sb, "full ip bitmap ring (%u < %u)", free_len, bmaps_count);
return -ENOSPC;
}
for (i = 0; i < bmaps_count; ++i) {
err = apfs_rotate_single_ip_bitmap(sb, i);
if (err) {
apfs_err(sb, "failed to rotate ip bitmap %u", i);
return err;
}
}
/* All bitmaps have been reallocated, so just free the same number */
free_tail = (free_tail + bmaps_count) % ring_length;
sm_raw->sm_ip_bm_free_tail = cpu_to_le16(free_tail);
err = apfs_update_ip_bm_free_next(sb);
if (err) {
apfs_err(sb, "failed to update bitmap ring");
return err;
}
return 0;
}
/*
* Free queue record data
*/
@@ -309,10 +383,11 @@ static int apfs_ip_mark_free(struct super_block *sb, u64 bno)
{
struct apfs_spaceman *sm = APFS_SM(sb);
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
char *bitmap = sm->sm_ip->b_data;
struct buffer_head *bmap_bh = NULL;
bno -= le64_to_cpu(sm_raw->sm_ip_base);
__clear_bit_le(bno, bitmap);
bmap_bh = sm->sm_ip_bmaps[bno >> sm->sm_ip_bmaps_shift];
__clear_bit_le(bno & sm->sm_ip_bmaps_mask, bmap_bh->b_data);
return 0;
}
@@ -337,15 +412,13 @@ static int apfs_flush_fq_rec(struct apfs_node *root, u64 xid, u64 *len)
struct apfs_spaceman *sm = APFS_SM(sb);
struct apfs_query *query = NULL;
struct apfs_fq_rec fqrec = {0};
struct apfs_key key;
u64 bno;
int err;
query = apfs_alloc_query(root, NULL /* parent */);
if (!query)
return -ENOMEM;
apfs_init_free_queue_key(xid, 0 /* paddr */, &key);
query->key = &key;
apfs_init_free_queue_key(xid, 0 /* paddr */, &query->key);
query->flags |= APFS_QUERY_FREE_QUEUE | APFS_QUERY_ANY_NUMBER | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
@@ -361,7 +434,7 @@ static int apfs_flush_fq_rec(struct apfs_node *root, u64 xid, u64 *len)
}
for (bno = fqrec.bno; bno < fqrec.bno + fqrec.len; ++bno) {
if(apfs_block_in_ip(sm, bno))
if (apfs_block_in_ip(sm, bno))
err = apfs_ip_mark_free(sb, bno);
else
err = apfs_main_free(sb, bno);
@@ -406,7 +479,7 @@ static u64 apfs_free_queue_oldest_xid(struct apfs_node *root)
*
* Returns 0 on success or a negative error code in case of failure.
*/
static int apfs_flush_free_queue(struct super_block *sb, unsigned qid)
static int apfs_flush_free_queue(struct super_block *sb, unsigned int qid)
{
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_spaceman *sm = APFS_SM(sb);
@@ -466,6 +539,42 @@ fail:
return err;
}
/**
* apfs_allocate_spaceman - Allocate an in-memory spaceman struct, if needed
* @sb: superblock structure
* @bmap_cnt: internal pool bitmap count
*
* Returns the spaceman and sets it in the superblock info. Also sets the fixed
* information about the ip bitmap count. On failure, returns an error pointer.
*/
static struct apfs_spaceman *apfs_allocate_spaceman(struct super_block *sb, u32 bmap_cnt)
{
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_spaceman *spaceman = NULL;
int blk_bitcnt = sb->s_blocksize * 8;
size_t sm_size;
if (nxi->nx_spaceman)
return nxi->nx_spaceman;
/* We don't expect filesystems this big, it would be like 260 TiB */
if (bmap_cnt > 200) {
apfs_err(sb, "too many ip bitmap blocks (%u)", bmap_cnt);
return ERR_PTR(-EFSCORRUPTED);
}
sm_size = sizeof(*spaceman) + bmap_cnt * sizeof(spaceman->sm_ip_bmaps[0]);
spaceman = nxi->nx_spaceman = kzalloc(sm_size, GFP_KERNEL);
if (!spaceman)
return ERR_PTR(-ENOMEM);
spaceman->sm_nxi = nxi;
spaceman->sm_ip_bmaps_count = bmap_cnt;
spaceman->sm_ip_bmaps_mask = blk_bitcnt - 1;
spaceman->sm_ip_bmaps_shift = order_base_2(blk_bitcnt);
return spaceman;
}
/**
* apfs_read_spaceman - Find and read the space manager
* @sb: superblock structure
@@ -478,7 +587,7 @@ int apfs_read_spaceman(struct super_block *sb)
{
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_nx_superblock *raw_sb = nxi->nx_raw;
struct apfs_spaceman *spaceman = APFS_SM(sb);
struct apfs_spaceman *spaceman = NULL;
struct buffer_head *sm_bh;
struct apfs_spaceman_phys *sm_raw;
u32 sm_flags;
@@ -488,8 +597,6 @@ int apfs_read_spaceman(struct super_block *sb)
if (sb->s_flags & SB_RDONLY) /* The space manager won't be needed */
return 0;
spaceman->sm_free_cache_base = spaceman->sm_free_cache_blkcnt = 0;
sm_bh = apfs_read_ephemeral_object(sb, oid);
if (IS_ERR(sm_bh)) {
apfs_err(sb, "ephemeral read failed for oid 0x%llx", oid);
@@ -497,6 +604,15 @@ int apfs_read_spaceman(struct super_block *sb)
}
sm_raw = (struct apfs_spaceman_phys *)sm_bh->b_data;
spaceman = apfs_allocate_spaceman(sb, le32_to_cpu(sm_raw->sm_ip_bm_size_in_blocks));
if (IS_ERR(spaceman)) {
apfs_err(sb, "failed to allocate spaceman");
err = PTR_ERR(spaceman);
goto fail;
}
spaceman->sm_free_cache_base = spaceman->sm_free_cache_blkcnt = 0;
sm_flags = le32_to_cpu(sm_raw->sm_flags);
/* Undocumented feature, but it's too common to refuse to mount */
if (sm_flags & APFS_SM_FLAG_VERSIONED)
@@ -557,7 +673,7 @@ static void apfs_write_spaceman(struct apfs_spaceman *sm)
struct apfs_spaceman_device *dev_raw = &sm_raw->sm_dev[APFS_SD_MAIN];
struct apfs_nxsb_info *nxi;
nxi = container_of(sm, struct apfs_nxsb_info, nx_spaceman);
nxi = sm->sm_nxi;
ASSERT(le64_to_cpu(sm_raw->sm_o.o_xid) == nxi->nx_xid);
dev_raw->sm_free_count = cpu_to_le64(sm->sm_free_count);
@@ -573,18 +689,26 @@ static u64 apfs_ip_find_free(struct super_block *sb)
{
struct apfs_spaceman *sm = APFS_SM(sb);
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
u64 bitcount = le64_to_cpu(sm_raw->sm_ip_block_count);
char *bitmap = sm->sm_ip->b_data;
u64 offset;
int blk_bitcnt = sb->s_blocksize * 8;
u64 full_bitcnt = le64_to_cpu(sm_raw->sm_ip_block_count);
u32 i;
if (bitcount > sb->s_blocksize * 8)
return 0;
offset = find_next_zero_bit_le(bitmap, bitcount, 0 /* offset */);
if (offset >= bitcount) {
apfs_warn(sb, "internal pool seems full");
return 0;
for (i = 0; i < sm->sm_ip_bmaps_count; ++i) {
char *bitmap = sm->sm_ip_bmaps[i]->b_data;
u64 off_in_bmap_blk, off_in_ip;
off_in_bmap_blk = find_next_zero_bit_le(bitmap, blk_bitcnt, 0 /* offset */);
if (off_in_bmap_blk >= blk_bitcnt) /* No space in this chunk */
continue;
/* We found something, confirm that it's not outside the ip */
off_in_ip = (i << sm->sm_ip_bmaps_shift) + off_in_bmap_blk;
if (off_in_ip >= full_bitcnt)
break;
return le64_to_cpu(sm_raw->sm_ip_base) + off_in_ip;
}
return le64_to_cpu(sm_raw->sm_ip_base) + offset;
apfs_err(sb, "internal pool seems full");
return 0;
}
/**
@@ -615,10 +739,11 @@ static void apfs_ip_mark_used(struct super_block *sb, u64 bno)
{
struct apfs_spaceman *sm = APFS_SM(sb);
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
char *bitmap = sm->sm_ip->b_data;
struct buffer_head *bmap_bh = NULL;
bno -= le64_to_cpu(sm_raw->sm_ip_base);
__set_bit_le(bno, bitmap);
bmap_bh = sm->sm_ip_bmaps[bno >> sm->sm_ip_bmaps_shift];
__set_bit_le(bno & sm->sm_ip_bmaps_mask, bmap_bh->b_data);
}
/**
@@ -671,7 +796,6 @@ int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
struct apfs_query *query = NULL;
struct apfs_spaceman_free_queue_key raw_key;
__le64 raw_val;
struct apfs_key key;
u64 node_count;
u16 node_limit;
int err;
@@ -693,9 +817,7 @@ int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
err = -ENOMEM;
goto fail;
}
apfs_init_free_queue_key(nxi->nx_xid, bno, &key);
query->key = &key;
apfs_init_free_queue_key(nxi->nx_xid, bno, &query->key);
query->flags |= APFS_QUERY_FREE_QUEUE;
err = apfs_btree_query(sb, &query);
@@ -829,7 +951,7 @@ static int apfs_chunk_alloc_free(struct super_block *sb,
if (!ci->ci_bitmap_addr) {
u64 bmap_bno;
if(!is_alloc) {
if (!is_alloc) {
apfs_err(sb, "attempt to free block in all-free chunk");
return -EFSCORRUPTED;
}
@@ -930,10 +1052,9 @@ static int apfs_chunk_alloc_free(struct super_block *sb,
ci->ci_bitmap_addr = cpu_to_le64(bmap_bh->b_blocknr);
ASSERT(buffer_trans(*cib_bh));
set_buffer_csum(*cib_bh);
mark_buffer_dirty(*cib_bh);
/* Finally, allocate / free the actual block that was requested */
if(is_alloc) {
if (is_alloc) {
*bno = apfs_chunk_find_free(sb, bmap, le64_to_cpu(ci->ci_addr));
if (!*bno) {
apfs_err(sb, "no free blocks in chunk");
@@ -943,11 +1064,10 @@ static int apfs_chunk_alloc_free(struct super_block *sb,
apfs_chunk_mark_used(sb, bmap, *bno);
sm->sm_free_count -= 1;
} else {
if(!apfs_chunk_mark_free(sb, bmap, *bno)) {
if (!apfs_chunk_mark_free(sb, bmap, *bno)) {
apfs_err(sb, "block already marked as free (0x%llx)", *bno);
le32_add_cpu(&ci->ci_free_count, -1);
set_buffer_csum(*cib_bh);
mark_buffer_dirty(*cib_bh);
err = -EFSCORRUPTED;
} else
sm->sm_free_count += 1;
@@ -1102,7 +1222,7 @@ static int apfs_main_free(struct super_block *sb, u64 bno)
u64 cib_bno;
int err;
if(!sm_raw->sm_blocks_per_chunk || !sm_raw->sm_chunks_per_cib) {
if (!sm_raw->sm_blocks_per_chunk || !sm_raw->sm_chunks_per_cib) {
apfs_err(sb, "block or chunk count not set");
return -EINVAL;
}
+99 -13
View File
@@ -14,6 +14,9 @@
#include <linux/statfs.h>
#include <linux/seq_file.h>
#include "apfs.h"
#include "version.h"
#define APFS_MODULE_ID_STRING "linux-apfs by eafer (" GIT_COMMIT ")"
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) /* iversion came in 4.16 */
#include <linux/iversion.h>
@@ -272,11 +275,13 @@ static void apfs_update_software_info(struct super_block *sb)
memmove(mod_by + 1, mod_by, (APFS_MAX_HIST - 1) * sizeof(*mod_by));
memset(mod_by->id, 0, sizeof(mod_by->id));
strcpy(mod_by->id, APFS_MODULE_ID_STRING);
strscpy(mod_by->id, APFS_MODULE_ID_STRING, sizeof(mod_by->id));
mod_by->timestamp = cpu_to_le64(ktime_get_real_ns());
mod_by->last_xid = cpu_to_le64(APFS_NXI(sb)->nx_xid);
}
static struct file_system_type apfs_fs_type;
/**
* apfs_unmap_main_super - Clean up apfs_map_main_super()
* @sbi: in-memory superblock info
@@ -286,11 +291,15 @@ static void apfs_update_software_info(struct super_block *sb)
static inline void apfs_unmap_main_super(struct apfs_sb_info *sbi)
{
struct apfs_nxsb_info *nxi = sbi->s_nxi;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0)
fmode_t mode = FMODE_READ | FMODE_EXCL;
#endif
struct apfs_object *obj = NULL;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0)
if (nxi->nx_flags & APFS_READWRITE)
mode |= FMODE_WRITE;
#endif
lockdep_assert_held(&nxs_mutex);
@@ -304,8 +313,15 @@ static inline void apfs_unmap_main_super(struct apfs_sb_info *sbi)
obj->o_bh = NULL;
obj = NULL;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)
blkdev_put(nxi->nx_bdev, &apfs_fs_type);
#else
blkdev_put(nxi->nx_bdev, mode);
#endif
list_del(&nxi->nx_list);
kfree(nxi->nx_spaceman);
nxi->nx_spaceman = NULL;
kfree(nxi);
out:
sbi->s_nxi = NULL;
@@ -868,10 +884,11 @@ static int apfs_count_used_blocks(struct super_block *sb, u64 *count)
vol_id = le64_to_cpu(msb_raw->nx_fs_oid[i]);
if (vol_id == 0) /* All volumes have been checked */
break;
err = apfs_omap_lookup_block(sb, omap, vol_id, &vol_bno,
false /* write */);
if (err)
err = apfs_omap_lookup_newest_block(sb, omap, vol_id, &vol_bno, false /* write */);
if (err) {
apfs_err(sb, "omap lookup failed for vol id 0x%llx", vol_id);
break;
}
bh = apfs_sb_bread(sb, vol_bno);
if (!bh) {
@@ -947,6 +964,8 @@ static int apfs_show_options(struct seq_file *seq, struct dentry *root)
if (sbi->s_vol_nr != 0)
seq_printf(seq, ",vol=%u", sbi->s_vol_nr);
if (sbi->s_snap_name)
seq_printf(seq, ",snap=%s", sbi->s_snap_name);
if (uid_valid(sbi->s_uid))
seq_printf(seq, ",uid=%u", from_kuid(&init_user_ns,
sbi->s_uid));
@@ -959,12 +978,15 @@ static int apfs_show_options(struct seq_file *seq, struct dentry *root)
return 0;
}
/* TODO: don't ignore @wait */
int apfs_sync_fs(struct super_block *sb, int wait)
{
struct apfs_max_ops maxops = {0};
int err;
/* TODO: actually start the commit and return without waiting? */
if (wait == 0)
return 0;
err = apfs_transaction_start(sb, maxops);
if (err)
return err;
@@ -1320,6 +1342,27 @@ static int apfs_setup_bdi(struct super_block *sb)
#endif
static void apfs_set_trans_buffer_limit(struct super_block *sb)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
unsigned long memsize_in_blocks;
struct sysinfo info = {0};
si_meminfo(&info);
memsize_in_blocks = info.totalram << (PAGE_SHIFT - sb->s_blocksize_bits);
/*
* Buffer heads are not reclaimed while they are part of the current
* transaction, so systems with little memory will crash if we don't
* commit often enough. This hack should make that happen in general,
* but I still need to get the reclaim to work eventually (TODO).
*/
if (memsize_in_blocks >= 16 * TRANSACTION_BUFFERS_MAX)
sbi->s_trans_buffers_max = TRANSACTION_BUFFERS_MAX;
else
sbi->s_trans_buffers_max = memsize_in_blocks / 16;
}
static int apfs_fill_super(struct super_block *sb, void *data, int silent)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
@@ -1333,6 +1376,8 @@ static int apfs_fill_super(struct super_block *sb, void *data, int silent)
if (err)
return err;
apfs_set_trans_buffer_limit(sb);
sbi->s_uid = INVALID_UID;
sbi->s_gid = INVALID_GID;
err = parse_options(sb, data);
@@ -1438,19 +1483,35 @@ static int apfs_test_super(struct super_block *sb, void *data)
}
/**
* apfs_set_super - Assign a fake bdev and an info struct to a superblock
* apfs_set_super - Assign the device and an info struct to a superblock
* @sb: superblock structure to set
* @data: superblock info for the volume being mounted
*/
static int apfs_set_super(struct super_block *sb, void *data)
{
int err = set_anon_super(sb, data);
if (!err)
sb->s_fs_info = data;
return err;
}
struct apfs_sb_info *sbi = data;
struct apfs_nxsb_info *nxi = sbi->s_nxi;
int err;
static struct file_system_type apfs_fs_type;
/*
* This fake device number will be unique to this volume-snapshot
* combination. It gets reported by stat(), so that userland tools can
* use it to tell different mountpoints apart.
*/
err = get_anon_bdev(&sbi->s_anon_dev);
if (err)
return err;
/*
* This is the actual device number, shared by all volumes and
* snapshots. It gets reported by the mountinfo file, and it seems that
* udisks uses it to decide if a device is mounted, so it must be set.
*/
sb->s_dev = nxi->nx_bdev->bd_dev;
sb->s_fs_info = sbi;
return 0;
}
/*
* Wrapper for lookup_bdev() that supports older kernels.
@@ -1480,7 +1541,11 @@ static int apfs_lookup_bdev(const char *pathname, dev_t *dev)
*
* Returns 0 on success, or a negative error code in case of failure.
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)
static int apfs_attach_nxi(struct apfs_sb_info *sbi, const char *dev_name, blk_mode_t mode)
#else
static int apfs_attach_nxi(struct apfs_sb_info *sbi, const char *dev_name, fmode_t mode)
#endif
{
struct apfs_nxsb_info *nxi;
dev_t dev = 0;
@@ -1500,7 +1565,11 @@ static int apfs_attach_nxi(struct apfs_sb_info *sbi, const char *dev_name, fmode
if (!nxi)
return -ENOMEM;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)
bdev = blkdev_get_by_path(dev_name, mode, &apfs_fs_type, NULL);
#else
bdev = blkdev_get_by_path(dev_name, mode, &apfs_fs_type);
#endif
if (IS_ERR(bdev)) {
kfree(nxi);
return PTR_ERR(bdev);
@@ -1527,7 +1596,11 @@ static struct dentry *apfs_mount(struct file_system_type *fs_type, int flags,
struct apfs_nxsb_info *nxi;
struct super_block *sb;
struct apfs_sb_info *sbi;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)
blk_mode_t mode = sb_open_mode(flags);
#else
fmode_t mode = FMODE_READ | FMODE_EXCL;
#endif
int error = 0;
mutex_lock(&nxs_mutex);
@@ -1544,8 +1617,10 @@ static struct dentry *apfs_mount(struct file_system_type *fs_type, int flags,
if (sbi->s_snap_name)
flags |= SB_RDONLY;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0)
if (!(flags & SB_RDONLY))
mode |= FMODE_WRITE;
#endif
error = apfs_attach_nxi(sbi, dev_name, mode);
if (error)
@@ -1572,7 +1647,9 @@ static struct dentry *apfs_mount(struct file_system_type *fs_type, int flags,
error = apfs_map_main_super(sb);
if (error)
goto out_deactivate_super;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0)
sb->s_mode = mode;
#endif
snprintf(sb->s_id, sizeof(sb->s_id), "%xg", sb->s_dev);
error = apfs_fill_super(sb, data, flags & SB_SILENT ? 1 : 0);
if (error)
@@ -1595,11 +1672,19 @@ out_unlock:
return ERR_PTR(error);
}
static void apfs_kill_sb(struct super_block *sb)
{
dev_t anon_dev = APFS_SB(sb)->s_anon_dev;
generic_shutdown_super(sb);
free_anon_bdev(anon_dev);
}
static struct file_system_type apfs_fs_type = {
.owner = THIS_MODULE,
.name = "apfs",
.mount = apfs_mount,
.kill_sb = kill_anon_super,
.kill_sb = apfs_kill_sb,
.fs_flags = FS_REQUIRES_DEV,
};
MODULE_ALIAS_FS("apfs");
@@ -1625,6 +1710,7 @@ static void __exit exit_apfs_fs(void)
MODULE_AUTHOR("Ernesto A. Fernández");
MODULE_DESCRIPTION("Apple File System");
MODULE_VERSION(GIT_COMMIT);
MODULE_LICENSE("GPL");
module_init(init_apfs_fs)
module_exit(exit_apfs_fs)
+64 -52
View File
@@ -523,42 +523,6 @@ fail:
return err;
}
/**
* apfs_end_buffer_write_sync - Clean up a buffer head just synced to disk
* @bh: the buffer head to clean
* @uptodate: has the write succeeded?
*/
static void apfs_end_buffer_write_sync(struct buffer_head *bh, int uptodate)
{
struct page *page = NULL;
bool must_unlock, is_metadata;
page = bh->b_page;
get_page(page);
is_metadata = buffer_csum(bh);
clear_buffer_csum(bh);
end_buffer_write_sync(bh, uptodate);
bh = NULL;
/* Future writes to mmapped areas should fault for CoW */
must_unlock = trylock_page(page);
page_mkclean(page);
/* XXX: otherwise, the page cache fills up and crashes the machine */
if (!is_metadata) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
try_to_free_buffers(page_folio(page));
#else
try_to_free_buffers(page);
#endif
}
if (must_unlock)
unlock_page(page);
put_page(page);
}
/**
* apfs_transaction_flush_all_inodes - Flush inode metadata to the buffer heads
* @sb: superblock structure
@@ -628,6 +592,7 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
struct apfs_nx_transaction *nx_trans = &nxi->nx_transaction;
struct apfs_bh_info *bhi, *tmp;
int err = 0;
u32 bmap_idx;
ASSERT(!(sb->s_flags & SB_RDONLY));
ASSERT(nx_trans->t_old_msb);
@@ -652,7 +617,7 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
sm->sm_free_cache_base = sm->sm_free_cache_blkcnt = 0;
}
list_for_each_entry_safe(bhi, tmp, &nx_trans->t_buffers, list) {
list_for_each_entry(bhi, &nx_trans->t_buffers, list) {
struct buffer_head *bh = bhi->bh;
ASSERT(buffer_trans(bh));
@@ -660,6 +625,25 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
if (buffer_csum(bh))
apfs_obj_set_csum(sb, (void *)bh->b_data);
clear_buffer_dirty(bh);
get_bh(bh);
lock_buffer(bh);
bh->b_end_io = end_buffer_write_sync;
apfs_submit_bh(REQ_OP_WRITE, REQ_SYNC, bh);
}
list_for_each_entry_safe(bhi, tmp, &nx_trans->t_buffers, list) {
struct buffer_head *bh = bhi->bh;
struct page *page = NULL;
bool is_metadata;
ASSERT(buffer_trans(bh));
wait_on_buffer(bh);
if (!buffer_uptodate(bh)) {
apfs_err(sb, "failed to write some blocks");
return -EIO;
}
list_del(&bhi->list);
clear_buffer_trans(bh);
nx_trans->t_buffers_count--;
@@ -669,10 +653,27 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
kfree(bhi);
bhi = NULL;
clear_buffer_dirty(bh);
lock_buffer(bh);
bh->b_end_io = apfs_end_buffer_write_sync;
apfs_submit_bh(REQ_OP_WRITE, REQ_SYNC, bh);
page = bh->b_page;
get_page(page);
is_metadata = buffer_csum(bh);
clear_buffer_csum(bh);
put_bh(bh);
bh = NULL;
/* Future writes to mmapped areas should fault for CoW */
lock_page(page);
page_mkclean(page);
/* XXX: otherwise, the page cache fills up and crashes the machine */
if (!is_metadata) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
try_to_free_buffers(page_folio(page));
#else
try_to_free_buffers(page);
#endif
}
unlock_page(page);
put_page(page);
}
err = apfs_checkpoint_end(sb);
if (err) {
@@ -686,6 +687,7 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
list_for_each_entry(sbi, &nxi->vol_list, list) {
struct apfs_vol_transaction *vol_trans = &sbi->s_transaction;
if (!vol_trans->t_old_vsb)
continue;
@@ -701,8 +703,10 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
vol_trans->t_old_cat_root.object.o_bh = NULL;
}
brelse(APFS_SM(sb)->sm_ip);
APFS_SM(sb)->sm_ip = NULL;
for (bmap_idx = 0; bmap_idx < APFS_SM(sb)->sm_ip_bmaps_count; ++bmap_idx) {
brelse(APFS_SM(sb)->sm_ip_bmaps[bmap_idx]);
APFS_SM(sb)->sm_ip_bmaps[bmap_idx] = NULL;
}
brelse(APFS_SM(sb)->sm_bh);
APFS_SM(sb)->sm_bh = NULL;
return 0;
@@ -736,7 +740,7 @@ static bool apfs_transaction_need_commit(struct super_block *sb)
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
struct apfs_spaceman_free_queue *fq_ip = &sm_raw->sm_fq[APFS_SFQ_IP];
struct apfs_spaceman_free_queue *fq_main = &sm_raw->sm_fq[APFS_SFQ_MAIN];
int buffers_max = TRANSACTION_BUFFERS_MAX;
int buffers_max = APFS_SB(sb)->s_trans_buffers_max;
int starts_max = TRANSACTION_STARTS_MAX;
int mq_max = TRANSACTION_MAIN_QUEUE_MAX;
@@ -751,7 +755,7 @@ static bool apfs_transaction_need_commit(struct super_block *sb)
mq_max += 20;
}
if(nx_trans->t_buffers_count > buffers_max)
if (nx_trans->t_buffers_count > buffers_max)
return true;
if (nx_trans->t_starts_count > starts_max)
return true;
@@ -761,11 +765,11 @@ static bool apfs_transaction_need_commit(struct super_block *sb)
* exactly 3 times. Don't allow large transactions if we can't
* be sure the bitmap changes will all fit.
*/
if(le64_to_cpu(fq_ip->sfq_count) * 3 > le64_to_cpu(sm_raw->sm_ip_block_count))
if (le64_to_cpu(fq_ip->sfq_count) * 3 > le64_to_cpu(sm_raw->sm_ip_block_count))
return true;
/* Don't let the main queue get too full either */
if(le64_to_cpu(fq_main->sfq_count) > mq_max)
if (le64_to_cpu(fq_main->sfq_count) > mq_max)
return true;
}
@@ -845,7 +849,7 @@ int apfs_transaction_join(struct super_block *sb, struct buffer_head *bh)
get_bh(bh);
bhi->bh = bh;
list_add(&bhi->list, &nx_trans->t_buffers);
nx_trans->t_buffers_count ++;
nx_trans->t_buffers_count++;
set_buffer_trans(bh);
bh->b_private = bhi;
@@ -883,6 +887,8 @@ void apfs_transaction_abort(struct super_block *sb)
struct apfs_nx_transaction *nx_trans = &nxi->nx_transaction;
struct apfs_bh_info *bhi, *tmp;
struct apfs_inode_info *ai, *ai_tmp;
struct apfs_spaceman *sm = NULL;
u32 bmap_idx;
if (sb->s_flags & SB_RDONLY) {
/* Transaction already aborted, do nothing */
@@ -923,6 +929,7 @@ void apfs_transaction_abort(struct super_block *sb)
list_for_each_entry(sbi, &nxi->vol_list, list) {
struct apfs_vol_transaction *vol_trans = &sbi->s_transaction;
if (!vol_trans->t_old_vsb)
continue;
@@ -945,10 +952,15 @@ void apfs_transaction_abort(struct super_block *sb)
vol_trans->t_old_cat_root.object.data = NULL;
}
brelse(APFS_SM(sb)->sm_ip);
APFS_SM(sb)->sm_ip = NULL;
brelse(APFS_SM(sb)->sm_bh);
APFS_SM(sb)->sm_bh = NULL;
sm = APFS_SM(sb);
if (sm) {
for (bmap_idx = 0; bmap_idx < sm->sm_ip_bmaps_count; ++bmap_idx) {
brelse(sm->sm_ip_bmaps[bmap_idx]);
sm->sm_ip_bmaps[bmap_idx] = NULL;
}
brelse(sm->sm_bh);
sm->sm_bh = NULL;
}
/*
* It's not possible to undo in-memory changes from old operations in
+126 -70
View File
@@ -112,9 +112,7 @@ static int apfs_xattr_extents_read(struct inode *parent,
{
struct super_block *sb = parent->i_sb;
struct apfs_dstream_info *dstream;
int length, blkcnt, i;
struct buffer_head **bhs = NULL;
int ret;
int length, ret;
dstream = kzalloc(sizeof(*dstream), GFP_KERNEL);
if (!dstream)
@@ -146,59 +144,11 @@ static int apfs_xattr_extents_read(struct inode *parent,
length = size;
}
blkcnt = (length + sb->s_blocksize - 1) >> sb->s_blocksize_bits;
bhs = kcalloc(blkcnt, sizeof(*bhs), GFP_KERNEL);
for (i = 0; i < blkcnt; i++) {
struct buffer_head *bh = NULL;
u64 bno = 0;
ret = apfs_logic_to_phys_bno(dstream, i, &bno);
if (ret)
goto out;
if (bno == 0) {
/* No holes in xattr dstreams, I believe */
apfs_err(sb, "xattr dstream has a hole");
ret = -EFSCORRUPTED;
goto out;
}
bhs[i] = __getblk_gfp(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
if (!bhs[i]) {
apfs_err(sb, "failed to map block 0x%llx", bno);
ret = -EIO;
goto out;
}
bh = bhs[i];
if (!buffer_uptodate(bh)) {
get_bh(bh);
lock_buffer(bh);
bh->b_end_io = end_buffer_read_sync;
apfs_submit_bh(REQ_OP_READ, 0, bh);
}
}
for (i = 0; i < blkcnt; i++) {
int off, tocopy;
wait_on_buffer(bhs[i]);
if (!buffer_uptodate(bhs[i])) {
apfs_err(sb, "failed to read a block");
ret = -EIO;
goto out;
}
off = i << sb->s_blocksize_bits;
tocopy = min(sb->s_blocksize, (unsigned long)(length - off));
memcpy(buffer + off, bhs[i]->b_data, tocopy);
}
ret = length;
ret = apfs_nonsparse_dstream_read(dstream, buffer, length, 0 /* offset */);
if (ret == 0)
ret = length;
out:
if (bhs) {
for (i = 0; i < blkcnt; i++)
brelse(bhs[i]);
kfree(bhs);
}
kfree(dstream);
return ret;
}
@@ -233,6 +183,123 @@ static int apfs_xattr_inline_read(struct apfs_xattr *xattr, void *buffer, size_t
return length;
}
/**
* apfs_xattr_get_compressed_data - Get the compressed data in a named attribute
* @inode: inode the attribute belongs to
* @name: name of the attribute
* @cdata: compressed data struct to set on return
*
* Returns 0 on success or a negative error code in case of failure.
*/
int apfs_xattr_get_compressed_data(struct inode *inode, const char *name, struct apfs_compressed_data *cdata)
{
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_query *query;
struct apfs_xattr xattr;
u64 cnid = apfs_ino(inode);
int ret;
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
apfs_init_xattr_key(cnid, name, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
if (ret) {
apfs_err(sb, "query failed for id 0x%llx (%s)", cnid, name);
goto done;
}
ret = apfs_xattr_from_query(query, &xattr);
if (ret) {
apfs_err(sb, "bad xattr record in inode 0x%llx", cnid);
goto done;
}
cdata->has_dstream = xattr.has_dstream;
if (cdata->has_dstream) {
struct apfs_dstream_info *dstream = NULL;
dstream = kzalloc(sizeof(*dstream), GFP_KERNEL);
if (!dstream) {
ret = -ENOMEM;
goto done;
}
apfs_dstream_from_xattr(sb, &xattr, dstream);
cdata->dstream = dstream;
cdata->size = dstream->ds_size;
} else {
void *data = NULL;
int len;
len = xattr.xdata_len;
if (len > APFS_XATTR_MAX_EMBEDDED_SIZE) {
apfs_err(sb, "inline xattr too big");
ret = -EFSCORRUPTED;
goto done;
}
data = kzalloc(len, GFP_KERNEL);
if (!data) {
ret = -ENOMEM;
goto done;
}
memcpy(data, xattr.xdata, len);
cdata->data = data;
cdata->size = len;
}
ret = 0;
done:
apfs_free_query(query);
return ret;
}
/**
* apfs_release_compressed_data - Clean up a compressed data struct
* @cdata: struct to clean up (but not free)
*/
void apfs_release_compressed_data(struct apfs_compressed_data *cdata)
{
if (!cdata)
return;
if (cdata->has_dstream) {
kfree(cdata->dstream);
cdata->dstream = NULL;
} else {
kfree(cdata->data);
cdata->data = NULL;
}
}
/**
* apfs_compressed_data_read - Read from a compressed data struct
* @cdata: compressed data struct
* @buf: destination buffer
* @count: exact number of bytes to read
* @offset: dstream offset to read from
*
* Returns 0 on success or a negative error code in case of failure.
*/
int apfs_compressed_data_read(struct apfs_compressed_data *cdata, void *buf, size_t count, u64 offset)
{
if (cdata->has_dstream)
return apfs_nonsparse_dstream_read(cdata->dstream, buf, count, offset);
if (offset > cdata->size || count > cdata->size - offset) {
apfs_err(NULL, "reading past the end (0x%llx-0x%llx)", offset, (unsigned long long)count);
/* No caller is expected to legitimately read out-of-bounds */
return -EFSCORRUPTED;
}
memcpy(buf, cdata->data + offset, count);
return 0;
}
/**
* __apfs_xattr_get - Find and read a named attribute
* @inode: inode the attribute belongs to
@@ -261,18 +328,15 @@ int ____apfs_xattr_get(struct inode *inode, const char *name, void *buffer,
{
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_xattr xattr;
u64 cnid = apfs_ino(inode);
int ret;
apfs_init_xattr_key(cnid, name, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
apfs_init_xattr_key(cnid, name, &query->key);
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -392,16 +456,13 @@ static int apfs_delete_any_xattr(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
int ret;
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
apfs_init_xattr_key(apfs_ino(inode), NULL /* name */, &key);
query->key = &key;
apfs_init_xattr_key(apfs_ino(inode), NULL /* name */, &query->key);
query->flags = APFS_QUERY_CAT | APFS_QUERY_ANY_NAME | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -465,7 +526,7 @@ static int apfs_build_xattr_key(const char *name, u64 ino, struct apfs_xattr_key
apfs_key_set_hdr(APFS_TYPE_XATTR, ino, key);
key->name_len = cpu_to_le16(namelen);
strcpy(key->name, name);
strscpy(key->name, name, namelen);
*key_p = key;
return key_len;
@@ -646,7 +707,6 @@ int apfs_xattr_set(struct inode *inode, const char *name, const void *value,
{
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query = NULL;
u64 cnid = apfs_ino(inode);
int key_len, val_len;
@@ -664,14 +724,12 @@ int apfs_xattr_set(struct inode *inode, const char *name, const void *value,
}
}
apfs_init_xattr_key(cnid, name, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query) {
ret = -ENOMEM;
goto done;
}
query->key = &key;
apfs_init_xattr_key(cnid, name, &query->key);
query->flags = APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
@@ -805,7 +863,6 @@ ssize_t apfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
struct apfs_key key;
struct apfs_query *query;
u64 cnid = apfs_ino(inode);
size_t free = size;
@@ -820,8 +877,7 @@ ssize_t apfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
}
/* We want all the xattrs for the cnid, regardless of the name */
apfs_init_xattr_key(cnid, NULL /* name */, &key);
query->key = &key;
apfs_init_xattr_key(cnid, NULL /* name */, &query->key);
query->flags = APFS_QUERY_CAT | APFS_QUERY_MULTIPLE | APFS_QUERY_EXACT;
while (1) {