42 Commits
Author SHA1 Message Date
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
Ernesto A. Fernández d4799475db 0.3.3
This release fixes builds for kernels below 5.3, which have all been
broken for a while. So far development has only been focused on Linux
5.3, and I've relied on reporters to find out about issues on other
versions. From now on I will at least run builds for a range of kernels
before each release, so hopefully this won't happen again.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-05-24 23:05:25 -03:00
Ernesto A. Fernández c54e3a7423 Silence b_blocknr width warnings on old kernels
While fixing the build for old kernels, I noticed that in some of them
you get a warning when you prink a buffer head's b_blocknr as 0x%llx.
I haven't checked but I'm guessing that, at some point, sector_t on
64-bit archs went from being 'unsigned long' to 'unsigned long long',
so these warnings went away. Anyway, just cast b_blocknr before printing
it in all kernels.

At some point I do need to attempt a 32-bit build though. It's been a
long time, and I'm sure there will be much worse problems than this one.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-05-19 18:49:38 -03:00
Ernesto A. Fernández b185ce13bd Fix builds before generic_copy_file_range()
Even with the previous patch, builds are still broken for all kernels
before 5.2. The problem is that generic_copy_file_range() wasn't
introduced until Linux 5.3. The good news is that we don't actually need
it for older kernels: as I explained in the commit message for the patch
that broke this, we can just returning an error code from
apfs_remap_file_range().

Fixes: 396650b056 ("Don't let copy_file_range() use clones")
Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-05-19 18:31:51 -03:00
Ernesto A. Fernández 42d70a8997 Fix build for kernels with separate clone/dedupe
Luflosi reports that the build is broken once again for Linux 4.14 and
4.19:

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

The problem here is that the clone and dedupe operations used to be
implemented with separate methods. Since Linux 4.20, they both use the
same ->remap_file_range(), with some flags passed to tell them apart.
So when I implemented clones I used this new version and broke the build
for the old ones. Fix it now.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-05-19 18:31:51 -03:00
Ernesto A. Fernández 97ba9b0dc8 0.3.2
This release improves error messages, which will hopefully make it
easier to communicate with bug reporters. A number of bugs are also
addressed, most importantly a missing check on the node count limit for
the free queues, which could trigger reports of corruption in some rare
cases (no data loss though, as far as I can see).

Support is added for the 6.3 kernel version, courtesy of Woody Suwalski.

I was hoping to wait until encryption was ready before making a release,
but I'm not finding much time for apfs these days so that could take a
while. In the meantime, it's better to get this stuff out the door.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-05-16 17:57:01 -03:00
Ernesto A. Fernández 8b99af84d1 Try to avoid failing insertion on fragmented nodes
I'm getting somewhat regular failures on generic/112 because the ip free
queue for the test image has a one-node limit, and it sometimes gets
full. The thing is, the node actually has plenty of room, but it's all
in the key free list, so I can't allocate values. From now on, if an
insertion fails with ENOSPC, check the available room and defragment
the node if it should be enough. Then retry the insertion instead of
jumping straight to a split.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-04-17 20:05:26 -03:00
Ernesto A. Fernández 3a605ec8ff Don't ignore error code in apfs_flush_fq_rec()
The previous patch was intended to trigger an immediate abort when a
free queue violated its own node limit. But I managed to trigger this
event again with generic/112, and to my surprise there were several
operations in dmesg before the abort finally happened. The problem here
is in apfs_flush_fq_rec(): when apfs_main_free() fails (in this case
because the ip free queue is full), the error code is reported but not
bubbled up. This is a mistake of course, so fix it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-04-12 17:49:12 -03:00
Ernesto A. Fernández ccefdd13d8 Don't allow free queues to exceed their node limit
Free queues have a node limit that is set by the mkfs. Very rarely,
generic/112 manages to break this limit, and corruption is reported.
Preventing corruption is always the number one priority, so add a check
for this limit after every free queue insertion so that we can abort
immediately when it fails.

Remember though, that there is a deeper issue here: either the btrees
are becoming too fragmented, or the flushes are not frequent enough. The
queues should never fill like this.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-04-12 16:47:55 -03:00
Ernesto A. Fernández 5beaaae75c Fix out-of-bounds read on snapshot ioctl
Don't call strlen() on the name argument of the ioctl, because it may
not be null-terminated. I'm not sure what the consequences of this are,
but I don't think it should be exploitable. Use strnlen() instead.

Also move the length check outside the transaction: we don't want simple
user mistakes triggering aborts. And fix the returned error code: there
is no on-disk corruption going on, just user error.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-04-05 23:06:22 -03:00
Ernesto A. Fernández 0f47fc762a Fix memory leak on xattr length overflow
Always free the allocated dstream in apfs_xattr_extents_read() before
returning.

Fixes: 362c4e32ab ("Allow internal reads of huge xattrs")
Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-04-05 22:21:53 -03:00
Ernesto A. Fernández d4797823ea Improve error reporting
The driver is much closer to being usable, so I might start getting
subtler bug reports soon. To make them easier to handle, put error
messages all over the place. I should have done this from the beginning,
but I guess I didn't fully understand the need back then.

From now my general policy will be to use apfs_warn() for user errors or
unsupported features; apfs_err() for things that are probably corruption
or io errors; and apfs_alert() for things that are most likely bugs.
These last two should be rare, so the same error/alert will be thrown by
several layers in the callstack to provide as much information as
possible. Be careful and don't flood the console on normal situations.

Also, make messages with a log level lower than warning output their
function name and line number, which I think will help debugging more
than the actual messages.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2023-04-05 21:59:24 -03:00
Woody Suwalski 24daddc890 Changes needed to build on kernel 6.3 2023-03-24 15:12:47 -03:00
20 changed files with 2245 additions and 875 deletions

No files matched your search

+71 -19
View File
@@ -299,6 +299,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 +320,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;
}
@@ -497,7 +500,7 @@ static inline void apfs_init_sibling_link_key(u64 ino, u64 id,
{
key->id = ino;
key->type = APFS_TYPE_SIBLING_LINK;
key->number = id; /* Only guessing */
key->number = id; /* Only guessing (TODO) */
key->name = NULL;
}
@@ -646,7 +649,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,17 +779,31 @@ struct apfs_xattr {
bool has_dstream;
};
#define apfs_emerg(sb, fmt, ...) apfs_msg(sb, KERN_EMERG, fmt, ##__VA_ARGS__)
#define apfs_alert(sb, fmt, ...) apfs_msg(sb, KERN_ALERT, fmt, ##__VA_ARGS__)
#define apfs_crit(sb, fmt, ...) apfs_msg(sb, KERN_CRIT, fmt, ##__VA_ARGS__)
#define apfs_err(sb, fmt, ...) apfs_msg(sb, KERN_ERR, fmt, ##__VA_ARGS__)
#define apfs_warn(sb, fmt, ...) apfs_msg(sb, KERN_WARNING, fmt, ##__VA_ARGS__)
#define apfs_notice(sb, fmt, ...) apfs_msg(sb, KERN_NOTICE, fmt, ##__VA_ARGS__)
#define apfs_info(sb, fmt, ...) apfs_msg(sb, KERN_INFO, fmt, ##__VA_ARGS__)
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
* common messages, there is no point and it makes the console look too busy.
*/
#define apfs_emerg(sb, fmt, ...) apfs_msg(sb, KERN_EMERG, __func__, __LINE__, fmt, ##__VA_ARGS__)
#define apfs_alert(sb, fmt, ...) apfs_msg(sb, KERN_ALERT, __func__, __LINE__, fmt, ##__VA_ARGS__)
#define apfs_crit(sb, fmt, ...) apfs_msg(sb, KERN_CRIT, __func__, __LINE__, fmt, ##__VA_ARGS__)
#define apfs_err(sb, fmt, ...) apfs_msg(sb, KERN_ERR, __func__, __LINE__, fmt, ##__VA_ARGS__)
#define apfs_warn(sb, fmt, ...) apfs_msg(sb, KERN_WARNING, NULL, 0, fmt, ##__VA_ARGS__)
#define apfs_notice(sb, fmt, ...) apfs_msg(sb, KERN_NOTICE, NULL, 0, fmt, ##__VA_ARGS__)
#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 apfs_debug(sb, fmt, ...) apfs_msg(sb, KERN_DEBUG, fmt, ##__VA_ARGS__)
#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)
#define apfs_debug(sb, fmt, ...) no_printk(fmt, ##__VA_ARGS__)
@@ -807,7 +826,6 @@ 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 struct apfs_node *apfs_omap_read_node(struct super_block *sb, u64 id);
extern int apfs_omap_lookup_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);
@@ -838,7 +856,7 @@ extern int apfs_rename(struct inode *old_dir, struct dentry *old_dentry,
unsigned int flags);
extern int apfs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
bool excl);
#else
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0)
extern int apfs_mknod(struct user_namespace *mnt_userns, struct inode *dir,
struct dentry *dentry, umode_t mode, dev_t rdev);
extern int apfs_mkdir(struct user_namespace *mnt_userns, struct inode *dir,
@@ -848,6 +866,16 @@ extern int apfs_rename(struct user_namespace *mnt_userns, struct inode *old_dir,
struct dentry *new_dentry, unsigned int flags);
extern int apfs_create(struct user_namespace *mnt_userns, struct inode *dir,
struct dentry *dentry, umode_t mode, bool excl);
#else
extern int apfs_mknod(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode, dev_t rdev);
extern int apfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode);
extern int apfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
struct dentry *old_dentry, struct inode *new_dir,
struct dentry *new_dentry, unsigned int flags);
extern int apfs_create(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode, bool excl);
#endif
extern int apfs_link(struct dentry *old_dentry, struct inode *dir,
@@ -871,10 +899,17 @@ extern int apfs_get_new_block(struct inode *inode, sector_t iblock,
struct buffer_head *bh_result, int create);
extern int APFS_GET_NEW_BLOCK_MAXOPS(void);
extern int apfs_truncate(struct apfs_dstream_info *dstream, loff_t new_size);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
extern loff_t apfs_remap_file_range(struct file *src_file, loff_t off, struct file *dst_file, loff_t destoff, loff_t len, unsigned int remap_flags);
#else
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 */
@@ -894,12 +929,19 @@ extern int apfs_dstream_adj_refcnt(struct apfs_dstream_info *dstream, u32 delta)
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0)
extern int apfs_setattr(struct dentry *dentry, struct iattr *iattr);
#else
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0)
extern int apfs_setattr(struct user_namespace *mnt_userns,
struct dentry *dentry, struct iattr *iattr);
#else
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);
@@ -909,16 +951,23 @@ extern int apfs_getattr(struct vfsmount *mnt, struct dentry *dentry,
#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0)
extern int apfs_getattr(const struct path *path, struct kstat *stat,
u32 request_mask, unsigned int query_flags);
#else
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0)
extern int apfs_getattr(struct user_namespace *mnt_userns,
const struct path *path, struct kstat *stat, u32 request_mask,
unsigned int query_flags);
#else
extern int apfs_getattr(struct mnt_idmap *idmap,
const struct path *path, struct kstat *stat, u32 request_mask,
unsigned int query_flags);
#endif
extern int apfs_crypto_adj_refcnt(struct super_block *sb, u64 crypto_id, int delta);
extern int APFS_CRYPTO_ADJ_REFCNT_MAXOPS(void);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 13, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0)
extern int apfs_fileattr_get(struct dentry *dentry, struct fileattr *fa);
extern int apfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, struct fileattr *fa);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 13, 0)
extern int apfs_fileattr_get(struct dentry *dentry, struct fileattr *fa);
extern int apfs_fileattr_set(struct user_namespace *mnt_userns, struct dentry *dentry, struct fileattr *fa);
#endif
@@ -935,8 +984,7 @@ extern int apfs_read_snap_meta_key(void *raw, int size, struct apfs_key *key);
extern int apfs_read_omap_snap_key(void *raw, int size, struct apfs_key *key);
/* message.c */
extern __printf(3, 4)
void apfs_msg(struct super_block *sb, const char *prefix, const char *fmt, ...);
extern __printf(5, 6) void apfs_msg(struct super_block *sb, const char *prefix, const char *func, int line, const char *fmt, ...);
/* node.c */
extern struct apfs_node *apfs_read_node(struct super_block *sb, u64 oid,
@@ -1006,6 +1054,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);
@@ -1019,6 +1070,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 */
+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 */
+92 -56
View File
@@ -35,13 +35,17 @@ static int apfs_child_from_query(struct apfs_query *query, u64 *child)
if (query->flags & APFS_QUERY_CAT && apfs_is_sealed(sb)) {
struct apfs_btn_index_node_val *index_val = NULL;
if (query->len != sizeof(*index_val))
if (query->len != sizeof(*index_val)) {
apfs_err(sb, "bad sealed index value length (%d)", query->len);
return -EFSCORRUPTED;
}
index_val = (struct apfs_btn_index_node_val *)(raw + query->off);
*child = le64_to_cpu(index_val->binv_child_oid) + apfs_catalog_base_oid(query);
} else {
if (query->len != 8) /* The data on a nonleaf node is the child id */
if (query->len != 8) { /* The data on a nonleaf node is the child id */
apfs_err(sb, "bad index value length (%d)", query->len);
return -EFSCORRUPTED;
}
*child = le64_to_cpup((__le64 *)(raw + query->off));
}
return 0;
@@ -190,8 +194,11 @@ int apfs_omap_lookup_block(struct super_block *sb, struct apfs_omap *omap,
query->flags |= APFS_QUERY_OMAP;
ret = apfs_btree_query(sb, &query);
if (ret)
if (ret) {
if (ret != -ENODATA)
apfs_err(sb, "query failed for oid 0x%llx, xid 0x%llx", id, apfs_mounted_xid(sb));
goto fail;
}
ret = apfs_omap_map_from_query(query, &map);
if (ret) {
@@ -211,6 +218,7 @@ int apfs_omap_lookup_block(struct super_block *sb, struct apfs_omap *omap,
new_bh = apfs_read_object_block(sb, *block, write, preserve);
if (IS_ERR(new_bh)) {
apfs_err(sb, "CoW failed for oid 0x%llx, xid 0x%llx", id, apfs_mounted_xid(sb));
ret = PTR_ERR(new_bh);
goto fail;
}
@@ -225,6 +233,8 @@ int apfs_omap_lookup_block(struct super_block *sb, struct apfs_omap *omap,
ret = apfs_btree_insert(query, &key, sizeof(key), &val, sizeof(val));
else
ret = apfs_btree_replace(query, &key, sizeof(key), &val, sizeof(val));
if (ret)
apfs_err(sb, "CoW omap update failed (oid 0x%llx, xid 0x%llx)", id, apfs_mounted_xid(sb));
*block = new_bh->b_blocknr;
brelse(new_bh);
@@ -265,8 +275,10 @@ int apfs_create_omap_rec(struct super_block *sb, u64 oid, u64 bno)
query->flags |= APFS_QUERY_OMAP;
ret = apfs_btree_query(sb, &query);
if (ret && ret != -ENODATA)
if (ret && ret != -ENODATA) {
apfs_err(sb, "query failed for oid 0x%llx, bno 0x%llx", oid, bno);
goto fail;
}
raw_key.ok_oid = cpu_to_le64(oid);
raw_key.ok_xid = cpu_to_le64(nxi->nx_xid);
@@ -276,8 +288,10 @@ int apfs_create_omap_rec(struct super_block *sb, u64 oid, u64 bno)
ret = apfs_btree_insert(query, &raw_key, sizeof(raw_key),
&raw_val, sizeof(raw_val));
if (ret)
if (ret) {
apfs_err(sb, "insertion failed for oid 0x%llx, bno 0x%llx", oid, bno);
goto fail;
}
apfs_omap_cache_save(omap, oid, bno);
@@ -311,13 +325,23 @@ int apfs_delete_omap_rec(struct super_block *sb, u64 oid)
query->flags |= APFS_QUERY_OMAP;
ret = apfs_btree_query(sb, &query);
if (ret == -ENODATA)
if (ret == -ENODATA) {
apfs_err(sb, "nonexistent record (oid 0x%llx)", oid);
ret = -EFSCORRUPTED;
if (!ret)
ret = apfs_btree_remove(query);
if (!ret)
apfs_omap_cache_delete(omap, oid);
goto fail;
}
if (ret) {
apfs_err(sb, "query failed (oid 0x%llx)", oid);
goto fail;
}
ret = apfs_btree_remove(query);
if (ret) {
apfs_err(sb, "removal failed (oid 0x%llx)", oid);
goto fail;
}
apfs_omap_cache_delete(omap, oid);
fail:
apfs_free_query(query);
return ret;
}
@@ -409,8 +433,10 @@ static int apfs_query_set_before_first(struct super_block *sb, struct apfs_query
/* Now go a level deeper */
node = apfs_read_node(sb, child_id, storage, false /* write */);
if (IS_ERR(node))
if (IS_ERR(node)) {
apfs_err(sb, "failed to read child 0x%llx of node 0x%llx", child_id, (*query)->node->object.oid);
return PTR_ERR(node);
}
parent = *query;
*query = apfs_alloc_query(node, parent);
@@ -422,7 +448,7 @@ static int apfs_query_set_before_first(struct super_block *sb, struct apfs_query
node = NULL;
}
apfs_alert(sb, "b-tree is corrupted");
apfs_err(sb, "btree is too high");
return -EFSCORRUPTED;
}
@@ -455,7 +481,7 @@ next_node:
* forever if the filesystem is damaged. 12 should be more
* than enough to map every block.
*/
apfs_alert(sb, "b-tree is corrupted");
apfs_err(sb, "btree is too high");
return -EFSCORRUPTED;
}
@@ -466,8 +492,10 @@ next_node:
* let the query give up at the root node.
*/
err = apfs_query_set_before_first(sb, query);
if (err)
if (err) {
apfs_err(sb, "failed to set before the first record");
return err;
}
return -ENODATA;
} else if (err == -EAGAIN) {
if (!(*query)->parent) /* We are at the root of the tree */
@@ -494,8 +522,10 @@ next_node:
/* Now go a level deeper and search the child */
node = apfs_read_node(sb, child_id, storage, false /* write */);
if (IS_ERR(node))
if (IS_ERR(node)) {
apfs_err(sb, "failed to read node 0x%llx", child_id);
return PTR_ERR(node);
}
if (node->object.oid != child_id)
apfs_debug(sb, "corrupt b-tree");
@@ -515,28 +545,6 @@ next_node:
goto next_node;
}
/**
* apfs_omap_read_node - Find and read a node from a b-tree
* @sb: filesystem superblock
* @id: id for the seeked node
*
* Returns NULL is case of failure, otherwise a pointer to the resulting
* apfs_node structure.
*/
struct apfs_node *apfs_omap_read_node(struct super_block *sb, u64 id)
{
struct apfs_node *result;
result = apfs_read_node(sb, id, APFS_OBJ_VIRTUAL, false /* write */);
if (IS_ERR(result))
return result;
if (result->object.oid != id)
apfs_debug(sb, "corrupt b-tree");
return result;
}
/**
* apfs_query_join_transaction - Add the found node to the current transaction
* @query: query that found the node
@@ -556,8 +564,10 @@ int apfs_query_join_transaction(struct apfs_query *query)
ASSERT(!apfs_node_is_root(node));
node = apfs_read_node(sb, oid, storage, true /* write */);
if (IS_ERR(node))
if (IS_ERR(node)) {
apfs_err(sb, "Cow failed for node 0x%llx", oid);
return PTR_ERR(node);
}
apfs_node_free(query->node);
query->node = node;
@@ -665,18 +675,20 @@ static int apfs_query_refresh(struct apfs_query *old_query)
* which are only expected when large inline xattr values are involved.
*/
if ((old_query->flags & APFS_QUERY_TREE_MASK) != APFS_QUERY_CAT) {
apfs_warn(sb, "attempt to refresh a non-catalog query");
apfs_err(sb, "non-catalog query");
return -EFSCORRUPTED;
}
if (!apfs_node_is_leaf(node)) {
apfs_warn(sb, "attempt to refresh a non-leaf query");
apfs_err(sb, "non-leaf query");
return -EFSCORRUPTED;
}
/* Build a new query that points exactly to the same key */
err = apfs_read_cat_key(raw + old_query->key_off, old_query->key_len, &new_key, hashed);
if (err)
if (err) {
apfs_err(sb, "failed to read the key");
return err;
}
new_query = apfs_alloc_query(APFS_SB(sb)->s_cat_root, NULL /* parent */);
if (!new_query)
return -ENOMEM;
@@ -684,8 +696,10 @@ static int apfs_query_refresh(struct apfs_query *old_query)
new_query->flags = APFS_QUERY_CAT | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &new_query);
if (err)
if (err) {
apfs_err(sb, "failed to rerun");
goto fail;
}
/* Set the original query flags and key on the new query */
for (ancestor = new_query; ancestor; ancestor = ancestor->parent) {
@@ -739,6 +753,7 @@ int apfs_btree_insert(struct apfs_query *query, void *key, int key_len,
void *val, int val_len)
{
struct apfs_node *node = query->node;
struct super_block *sb = node->object.sb;
struct apfs_btree_node_phys *node_raw;
int err;
@@ -748,8 +763,10 @@ int apfs_btree_insert(struct apfs_query *query, void *key, int key_len,
key_len, val_len);
err = apfs_query_join_transaction(query);
if (err)
if (err) {
apfs_err(sb, "query join failed");
return err;
}
again:
node = query->node;
@@ -760,18 +777,23 @@ again:
if (err == -ENOSPC) {
if (!query->parent && !apfs_node_is_root(node)) {
err = apfs_query_refresh(query);
if (err)
if (err) {
apfs_err(sb, "query refresh failed");
return err;
}
if (node->records == 1) {
/* The new record just won't fit in the node */
return apfs_create_single_rec_node(query, key, key_len, val, val_len);
}
}
err = apfs_node_split(query);
if (err)
if (err) {
apfs_err(sb, "node split failed");
return err;
}
goto again;
} else if (err) {
apfs_err(sb, "node record insertion failed");
return err;
}
@@ -779,6 +801,8 @@ again:
if (query->parent && query->index == 0) {
err = apfs_btree_replace(query->parent, key, key_len,
NULL /* val */, 0 /* val_len */);
if (err)
apfs_err(sb, "parent update failed");
}
return err;
}
@@ -792,6 +816,7 @@ again:
int apfs_btree_remove(struct apfs_query *query)
{
struct apfs_node *node = query->node;
struct super_block *sb = node->object.sb;
struct apfs_btree_node_phys *node_raw;
int later_entries = node->records - query->index - 1;
int err;
@@ -804,8 +829,10 @@ int apfs_btree_remove(struct apfs_query *query)
apfs_btree_change_node_count(query, -1 /* change */);
err = apfs_query_join_transaction(query);
if (err)
if (err) {
apfs_err(sb, "query join failed");
return err;
}
node = query->node;
node_raw = (void *)query->node->object.data;
@@ -815,11 +842,10 @@ int apfs_btree_remove(struct apfs_query *query)
if (query->parent) {
/* Just get rid of the node */
return apfs_delete_node(query);
} else {
/* All descendants are gone, root is the whole tree */
node_raw->btn_level = 0;
node->flags |= APFS_BTNODE_LEAF;
}
/* All descendants are gone, root is the whole tree */
node_raw->btn_level = 0;
node->flags |= APFS_BTNODE_LEAF;
}
/* The first key in a node must match the parent record's */
@@ -834,8 +860,10 @@ int apfs_btree_remove(struct apfs_query *query)
err = apfs_btree_replace(query->parent, key, first_key_len,
NULL /* val */, 0 /* val_len */);
if (err)
if (err) {
apfs_err(sb, "parent update failed");
return err;
}
}
/* Remove the entry from the table of contents */
@@ -902,8 +930,10 @@ int apfs_btree_replace(struct apfs_query *query, void *key, int key_len,
}
err = apfs_query_join_transaction(query);
if (err)
if (err) {
apfs_err(sb, "query join failed");
return err;
}
again:
node = query->node;
@@ -914,8 +944,10 @@ again:
if (key && query->parent && query->index == 0) {
err = apfs_btree_replace(query->parent, key, key_len,
NULL /* val */, 0 /* val_len */);
if (err)
if (err) {
apfs_err(sb, "parent update failed");
return err;
}
}
err = apfs_node_replace(query, key, key_len, val, val_len);
@@ -923,16 +955,20 @@ again:
if (!query->parent && !apfs_node_is_root(node)) {
if (node->records == 1) {
/* Node is defragmented, ENOSPC is absurd */
WARN_ON(1);
apfs_alert(sb, "absurd ENOSPC in empty node");
return -EFSCORRUPTED;
}
err = apfs_query_refresh(query);
if (err)
if (err) {
apfs_err(sb, "query refresh failed");
return err;
}
}
err = apfs_node_split(query);
if (err)
if (err) {
apfs_err(sb, "node split failed");
return err;
}
goto again;
}
return err;
+327 -300
View File
File diff suppressed because it is too large. Load diff
+187 -52
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1,5 +1,5 @@
PACKAGE_NAME="linux-apfs-rw"
PACKAGE_VERSION="0.3.1"
PACKAGE_VERSION="0.3.5"
BUILT_MODULE_NAME[0]="apfs"
DEST_MODULE_LOCATION[0]="/extra"
+402 -84
View File
File diff suppressed because it is too large. Load diff
+26 -3
View File
@@ -42,8 +42,10 @@ static vm_fault_t apfs_page_mkwrite(struct vm_fault *vmf)
apfs_inode_join_transaction(sb, inode);
err = apfs_inode_create_exclusive_dstream(inode);
if (err)
if (err) {
apfs_err(sb, "dstream creation failed for ino 0x%llx", apfs_ino(inode));
goto out_abort;
}
lock_page(page);
wait_for_stable_page(page);
@@ -77,8 +79,10 @@ static vm_fault_t apfs_page_mkwrite(struct vm_fault *vmf)
unlock_page(page); /* XXX: race? */
err = block_page_mkwrite(vma, vmf, apfs_get_new_block);
if (err)
if (err) {
apfs_err(sb, "mkwrite failed for ino 0x%llx", apfs_ino(inode));
goto out_abort;
}
set_page_dirty(page);
/* An immediate commit would leave the page unlocked */
@@ -95,7 +99,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;
}
@@ -106,7 +114,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;
@@ -141,8 +149,23 @@ const struct file_operations apfs_file_operations = {
.open = generic_file_open,
.fsync = apfs_fsync,
.unlocked_ioctl = apfs_file_ioctl,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
.copy_file_range = generic_copy_file_range,
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
.remap_file_range = apfs_remap_file_range,
#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)
+262 -78
View File
File diff suppressed because it is too large. Load diff
+34 -10
View File
@@ -81,8 +81,10 @@ int apfs_keycmp(struct apfs_key *k1, struct apfs_key *k2)
*/
int apfs_read_cat_key(void *raw, int size, struct apfs_key *key, bool hashed)
{
if (size < sizeof(struct apfs_key_header))
if (size < sizeof(struct apfs_key_header)) {
apfs_err(NULL, "bad key length (%d)", size);
return -EFSCORRUPTED;
}
key->id = apfs_cat_cnid((struct apfs_key_header *)raw);
key->type = apfs_cat_type((struct apfs_key_header *)raw);
@@ -92,6 +94,7 @@ int apfs_read_cat_key(void *raw, int size, struct apfs_key *key, bool hashed)
if (size < sizeof(struct apfs_drec_hashed_key) + 1 ||
*((char *)raw + size - 1) != 0) {
/* Filename must have NULL-termination */
apfs_err(NULL, "invalid drec key (%d)", size);
return -EFSCORRUPTED;
}
/* Name length is not used in key comparisons, only the hash */
@@ -103,6 +106,7 @@ int apfs_read_cat_key(void *raw, int size, struct apfs_key *key, bool hashed)
if (size < sizeof(struct apfs_drec_key) + 1 ||
*((char *)raw + size - 1) != 0) {
/* Filename must have NULL-termination */
apfs_err(NULL, "invalid drec key (%d)", size);
return -EFSCORRUPTED;
}
/* There's no hash */
@@ -114,21 +118,26 @@ int apfs_read_cat_key(void *raw, int size, struct apfs_key *key, bool hashed)
if (size < sizeof(struct apfs_xattr_key) + 1 ||
*((char *)raw + size - 1) != 0) {
/* xattr name must have NULL-termination */
apfs_err(NULL, "invalid xattr key (%d)", size);
return -EFSCORRUPTED;
}
key->number = 0;
key->name = ((struct apfs_xattr_key *)raw)->name;
break;
case APFS_TYPE_FILE_EXTENT:
if (size != sizeof(struct apfs_file_extent_key))
if (size != sizeof(struct apfs_file_extent_key)) {
apfs_err(NULL, "bad key length (%d)", size);
return -EFSCORRUPTED;
}
key->number = le64_to_cpu(
((struct apfs_file_extent_key *)raw)->logical_addr);
key->name = NULL;
break;
case APFS_TYPE_SIBLING_LINK:
if (size != sizeof(struct apfs_sibling_link_key))
if (size != sizeof(struct apfs_sibling_link_key)) {
apfs_err(NULL, "bad key length (%d)", size);
return -EFSCORRUPTED;
}
key->number = le64_to_cpu(
((struct apfs_sibling_link_key *)raw)->sibling_id);
key->name = NULL;
@@ -146,8 +155,10 @@ int apfs_read_fext_key(void *raw, int size, struct apfs_key *key)
{
struct apfs_fext_tree_key *raw_key;
if (size != sizeof(*raw_key))
if (size != sizeof(*raw_key)) {
apfs_err(NULL, "bad key length (%d)", size);
return -EFSCORRUPTED;
}
raw_key = raw;
key->id = le64_to_cpu(raw_key->private_id);
@@ -169,8 +180,10 @@ int apfs_read_free_queue_key(void *raw, int size, struct apfs_key *key)
{
struct apfs_spaceman_free_queue_key *raw_key;
if (size < sizeof(struct apfs_spaceman_free_queue_key))
if (size < sizeof(struct apfs_spaceman_free_queue_key)) {
apfs_err(NULL, "bad key length (%d)", size);
return -EFSCORRUPTED;
}
raw_key = raw;
key->id = le64_to_cpu(raw_key->sfqk_xid);
@@ -191,8 +204,10 @@ int apfs_read_free_queue_key(void *raw, int size, struct apfs_key *key)
*/
int apfs_read_omap_key(void *raw, int size, struct apfs_key *key)
{
if (size < sizeof(struct apfs_omap_key))
if (size < sizeof(struct apfs_omap_key)) {
apfs_err(NULL, "bad key length (%d)", size);
return -EFSCORRUPTED;
}
key->id = le64_to_cpu(((struct apfs_omap_key *)raw)->ok_oid);
key->type = 0;
@@ -212,8 +227,10 @@ int apfs_read_omap_key(void *raw, int size, struct apfs_key *key)
*/
int apfs_read_extentref_key(void *raw, int size, struct apfs_key *key)
{
if (size != sizeof(struct apfs_phys_ext_key))
if (size != sizeof(struct apfs_phys_ext_key)) {
apfs_err(NULL, "bad key length (%d)", size);
return -EFSCORRUPTED;
}
key->id = apfs_cat_cnid((struct apfs_key_header *)raw);
key->type = apfs_cat_type((struct apfs_key_header *)raw);
key->number = 0;
@@ -223,21 +240,26 @@ int apfs_read_extentref_key(void *raw, int size, struct apfs_key *key)
int apfs_read_snap_meta_key(void *raw, int size, struct apfs_key *key)
{
if (size < sizeof(struct apfs_key_header))
if (size < sizeof(struct apfs_key_header)) {
apfs_err(NULL, "bad key length (%d)", size);
return -EFSCORRUPTED;
}
key->id = apfs_cat_cnid((struct apfs_key_header *)raw);
key->type = apfs_cat_type((struct apfs_key_header *)raw);
key->number = 0;
switch (key->type) {
case APFS_TYPE_SNAP_METADATA:
if (size != sizeof(struct apfs_snap_metadata_key))
if (size != sizeof(struct apfs_snap_metadata_key)) {
apfs_err(NULL, "bad key length (%d)", size);
return -EFSCORRUPTED;
}
key->name = NULL;
return 0;
case APFS_TYPE_SNAP_NAME:
if (size < sizeof(struct apfs_snap_name_key) + 1 || *((char *)raw + size - 1) != 0) {
/* snapshot name must have NULL-termination */
apfs_err(NULL, "invalid snap name key (%d)", size);
return -EFSCORRUPTED;
}
key->name = ((struct apfs_snap_name_key *)raw)->name;
@@ -251,8 +273,10 @@ int apfs_read_omap_snap_key(void *raw, int size, struct apfs_key *key)
{
__le64 *xid = NULL;
if (size != sizeof(*xid))
if (size != sizeof(*xid)) {
apfs_err(NULL, "bad key length (%d)", size);
return -EFSCORRUPTED;
}
xid = raw;
key->id = le64_to_cpup(xid);
+9 -2
View File
@@ -6,8 +6,9 @@
#include <linux/fs.h>
#include "apfs.h"
void apfs_msg(struct super_block *sb, const char *prefix, const char *fmt, ...)
void apfs_msg(struct super_block *sb, const char *prefix, const char *func, int line, const char *fmt, ...)
{
char *sb_id = NULL;
struct va_format vaf;
va_list args;
@@ -16,7 +17,13 @@ void apfs_msg(struct super_block *sb, const char *prefix, const char *fmt, ...)
vaf.fmt = fmt;
vaf.va = &args;
printk("%sAPFS (%s): %pV\n", prefix, sb->s_id, &vaf);
/* The superblock is not available to all callers */
sb_id = sb ? sb->s_id : "?";
if (func)
printk("%sAPFS (%s): %pV (%s:%d)\n", prefix, sb_id, &vaf, func, line);
else
printk("%sAPFS (%s): %pV\n", prefix, sb_id, &vaf);
va_end(args);
}
+10 -3
View File
@@ -18,13 +18,17 @@ static struct dentry *apfs_lookup(struct inode *dir, struct dentry *dentry,
return ERR_PTR(-ENAMETOOLONG);
err = apfs_inode_by_name(dir, &dentry->d_name, &ino);
if (err && err != -ENODATA)
if (err && err != -ENODATA) {
apfs_err(dir->i_sb, "inode lookup by name failed");
return ERR_PTR(err);
}
if (!err) {
inode = apfs_iget(dir->i_sb, ino);
if (IS_ERR(inode))
if (IS_ERR(inode)) {
apfs_err(dir->i_sb, "iget failed");
return ERR_CAST(inode);
}
}
return d_splice_alias(inode, dentry);
@@ -33,9 +37,12 @@ static struct dentry *apfs_lookup(struct inode *dir, struct dentry *dentry,
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0)
static int apfs_symlink(struct inode *dir, struct dentry *dentry,
const char *symname)
#else
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0)
static int apfs_symlink(struct user_namespace *mnt_userns, struct inode *dir,
struct dentry *dentry, const char *symname)
#else
static int apfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
#endif
{
/* Symlink permissions don't mean anything and their value is fixed */
+214 -60
View File
File diff suppressed because it is too large. Load diff
+32 -9
View File
@@ -78,8 +78,10 @@ static int apfs_cpm_lookup_oid(struct super_block *sb,
u32 map_count = le32_to_cpu(cpm->cpm_count);
int i;
if (map_count > apfs_max_maps_per_block(sb))
if (map_count > apfs_max_maps_per_block(sb)) {
apfs_err(sb, "block has too many maps (%d)", map_count);
return -EFSCORRUPTED;
}
for (i = 0; i < map_count; ++i) {
struct apfs_checkpoint_mapping *map = &cpm->cpm_map[i];
@@ -214,14 +216,17 @@ int apfs_remove_cpoint_map(struct super_block *sb, u64 bno)
int err = 0;
bh = apfs_read_cpm_block(sb);
if (!bh)
if (!bh) {
apfs_err(sb, "failed to read cpm block");
return -EIO;
}
cpm = (struct apfs_checkpoint_map_phys *)bh->b_data;
apfs_assert_in_transaction(sb, &cpm->cpm_o);
/* TODO: multiple cpm blocks? */
cpm_count = le32_to_cpu(cpm->cpm_count);
if (cpm_count > apfs_max_maps_per_block(sb)) {
apfs_err(sb, "block has too many maps (%d)", cpm_count);
err = -EFSCORRUPTED;
goto fail;
}
@@ -240,6 +245,7 @@ int apfs_remove_cpoint_map(struct super_block *sb, u64 bno)
map->cpm_paddr = cpu_to_le64(apfs_data_index_to_bno(sb, curr_off - 1));
}
if (!bno_map) {
apfs_err(sb, "no mapping for bno 0x%llx", bno);
err = -EFSCORRUPTED;
goto fail;
}
@@ -269,8 +275,10 @@ struct buffer_head *apfs_read_ephemeral_object(struct super_block *sb, u64 oid)
u32 desc_len = le32_to_cpu(raw_sb->nx_xp_desc_len);
u32 i;
if (!desc_blks || !desc_len)
if (!desc_blks || !desc_len) {
apfs_err(sb, "no blocks in checkpoint descriptor area");
return ERR_PTR(-EFSCORRUPTED);
}
/* Last block in the area is superblock; the rest are mapping blocks */
for (i = 0; i < desc_len - 1; ++i) {
@@ -281,8 +289,10 @@ struct buffer_head *apfs_read_ephemeral_object(struct super_block *sb, u64 oid)
int err;
bh = apfs_sb_bread(sb, cpm_bno);
if (!bh)
if (!bh) {
apfs_err(sb, "failed to read cpm block");
return ERR_PTR(-EIO);
}
cpm = (struct apfs_checkpoint_map_phys *)bh->b_data;
err = apfs_cpm_lookup_oid(sb, cpm, oid, &obj_bno);
@@ -290,15 +300,20 @@ struct buffer_head *apfs_read_ephemeral_object(struct super_block *sb, u64 oid)
cpm = NULL;
if (err == -EAGAIN) /* Search the next mapping block */
continue;
if (err)
if (err) {
apfs_err(sb, "cpm lookup failed for oid 0x%llx", oid);
return ERR_PTR(err);
}
bh = apfs_sb_bread(sb, obj_bno);
if (!bh)
if (!bh) {
apfs_err(sb, "failed to read ephemeral block");
return ERR_PTR(-EIO);
}
return bh;
}
return ERR_PTR(-EFSCORRUPTED); /* The mapping is missing */
apfs_err(sb, "no mapping for oid 0x%llx", oid);
return ERR_PTR(-EFSCORRUPTED);
}
/**
@@ -325,13 +340,16 @@ struct buffer_head *apfs_read_object_block(struct super_block *sb, u64 bno, bool
ASSERT(write || !preserve);
bh = apfs_sb_bread(sb, bno);
if (!bh)
if (!bh) {
apfs_err(sb, "failed to read object block 0x%llx", bno);
return ERR_PTR(-EIO);
}
obj = (struct apfs_obj_phys *)bh->b_data;
type = le32_to_cpu(obj->o_type);
ASSERT(!(type & APFS_OBJ_EPHEMERAL));
if (nxi->nx_flags & APFS_CHECK_NODES && !apfs_obj_verify_csum(sb, bh)) {
apfs_err(sb, "bad checksum for object in block 0x%llx", bno);
err = -EFSBADCRC;
goto fail;
}
@@ -345,10 +363,13 @@ struct buffer_head *apfs_read_object_block(struct super_block *sb, u64 bno, bool
return bh;
err = apfs_spaceman_allocate_block(sb, &new_bno, true /* backwards */);
if (err)
if (err) {
apfs_err(sb, "block allocation failed");
goto fail;
}
new_bh = apfs_getblk(sb, new_bno);
if (!new_bh) {
apfs_err(sb, "failed to map block for CoW (0x%llx)", new_bno);
err = -EIO;
goto fail;
}
@@ -361,6 +382,8 @@ struct buffer_head *apfs_read_object_block(struct super_block *sb, u64 bno, bool
*/
if (!preserve) {
err = apfs_free_queue_insert(sb, bh->b_blocknr, 1);
if (err)
apfs_err(sb, "free queue insertion failed for 0x%llx", (unsigned long long)bh->b_blocknr);
} else if ((type & APFS_OBJECT_TYPE_MASK) != APFS_OBJECT_TYPE_FS) {
vsb_raw = APFS_SB(sb)->s_vsb_raw;
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
+104 -36
View File
@@ -24,11 +24,14 @@ static int apfs_create_superblock_snapshot(struct super_block *sb, u64 *bno)
int err;
err = apfs_spaceman_allocate_block(sb, bno, true /* backwards */);
if (err)
if (err) {
apfs_err(sb, "block allocation failed");
goto fail;
}
snap_bh = apfs_getblk(sb, *bno);
if (!snap_bh) {
apfs_err(sb, "failed to map block for volume snap (0x%llx)", *bno);
err = -EIO;
goto fail;
}
@@ -88,11 +91,14 @@ static int apfs_create_snap_metadata_rec(struct inode *mntpoint, struct apfs_nod
err = apfs_btree_query(sb, &query);
if (err == 0) {
apfs_err(sb, "record exists for xid 0x%llx", xid);
err = -EFSCORRUPTED;
goto fail;
}
if (err != -ENODATA)
if (err != -ENODATA) {
apfs_err(sb, "query failed for xid 0x%llx", xid);
goto fail;
}
apfs_key_set_hdr(APFS_TYPE_SNAP_METADATA, xid, &raw_key);
@@ -110,13 +116,15 @@ 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;
le64_add_cpu(&vsb_raw->apfs_next_obj_id, 1);
err = apfs_btree_insert(query, &raw_key, sizeof(raw_key), raw_val, val_len);
if (err)
apfs_err(sb, "insertion failed for xid 0x%llx", xid);
fail:
kfree(raw_val);
raw_val = NULL;
@@ -152,8 +160,10 @@ static int apfs_create_snap_name_rec(struct apfs_node *snap_root, const char *na
err = -EEXIST;
goto fail;
}
if (err != -ENODATA)
if (err != -ENODATA) {
apfs_err(sb, "query failed (%s)", name);
goto fail;
}
key_len = sizeof(*raw_key) + name_len + 1;
raw_key = kzalloc(key_len, GFP_KERNEL);
@@ -163,11 +173,13 @@ 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);
err = apfs_btree_insert(query, raw_key, key_len, &raw_val, sizeof(raw_val));
if (err)
apfs_err(sb, "insertion failed (%s)", name);
fail:
kfree(raw_key);
raw_key = NULL;
@@ -176,30 +188,29 @@ fail:
return err;
}
static int apfs_create_snap_meta_records(struct inode *mntpoint, const char *name, u64 sblock_oid)
static int apfs_create_snap_meta_records(struct inode *mntpoint, const char *name, int name_len, u64 sblock_oid)
{
struct super_block *sb = mntpoint->i_sb;
struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
struct apfs_node *snap_root = NULL;
size_t name_len;
int err;
snap_root = apfs_read_node(sb, le64_to_cpu(vsb_raw->apfs_snap_meta_tree_oid), APFS_OBJ_PHYSICAL, true /* write */);
if (IS_ERR(snap_root))
if (IS_ERR(snap_root)) {
apfs_err(sb, "failed to read snap meta root 0x%llx", le64_to_cpu(vsb_raw->apfs_snap_meta_tree_oid));
return PTR_ERR(snap_root);
}
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
vsb_raw->apfs_snap_meta_tree_oid = cpu_to_le64(snap_root->object.oid);
name_len = strlen(name);
if (name_len > APFS_SNAP_MAX_NAMELEN) {
err = -EFSCORRUPTED;
err = apfs_create_snap_metadata_rec(mntpoint, snap_root, name, name_len, sblock_oid);
if (err) {
apfs_err(sb, "meta rec creation failed");
goto fail;
}
err = apfs_create_snap_metadata_rec(mntpoint, snap_root, name, name_len, sblock_oid);
if (err)
goto fail;
err = apfs_create_snap_name_rec(snap_root, name, name_len);
if (err)
apfs_err(sb, "name rec creation failed");
fail:
apfs_node_free(snap_root);
@@ -213,8 +224,10 @@ static int apfs_create_new_extentref_tree(struct super_block *sb)
int err;
err = apfs_make_empty_btree_root(sb, APFS_OBJECT_TYPE_BLOCKREFTREE, &oid);
if (err)
if (err) {
apfs_err(sb, "failed to make empty root");
return err;
}
apfs_assert_in_transaction(sb, &vsb_raw->apfs_o);
vsb_raw->apfs_extentref_tree_oid = cpu_to_le64(oid);
@@ -242,13 +255,17 @@ static int apfs_update_omap_snap_tree(struct super_block *sb, __le64 *oid_p)
/* An empty snapshots tree may not even have a root yet */
if (!oid) {
err = apfs_make_empty_btree_root(sb, APFS_OBJECT_TYPE_OMAP_SNAPSHOT, &oid);
if (err)
if (err) {
apfs_err(sb, "failed to make empty root");
return err;
}
}
root = apfs_read_node(sb, oid, APFS_OBJ_PHYSICAL, true /* write */);
if (IS_ERR(root))
if (IS_ERR(root)) {
apfs_err(sb, "failed to read omap snap root 0x%llx", oid);
return PTR_ERR(root);
}
oid = 0;
apfs_init_omap_snap_key(nxi->nx_xid, &key);
@@ -263,14 +280,19 @@ static int apfs_update_omap_snap_tree(struct super_block *sb, __le64 *oid_p)
err = apfs_btree_query(sb, &query);
if (err == 0) {
apfs_err(sb, "record exists for xid 0x%llx", nxi->nx_xid);
err = -EFSCORRUPTED;
goto fail;
}
if (err != -ENODATA)
if (err != -ENODATA) {
apfs_err(sb, "query failed for xid 0x%llx", nxi->nx_xid);
goto fail;
}
raw_key = cpu_to_le64(nxi->nx_xid);
err = apfs_btree_insert(query, &raw_key, sizeof(raw_key), &raw_val, sizeof(raw_val));
if (err)
apfs_err(sb, "insertion failed for xid 0x%llx", nxi->nx_xid);
*oid_p = cpu_to_le64(root->object.block_nr);
fail:
@@ -300,14 +322,18 @@ static int apfs_update_omap_snapshots(struct super_block *sb)
omap_blk = le64_to_cpu(vsb_raw->apfs_omap_oid);
bh = apfs_read_object_block(sb, omap_blk, true /* write */, false /* preserve */);
if (IS_ERR(bh))
if (IS_ERR(bh)) {
apfs_err(sb, "CoW failed for bno 0x%llx", omap_blk);
return PTR_ERR(bh);
}
omap = (struct apfs_omap_phys *)bh->b_data;
apfs_assert_in_transaction(sb, &omap->om_o);
le32_add_cpu(&omap->om_snap_count, 1);
omap->om_most_recent_snap = cpu_to_le64(xid);
err = apfs_update_omap_snap_tree(sb, &omap->om_snapshot_tree_oid);
if (err)
apfs_err(sb, "omap snap tree update failed");
omap = NULL;
brelse(bh);
@@ -322,7 +348,7 @@ static int apfs_update_omap_snapshots(struct super_block *sb)
*
* Returns 0 on success, or a negative error code in case of failure.
*/
static int apfs_do_ioc_take_snapshot(struct inode *mntpoint, const char *name)
static int apfs_do_ioc_take_snapshot(struct inode *mntpoint, const char *name, int name_len)
{
struct super_block *sb = mntpoint->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
@@ -343,24 +369,34 @@ static int apfs_do_ioc_take_snapshot(struct inode *mntpoint, const char *name)
* changes to the layout after the snapshot is set up.
*/
err = apfs_transaction_flush_all_inodes(sb);
if (err)
if (err) {
apfs_err(sb, "failed to flush all inodes");
goto fail;
}
err = apfs_create_superblock_snapshot(sb, &sblock_oid);
if (err)
if (err) {
apfs_err(sb, "failed to snapshot superblock");
goto fail;
}
err = apfs_create_snap_meta_records(mntpoint, name, sblock_oid);
if (err)
err = apfs_create_snap_meta_records(mntpoint, name, name_len, sblock_oid);
if (err) {
apfs_err(sb, "failed to create snap meta records");
goto fail;
}
err = apfs_create_new_extentref_tree(sb);
if (err)
if (err) {
apfs_err(sb, "failed to create new extref tree");
goto fail;
}
err = apfs_update_omap_snapshots(sb);
if (err)
if (err) {
apfs_err(sb, "failed to update omap snapshots");
goto fail;
}
/*
* The official reference allows old implementations to ignore extended
@@ -397,6 +433,7 @@ int apfs_ioc_take_snapshot(struct file *file, void __user *user_arg)
struct inode *inode = file_inode(file);
struct super_block *sb = inode->i_sb;
struct apfs_ioctl_snap_name *arg = NULL;
size_t name_len;
int err;
if (apfs_ino(inode) != APFS_ROOT_DIR_INO_NUM) {
@@ -406,8 +443,10 @@ int apfs_ioc_take_snapshot(struct file *file, void __user *user_arg)
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0)
if (!inode_owner_or_capable(inode))
#else
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0)
if (!inode_owner_or_capable(&init_user_ns, inode))
#else
if (!inode_owner_or_capable(&nop_mnt_idmap, inode))
#endif
return -EPERM;
@@ -426,7 +465,14 @@ int apfs_ioc_take_snapshot(struct file *file, void __user *user_arg)
goto fail;
}
err = apfs_do_ioc_take_snapshot(inode, arg->name);
name_len = strnlen(arg->name, sizeof(arg->name));
if (name_len == sizeof(arg->name)) {
apfs_warn(sb, "snapshot name is too long (%d)", (int)name_len);
err = -EINVAL;
goto fail;
}
err = apfs_do_ioc_take_snapshot(inode, arg->name, name_len);
fail:
kfree(arg);
arg = NULL;
@@ -439,8 +485,10 @@ static int apfs_snap_xid_from_query(struct apfs_query *query, u64 *xid)
char *raw = query->node->object.data;
__le64 *val = NULL;
if (query->len != sizeof(*val))
if (query->len != sizeof(*val)) {
apfs_err(query->node->object.sb, "bad value length (%d)", query->len);
return -EFSCORRUPTED;
}
val = (__le64 *)(raw + query->off);
*xid = le64_to_cpup(val);
@@ -463,10 +511,15 @@ static int apfs_snapshot_name_to_xid(struct apfs_node *snap_root, const char *na
query->flags |= APFS_QUERY_SNAP_META | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
if (err)
if (err) {
if (err != -ENODATA)
apfs_err(sb, "query failed (%s)", name);
goto fail;
}
err = apfs_snap_xid_from_query(query, xid);
if (err)
apfs_err(sb, "bad snap name record (%s)", name);
fail:
apfs_free_query(query);
query = NULL;
@@ -478,8 +531,10 @@ static int apfs_snap_sblock_from_query(struct apfs_query *query, u64 *sblock_oid
char *raw = query->node->object.data;
struct apfs_snap_metadata_val *val = NULL;
if (query->len < sizeof(*val))
if (query->len < sizeof(*val)) {
apfs_err(query->node->object.sb, "bad value length (%d)", query->len);
return -EFSCORRUPTED;
}
val = (struct apfs_snap_metadata_val *)(raw + query->off);
*sblock_oid = le64_to_cpu(val->sblock_oid);
@@ -502,10 +557,14 @@ static int apfs_snapshot_xid_to_sblock(struct apfs_node *snap_root, u64 xid, u64
query->flags |= APFS_QUERY_SNAP_META | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
if (err)
if (err) {
apfs_err(sb, "query failed for xid 0x%llx", xid);
goto fail;
}
err = apfs_snap_sblock_from_query(query, sblock_oid);
if (err)
apfs_err(sb, "bad snap meta record for xid 0x%llx", xid);
fail:
apfs_free_query(query);
query = NULL;
@@ -533,17 +592,24 @@ int apfs_switch_to_snapshot(struct super_block *sb)
ASSERT(sb->s_flags & SB_RDONLY);
name = sbi->s_snap_name;
if (strlen(name) > APFS_SNAP_MAX_NAMELEN)
if (strlen(name) > APFS_SNAP_MAX_NAMELEN) {
apfs_warn(sb, "snapshot name is too long");
return -EINVAL;
}
snap_root = apfs_read_node(sb, le64_to_cpu(vsb_raw->apfs_snap_meta_tree_oid), APFS_OBJ_PHYSICAL, false /* write */);
if (IS_ERR(snap_root))
if (IS_ERR(snap_root)) {
apfs_err(sb, "failed to read snap meta root 0x%llx", le64_to_cpu(vsb_raw->apfs_snap_meta_tree_oid));
return PTR_ERR(snap_root);
}
vsb_raw = NULL;
err = apfs_snapshot_name_to_xid(snap_root, name, &xid);
if (err)
if (err) {
if (err == -ENODATA)
apfs_info(sb, "no snapshot under that name (%s)", name);
goto fail;
}
sbi->s_snap_xid = xid;
err = apfs_snapshot_xid_to_sblock(snap_root, xid, &sblock_oid);
@@ -552,6 +618,8 @@ int apfs_switch_to_snapshot(struct super_block *sb)
apfs_unmap_volume_super(sb);
err = apfs_map_volume_super_bno(sb, sblock_oid, nxi->nx_flags & APFS_CHECK_NODES);
if (err)
apfs_err(sb, "failed to map volume block 0x%llx", sblock_oid);
fail:
apfs_node_free(snap_root);
+132 -39
View File
@@ -87,8 +87,10 @@ static int apfs_read_spaceman_dev(struct super_block *sb,
/* Check that all the cib addresses fit in the spaceman block */
if ((long long)spaceman->sm_addr_offset +
(long long)spaceman->sm_cib_count * sizeof(u64) > sb->s_blocksize)
(long long)spaceman->sm_cib_count * sizeof(u64) > sb->s_blocksize) {
apfs_err(sb, "too many cibs (%u)", spaceman->sm_cib_count);
return -EFSCORRUPTED;
}
return 0;
}
@@ -146,10 +148,14 @@ static int apfs_update_ip_bm_free_next(struct super_block *sb)
__le16 *free_next;
int i;
if (free_next_off > sb->s_blocksize)
if (free_next_off > sb->s_blocksize) {
apfs_err(sb, "offset out of bounds (%u)", free_next_off);
return -EFSCORRUPTED;
if (free_next_off + bmap_count * sizeof(*free_next) > sb->s_blocksize)
}
if (free_next_off + bmap_count * sizeof(*free_next) > sb->s_blocksize) {
apfs_err(sb, "free next out of bounds (%u-%u)", free_next_off, bmap_count * (u32)sizeof(*free_next));
return -EFSCORRUPTED;
}
free_next = (void *)raw + free_next_off;
for (i = 0; i < bmap_count; ++i) {
@@ -187,19 +193,25 @@ static int apfs_rotate_ip_bitmaps(struct super_block *sb)
}
xid = apfs_spaceman_get_64(sb, le32_to_cpu(sm_raw->sm_ip_bm_xid_offset));
if (!xid)
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);
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)
if (!curr_bmap_off) {
apfs_err(sb, "bmap offset out of bounds (%u)", le32_to_cpu(sm_raw->sm_ip_bitmap_offset));
return -EFSCORRUPTED;
}
old_bh = apfs_sb_bread(sb, bmap_base + le64_to_cpup(curr_bmap_off));
if (!old_bh)
if (!old_bh) {
apfs_err(sb, "failed to read current ip bitmap (0x%llx)", bmap_base + le64_to_cpup(curr_bmap_off));
return -EIO;
}
*curr_bmap_off = cpu_to_le64(free_head);
free_head = (free_head + 1) % bmap_length;
@@ -207,11 +219,14 @@ static int apfs_rotate_ip_bitmaps(struct super_block *sb)
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)
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));
if (!new_bh) {
apfs_err(sb, "failed to map block for CoW (0x%llx)", bmap_base + le64_to_cpup(curr_bmap_off));
err = -EIO;
goto out;
}
@@ -251,8 +266,10 @@ static int apfs_fq_rec_from_query(struct apfs_query *query, struct apfs_fq_rec *
char *raw = query->node->object.data;
struct apfs_spaceman_free_queue_key *key;
if (query->key_len != sizeof(*key))
if (query->key_len != sizeof(*key)) {
apfs_err(query->node->object.sb, "bad key length (%d)", query->key_len);
return -EFSCORRUPTED;
}
key = (struct apfs_spaceman_free_queue_key *)(raw + query->key_off);
fqrec->xid = le64_to_cpu(key->sfqk_xid);
@@ -265,6 +282,7 @@ static int apfs_fq_rec_from_query(struct apfs_query *query, struct apfs_fq_rec *
fqrec->len = le64_to_cpup((__le64 *)(raw + query->off));
return 0;
}
apfs_err(query->node->object.sb, "bad value length (%d)", query->len);
return -EFSCORRUPTED;
}
@@ -331,23 +349,32 @@ static int apfs_flush_fq_rec(struct apfs_node *root, u64 xid, u64 *len)
query->flags |= APFS_QUERY_FREE_QUEUE | APFS_QUERY_ANY_NUMBER | APFS_QUERY_EXACT;
err = apfs_btree_query(sb, &query);
if (err)
if (err) {
if (err != -ENODATA)
apfs_err(sb, "query failed for xid 0x%llx, paddr 0x%llx", xid, 0ULL);
goto fail;
}
err = apfs_fq_rec_from_query(query, &fqrec);
if (err)
if (err) {
apfs_err(sb, "bad free queue rec for xid 0x%llx", xid);
goto fail;
}
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);
if(err)
if (err) {
apfs_err(sb, "freeing block 0x%llx failed (%d)", (unsigned long long)bno, err);
goto fail;
}
}
err = apfs_btree_remove(query);
if (err)
if (err) {
apfs_err(sb, "removal failed for xid 0x%llx", xid);
goto fail;
}
*len = fqrec.len;
fail:
@@ -379,7 +406,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);
@@ -391,8 +418,10 @@ static int apfs_flush_free_queue(struct super_block *sb, unsigned qid)
fq_root = apfs_read_node(sb, le64_to_cpu(fq->sfq_tree_oid),
APFS_OBJ_EPHEMERAL, true /* write */);
if (IS_ERR(fq_root))
if (IS_ERR(fq_root)) {
apfs_err(sb, "failed to read fq root 0x%llx", le64_to_cpu(fq->sfq_tree_oid));
return PTR_ERR(fq_root);
}
/* Preserve a few transactions */
while (oldest + 4 < nxi->nx_xid) {
@@ -407,6 +436,7 @@ static int apfs_flush_free_queue(struct super_block *sb, unsigned qid)
err = 0;
break;
} else if (err) {
apfs_err(sb, "failed to flush fq");
goto fail;
} else {
le64_add_cpu(&fq->sfq_count, -count);
@@ -461,8 +491,10 @@ int apfs_read_spaceman(struct super_block *sb)
spaceman->sm_free_cache_base = spaceman->sm_free_cache_blkcnt = 0;
sm_bh = apfs_read_ephemeral_object(sb, oid);
if (IS_ERR(sm_bh))
if (IS_ERR(sm_bh)) {
apfs_err(sb, "ephemeral read failed for oid 0x%llx", oid);
return PTR_ERR(sm_bh);
}
sm_raw = (struct apfs_spaceman_phys *)sm_bh->b_data;
sm_flags = le32_to_cpu(sm_raw->sm_flags);
@@ -472,13 +504,16 @@ int apfs_read_spaceman(struct super_block *sb)
/* Only read the main device; fusion drives are not yet supported */
err = apfs_read_spaceman_dev(sb, &sm_raw->sm_dev[APFS_SD_MAIN]);
if (err)
if (err) {
apfs_err(sb, "failed to read main device");
goto fail;
}
spaceman->sm_blocks_per_chunk =
le32_to_cpu(sm_raw->sm_blocks_per_chunk);
spaceman->sm_chunks_per_cib = le32_to_cpu(sm_raw->sm_chunks_per_cib);
if (spaceman->sm_chunks_per_cib > apfs_max_chunks_per_cib(sb)) {
apfs_err(sb, "too many chunks per cib (%u)", spaceman->sm_chunks_per_cib);
err = -EFSCORRUPTED;
goto fail;
}
@@ -486,14 +521,20 @@ int apfs_read_spaceman(struct super_block *sb)
spaceman->sm_bh = sm_bh;
spaceman->sm_raw = sm_raw;
err = apfs_rotate_ip_bitmaps(sb);
if (err)
if (err) {
apfs_err(sb, "failed to rotate ip bitmaps");
goto fail;
}
err = apfs_flush_free_queue(sb, APFS_SFQ_IP);
if (err)
if (err) {
apfs_err(sb, "failed to flush ip fq");
goto fail;
}
err = apfs_flush_free_queue(sb, APFS_SFQ_MAIN);
if (err)
if (err) {
apfs_err(sb, "failed to flush main fq");
goto fail;
}
return 0;
fail:
@@ -625,11 +666,14 @@ int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
struct apfs_spaceman *sm = APFS_SM(sb);
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
struct apfs_spaceman_free_queue *fq;
struct apfs_node *fq_root;
struct apfs_node *fq_root = NULL;
struct apfs_btree_info *fq_info = NULL;
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;
if (apfs_block_in_ip(sm, bno))
@@ -639,8 +683,10 @@ int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
fq_root = apfs_read_node(sb, le64_to_cpu(fq->sfq_tree_oid),
APFS_OBJ_EPHEMERAL, true /* write */);
if (IS_ERR(fq_root))
if (IS_ERR(fq_root)) {
apfs_err(sb, "failed to read fq root 0x%llx", le64_to_cpu(fq->sfq_tree_oid));
return PTR_ERR(fq_root);
}
query = apfs_alloc_query(fq_root, NULL /* parent */);
if (!query) {
@@ -653,8 +699,10 @@ int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
query->flags |= APFS_QUERY_FREE_QUEUE;
err = apfs_btree_query(sb, &query);
if (err && err != -ENODATA)
if (err && err != -ENODATA) {
apfs_err(sb, "query failed for xid 0x%llx, paddr 0x%llx", nxi->nx_xid, bno);
goto fail;
}
raw_key.sfqk_xid = cpu_to_le64(nxi->nx_xid);
raw_key.sfqk_paddr = cpu_to_le64(bno);
@@ -665,8 +713,24 @@ int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count)
raw_val = cpu_to_le64(count);
err = apfs_btree_insert(query, &raw_key, sizeof(raw_key), &raw_val, sizeof(raw_val));
}
if (err)
if (err) {
apfs_err(sb, "insertion failed for xid 0x%llx, paddr 0x%llx", nxi->nx_xid, bno);
goto fail;
}
fq_info = (void *)fq_root->object.data + sb->s_blocksize - sizeof(*fq_info);
node_count = le64_to_cpu(fq_info->bt_node_count);
node_limit = le16_to_cpu(fq->sfq_tree_node_limit);
if (node_count > node_limit) {
/*
* Ideally this should never happen, but at this point I can't
* be certain of that (TODO). If it does happen, it's best to
* abort and avoid corruption.
*/
apfs_alert(sb, "free queue has too many nodes (%llu > %u)", node_count, node_limit);
err = -EFSCORRUPTED;
goto fail;
}
if (!fq->sfq_oldest_xid)
fq->sfq_oldest_xid = cpu_to_le64(nxi->nx_xid);
@@ -719,8 +783,10 @@ int apfs_free_queue_insert(struct super_block *sb, u64 bno, u64 count)
/* Failed to extend the cache, so flush it and replace it */
err = apfs_free_queue_insert_nocache(sb, sm->sm_free_cache_base, sm->sm_free_cache_blkcnt);
if (err)
if (err) {
apfs_err(sb, "fq cache flush failed (0x%llx-0x%llx)", sm->sm_free_cache_base, sm->sm_free_cache_blkcnt);
return err;
}
sm->sm_free_cache_base = bno;
sm->sm_free_cache_blkcnt = count;
return 0;
@@ -763,20 +829,25 @@ static int apfs_chunk_alloc_free(struct super_block *sb,
if (!ci->ci_bitmap_addr) {
u64 bmap_bno;
/* Freeing a block in an all-free chunk? */
if(!is_alloc)
if (!is_alloc) {
apfs_err(sb, "attempt to free block in all-free chunk");
return -EFSCORRUPTED;
}
/* All blocks in this chunk are free */
bmap_bno = apfs_ip_find_free(sb);
if (!bmap_bno)
if (!bmap_bno) {
apfs_err(sb, "no free blocks in ip");
return -EFSCORRUPTED;
}
bmap_bh = apfs_sb_bread(sb, bmap_bno);
} else {
bmap_bh = apfs_sb_bread(sb, le64_to_cpu(ci->ci_bitmap_addr));
}
if (!bmap_bh)
if (!bmap_bh) {
apfs_err(sb, "failed to read bitmap block");
return -EIO;
}
bmap = bmap_bh->b_data;
if (!ci->ci_bitmap_addr) {
memset(bmap, 0, sb->s_blocksize);
@@ -790,12 +861,14 @@ static int apfs_chunk_alloc_free(struct super_block *sb,
new_bmap_bno = apfs_ip_find_free(sb);
if (!new_bmap_bno) {
apfs_err(sb, "no free blocks in ip");
err = -EFSCORRUPTED;
goto fail;
}
new_bmap_bh = apfs_getblk(sb, new_bmap_bno);
if (!new_bmap_bh) {
apfs_err(sb, "failed to map new bmap block (0x%llx)", new_bmap_bno);
err = -EIO;
goto fail;
}
@@ -803,8 +876,10 @@ static int apfs_chunk_alloc_free(struct super_block *sb,
err = apfs_free_queue_insert(sb, bmap_bh->b_blocknr, 1);
brelse(bmap_bh);
bmap_bh = new_bmap_bh;
if (err)
if (err) {
apfs_err(sb, "free queue insertion failed");
goto fail;
}
bmap = bmap_bh->b_data;
}
apfs_ip_mark_used(sb, bmap_bh->b_blocknr);
@@ -816,12 +891,14 @@ static int apfs_chunk_alloc_free(struct super_block *sb,
new_cib_bno = apfs_ip_find_free(sb);
if (!new_cib_bno) {
apfs_err(sb, "no free blocks in ip");
err = -EFSCORRUPTED;
goto fail;
}
new_cib_bh = apfs_getblk(sb, new_cib_bno);
if (!new_cib_bh) {
apfs_err(sb, "failed to map new cib block (0x%llx)", new_cib_bno);
err = -EIO;
goto fail;
}
@@ -829,8 +906,10 @@ static int apfs_chunk_alloc_free(struct super_block *sb,
err = apfs_free_queue_insert(sb, (*cib_bh)->b_blocknr, 1);
brelse(*cib_bh);
*cib_bh = new_cib_bh;
if (err)
if (err) {
apfs_err(sb, "free queue insertion failed");
goto fail;
}
err = apfs_transaction_join(sb, *cib_bh);
if (err)
@@ -851,22 +930,22 @@ 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");
err = -EFSCORRUPTED;
goto fail;
}
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;
@@ -924,8 +1003,10 @@ static int apfs_cib_allocate_block(struct super_block *sb,
/* Avoid out-of-bounds operations on corrupted cibs */
chunk_count = le32_to_cpu(cib->cib_chunk_info_count);
if (chunk_count > sm->sm_chunks_per_cib)
if (chunk_count > sm->sm_chunks_per_cib) {
apfs_err(sb, "too many chunks in cib (%u)", chunk_count);
return -EFSCORRUPTED;
}
for (i = 0; i < chunk_count; ++i) {
int index;
@@ -936,6 +1017,8 @@ static int apfs_cib_allocate_block(struct super_block *sb,
err = apfs_chunk_allocate_block(sb, cib_bh, index, bno);
if (err == -ENOSPC) /* This chunk is full */
continue;
if (err)
apfs_err(sb, "error during allocation");
return err;
}
return -ENOSPC;
@@ -966,8 +1049,10 @@ int apfs_spaceman_allocate_block(struct super_block *sb, u64 *bno, bool backward
cib_bno = apfs_spaceman_read_cib_addr(sb, index);
cib_bh = apfs_sb_bread(sb, cib_bno);
if (!cib_bh)
if (!cib_bh) {
apfs_err(sb, "failed to read cib");
return -EIO;
}
err = apfs_cib_allocate_block(sb, &cib_bh, bno, backwards);
if (!err) {
@@ -980,6 +1065,8 @@ int apfs_spaceman_allocate_block(struct super_block *sb, u64 *bno, bool backward
brelse(cib_bh);
if (err == -ENOSPC) /* This cib is full */
continue;
if (err)
apfs_err(sb, "error during allocation");
return err;
}
return -ENOSPC;
@@ -1013,8 +1100,10 @@ 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;
}
/* TODO: use bitshifts instead of do_div() */
chunk_idx = bno;
do_div(chunk_idx, sm->sm_blocks_per_chunk);
@@ -1023,8 +1112,10 @@ static int apfs_main_free(struct super_block *sb, u64 bno)
cib_bno = apfs_spaceman_read_cib_addr(sb, cib_idx);
cib_bh = apfs_sb_bread(sb, cib_bno);
if (!cib_bh)
if (!cib_bh) {
apfs_err(sb, "failed to read cib");
return -EIO;
}
err = apfs_chunk_free(sb, &cib_bh, chunk_idx, bno);
if (!err) {
@@ -1035,6 +1126,8 @@ static int apfs_main_free(struct super_block *sb, u64 bno)
set_buffer_csum(sm->sm_bh);
}
brelse(cib_bh);
if (err)
apfs_err(sb, "error during free");
return err;
}
+56 -4
View File
@@ -272,11 +272,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 +288,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,7 +310,12 @@ 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);
out:
@@ -959,12 +970,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 +1334,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 +1368,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);
@@ -1445,13 +1482,12 @@ static int apfs_test_super(struct super_block *sb, void *data)
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;
}
static struct file_system_type apfs_fs_type;
/*
* Wrapper for lookup_bdev() that supports older kernels.
*/
@@ -1480,7 +1516,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 +1540,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 +1571,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 +1592,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 +1622,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)
+3 -2
View File
@@ -35,6 +35,7 @@ static const char *apfs_get_link(struct dentry *dentry, struct inode *inode,
size = __apfs_xattr_get(inode, APFS_XATTR_NAME_SYMLINK,
NULL /* buffer */, 0 /* size */);
if (size < 0) { /* TODO: return a better error code */
apfs_err(sb, "symlink size read failed");
err = size;
goto fail;
}
@@ -47,13 +48,13 @@ static const char *apfs_get_link(struct dentry *dentry, struct inode *inode,
size = __apfs_xattr_get(inode, APFS_XATTR_NAME_SYMLINK, target, size);
if (size < 0) {
apfs_err(sb, "symlink read failed");
err = size;
goto fail;
}
if (size == 0 || *(target + size - 1) != 0) {
/* Target path must be NULL-terminated */
apfs_alert(sb, "bad link target in inode 0x%llx",
apfs_ino(inode));
apfs_err(sb, "bad link target in inode 0x%llx", apfs_ino(inode));
err = -EFSCORRUPTED;
goto fail;
}
+67 -28
View File
@@ -95,13 +95,17 @@ static int apfs_cpoint_init_desc(struct super_block *sb)
u32 new_sb_index;
int err;
if (!desc_blks || !desc_len)
if (!desc_blks || !desc_len) {
apfs_err(sb, "bad checkpoint descriptor area");
return -EFSCORRUPTED;
}
err = apfs_cpoint_init_area(sb, desc_base, desc_blks,
desc_next, desc_len);
if (err)
if (err) {
apfs_err(sb, "failed to init area");
return err;
}
/* Now update the superblock with the new checkpoint */
@@ -149,13 +153,17 @@ static int apfs_cpoint_init_data(struct super_block *sb)
u32 data_len = le32_to_cpu(raw_sb->nx_xp_data_len);
int err;
if (!data_blks || !data_len)
if (!data_blks || !data_len) {
apfs_err(sb, "bad checkpoint data area");
return -EFSCORRUPTED;
}
err = apfs_cpoint_init_area(sb, data_base, data_blks,
data_next, data_len);
if (err)
if (err) {
apfs_err(sb, "failed to init area");
return err;
}
/* Apparently the previous checkpoint gets invalidated right away */
apfs_assert_in_transaction(sb, &raw_sb->nx_o);
@@ -219,14 +227,17 @@ static int apfs_update_mapping_blocks(struct super_block *sb)
int j;
bh = apfs_sb_bread(sb, desc_base + desc_curr);
if (!bh)
if (!bh) {
apfs_err(sb, "failed to read cpm block");
return -EINVAL;
}
ASSERT(buffer_trans(bh));
cpm = (struct apfs_checkpoint_map_phys *)bh->b_data;
apfs_assert_in_transaction(sb, &cpm->cpm_o);
map_count = le32_to_cpu(cpm->cpm_count);
if (map_count > apfs_max_maps_per_block(sb)) {
apfs_err(sb, "block has too many maps (%d)", map_count);
brelse(bh);
return -EFSCORRUPTED;
}
@@ -290,6 +301,7 @@ int apfs_cpoint_data_free(struct super_block *sb, u64 bno)
new_bh = apfs_getblk(sb, apfs_data_index_to_bno(sb, i));
old_bh = apfs_sb_bread(sb, apfs_data_index_to_bno(sb, i + 1));
if (!new_bh || !old_bh) {
apfs_err(sb, "failed to map blocks in data area");
brelse(new_bh);
brelse(old_bh);
return -EIO;
@@ -327,12 +339,18 @@ static int apfs_checkpoint_start(struct super_block *sb)
ASSERT(!(sb->s_flags & SB_RDONLY));
err = apfs_cpoint_init_desc(sb);
if (err)
if (err) {
apfs_err(sb, "failed to init descriptor area");
return err;
}
err = apfs_cpoint_init_data(sb);
if (err)
if (err) {
apfs_err(sb, "failed to init data area");
return err;
}
err = apfs_update_mapping_blocks(sb);
if (err)
apfs_err(sb, "failed to update checkpoint mappings");
return err;
}
@@ -444,12 +462,16 @@ int apfs_transaction_start(struct super_block *sb, struct apfs_max_ops maxops)
nx_trans->t_starts_count = 0;
err = apfs_checkpoint_start(sb);
if (err)
if (err) {
apfs_err(sb, "failed to start a new checkpoint");
goto fail;
}
err = apfs_read_spaceman(sb);
if (err)
if (err) {
apfs_err(sb, "failed to read the spaceman");
goto fail;
}
}
/* Don't start transactions unless we are sure they fit in disk */
@@ -457,8 +479,10 @@ int apfs_transaction_start(struct super_block *sb, struct apfs_max_ops maxops)
/* Commit what we have so far to flush the queues */
nx_trans->t_state |= APFS_NX_TRANS_FORCE_COMMIT;
err = apfs_transaction_commit(sb);
if (err)
if (err) {
apfs_err(sb, "commit failed");
goto fail;
}
return -ENOSPC;
}
@@ -473,16 +497,22 @@ int apfs_transaction_start(struct super_block *sb, struct apfs_max_ops maxops)
get_bh(vol_trans->t_old_omap_root.object.o_bh);
err = apfs_map_volume_super(sb, true /* write */);
if (err)
if (err) {
apfs_err(sb, "CoW failed for volume super");
goto fail;
}
/* TODO: don't copy these nodes for transactions that don't use them */
err = apfs_read_omap(sb, true /* write */);
if (err)
if (err) {
apfs_err(sb, "CoW failed for omap");
goto fail;
}
err = apfs_read_catalog(sb, true /* write */);
if (err)
if (err) {
apfs_err(sb, "Cow failed for catalog");
goto fail;
}
}
nx_trans->t_starts_count++;
@@ -501,7 +531,7 @@ fail:
static void apfs_end_buffer_write_sync(struct buffer_head *bh, int uptodate)
{
struct page *page = NULL;
bool must_unlock, is_metadata;
bool is_metadata;
page = bh->b_page;
get_page(page);
@@ -512,7 +542,7 @@ static void apfs_end_buffer_write_sync(struct buffer_head *bh, int uptodate)
bh = NULL;
/* Future writes to mmapped areas should fault for CoW */
must_unlock = trylock_page(page);
lock_page(page);
page_mkclean(page);
/* XXX: otherwise, the page cache fills up and crashes the machine */
@@ -524,8 +554,7 @@ static void apfs_end_buffer_write_sync(struct buffer_head *bh, int uptodate)
#endif
}
if (must_unlock)
unlock_page(page);
unlock_page(page);
put_page(page);
}
@@ -577,8 +606,10 @@ int apfs_transaction_flush_all_inodes(struct super_block *sb)
nx_trans->t_state = 0;
/* Transaction aborted by ->evict_inode(), error code is lost */
if (sb->s_flags & SB_RDONLY)
if (sb->s_flags & SB_RDONLY) {
apfs_err(sb, "abort during inode eviction");
return -EROFS;
}
}
return err;
@@ -602,8 +633,10 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
/* Before committing the bhs, write all inode metadata to them */
err = apfs_transaction_flush_all_inodes(sb);
if (err)
if (err) {
apfs_err(sb, "failed to flush all inodes");
return err;
}
/*
* Now that nothing else will be freed, flush the last update to the
@@ -611,8 +644,10 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
*/
if (sm->sm_free_cache_base) {
err = apfs_free_queue_insert_nocache(sb, sm->sm_free_cache_base, sm->sm_free_cache_blkcnt);
if (err)
if (err) {
apfs_err(sb, "fq cache flush failed (0x%llx-0x%llx)", sm->sm_free_cache_base, sm->sm_free_cache_blkcnt);
return err;
}
sm->sm_free_cache_base = sm->sm_free_cache_blkcnt = 0;
}
@@ -639,8 +674,10 @@ static int apfs_transaction_commit_nx(struct super_block *sb)
apfs_submit_bh(REQ_OP_WRITE, REQ_SYNC, bh);
}
err = apfs_checkpoint_end(sb);
if (err)
if (err) {
apfs_err(sb, "failed to end the checkpoint");
return err;
}
/* Success: forget the old container and volume superblocks */
brelse(nx_trans->t_old_msb);
@@ -648,6 +685,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;
@@ -698,7 +736,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;
@@ -713,7 +751,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;
@@ -723,11 +761,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;
}
@@ -750,7 +788,7 @@ int apfs_transaction_commit(struct super_block *sb)
if (apfs_transaction_need_commit(sb)) {
err = apfs_transaction_commit_nx(sb);
if (err) {
apfs_warn(sb, "transaction commit failed");
apfs_err(sb, "transaction commit failed");
return err;
}
}
@@ -807,7 +845,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;
@@ -858,7 +896,7 @@ void apfs_transaction_abort(struct super_block *sb)
ASSERT(nx_trans->t_old_msb);
nx_trans->t_state = 0;
apfs_warn(sb, "aborting transaction");
apfs_err(sb, "aborting transaction");
--nxi->nx_xid;
list_for_each_entry_safe(bhi, tmp, &nx_trans->t_buffers, list) {
@@ -885,6 +923,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;
+202 -75
View File
@@ -23,32 +23,43 @@
static int apfs_xattr_from_query(struct apfs_query *query,
struct apfs_xattr *xattr)
{
struct super_block *sb = query->node->object.sb;
struct apfs_xattr_val *xattr_val;
struct apfs_xattr_key *xattr_key;
char *raw = query->node->object.data;
int datalen = query->len - sizeof(*xattr_val);
int namelen = query->key_len - sizeof(*xattr_key);
if (namelen < 1 || datalen < 0)
if (namelen < 1 || datalen < 0) {
apfs_err(sb, "bad length of name (%d) or data (%d)", namelen, datalen);
return -EFSCORRUPTED;
}
xattr_val = (struct apfs_xattr_val *)(raw + query->off);
xattr_key = (struct apfs_xattr_key *)(raw + query->key_off);
if (namelen != le16_to_cpu(xattr_key->name_len))
if (namelen != le16_to_cpu(xattr_key->name_len)) {
apfs_err(sb, "inconsistent name length (%d vs %d)", namelen, le16_to_cpu(xattr_key->name_len));
return -EFSCORRUPTED;
}
/* The xattr name must be NULL-terminated */
if (xattr_key->name[namelen - 1] != 0)
if (xattr_key->name[namelen - 1] != 0) {
apfs_err(sb, "null termination missing");
return -EFSCORRUPTED;
}
xattr->has_dstream = le16_to_cpu(xattr_val->flags) &
APFS_XATTR_DATA_STREAM;
if (xattr->has_dstream && datalen != sizeof(struct apfs_xattr_dstream))
if (xattr->has_dstream && datalen != sizeof(struct apfs_xattr_dstream)) {
apfs_err(sb, "bad data length (%d)", datalen);
return -EFSCORRUPTED;
if (!xattr->has_dstream && datalen != le16_to_cpu(xattr_val->xdata_len))
}
if (!xattr->has_dstream && datalen != le16_to_cpu(xattr_val->xdata_len)) {
apfs_err(sb, "inconsistent data length (%d vs %d)", datalen, le16_to_cpu(xattr_val->xdata_len));
return -EFSCORRUPTED;
}
xattr->name = xattr_key->name;
xattr->name_len = namelen - 1; /* Don't count the NULL termination */
@@ -101,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)
@@ -113,7 +122,9 @@ static int apfs_xattr_extents_read(struct inode *parent,
length = dstream->ds_size;
if (length < 0 || length < dstream->ds_size) {
/* TODO: avoid overflow here for huge compressed files */
return -E2BIG;
apfs_warn(sb, "xattr is too big to read on linux (0x%llx)", dstream->ds_size);
ret = -E2BIG;
goto out;
}
if (!buffer) {
@@ -133,56 +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 */
ret = -EFSCORRUPTED;
goto out;
}
bhs[i] = __getblk_gfp(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
if (!bhs[i]) {
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])) {
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;
}
@@ -217,6 +183,125 @@ 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_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;
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
@@ -260,12 +345,15 @@ int ____apfs_xattr_get(struct inode *inode, const char *name, void *buffer,
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
if (ret)
if (ret) {
if (ret != -ENODATA)
apfs_err(sb, "query failed for id 0x%llx (%s)", cnid, name);
goto done;
}
ret = apfs_xattr_from_query(query, &xattr);
if (ret) {
apfs_alert(sb, "bad xattr record in inode 0x%llx", cnid);
apfs_err(sb, "bad xattr record in inode 0x%llx", cnid);
goto done;
}
@@ -300,8 +388,10 @@ static int apfs_xattr_get(struct inode *inode, const char *name, void *buffer, s
down_read(&nxi->nx_big_sem);
ret = __apfs_xattr_get(inode, name, buffer, size);
up_read(&nxi->nx_big_sem);
if (ret > XATTR_SIZE_MAX)
if (ret > XATTR_SIZE_MAX) {
apfs_warn(inode->i_sb, "xattr is too big to read on linux (%d)", ret);
return -E2BIG;
}
return ret;
}
@@ -327,8 +417,10 @@ static int apfs_delete_xattr(struct apfs_query *query)
int err;
err = apfs_xattr_from_query(query, &xattr);
if (err)
if (err) {
apfs_err(sb, "bad xattr record");
return err;
}
if (!xattr.has_dstream)
return apfs_btree_remove(query);
@@ -344,9 +436,13 @@ static int apfs_delete_xattr(struct apfs_query *query)
* really need to add some assertions (TODO).
*/
err = apfs_btree_remove(query);
if (err)
if (err) {
apfs_err(sb, "removal failed");
goto fail;
}
err = apfs_truncate(dstream, 0);
if (err)
apfs_err(sb, "truncation failed for dstream 0x%llx", dstream->ds_id);
fail:
kfree(dstream);
@@ -381,12 +477,16 @@ static int apfs_delete_any_xattr(struct inode *inode)
if (ret) {
if (ret == -ENODATA)
ret = 0; /* No more xattrs, we are done */
else
apfs_err(sb, "query failed for ino 0x%llx", apfs_ino(inode));
goto out;
}
ret = apfs_delete_xattr(query);
if (!ret)
ret = -EAGAIN;
else
apfs_err(sb, "xattr deletion failed");
out:
apfs_free_query(query);
@@ -434,7 +534,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;
@@ -531,10 +631,13 @@ static struct apfs_dstream_info *apfs_create_xattr_dstream(struct super_block *s
u64 bno;
err = apfs_dstream_get_new_bno(dstream, i, &bno);
if (err)
if (err) {
apfs_err(sb, "failed to get new block in dstream 0x%llx", dstream->ds_id);
goto fail;
}
bh = apfs_sb_bread(sb, bno);
if (!bh) {
apfs_err(sb, "failed to read new block");
err = -EIO;
goto fail;
}
@@ -556,8 +659,10 @@ static struct apfs_dstream_info *apfs_create_xattr_dstream(struct super_block *s
}
err = apfs_flush_extent_cache(dstream);
if (err)
if (err) {
apfs_err(sb, "extent cache flush failed for dstream 0x%llx", dstream->ds_id);
goto fail;
}
return dstream;
fail:
@@ -580,8 +685,10 @@ static int apfs_xattr_dstream_from_query(struct apfs_query *query, struct apfs_d
int err;
err = apfs_xattr_from_query(query, &xattr);
if (err)
if (err) {
apfs_err(sb, "bad xattr record");
return err;
}
if (xattr.has_dstream) {
dstream = kzalloc(sizeof(*dstream), GFP_KERNEL);
@@ -620,8 +727,10 @@ int apfs_xattr_set(struct inode *inode, const char *name, const void *value,
if (size > APFS_XATTR_MAX_EMBEDDED_SIZE) {
dstream = apfs_create_xattr_dstream(sb, value, size);
if (IS_ERR(dstream))
if (IS_ERR(dstream)) {
apfs_err(sb, "failed to set xattr dstream for ino 0x%llx", apfs_ino(inode));
return PTR_ERR(dstream);
}
}
apfs_init_xattr_key(cnid, name, &key);
@@ -636,21 +745,27 @@ int apfs_xattr_set(struct inode *inode, const char *name, const void *value,
ret = apfs_btree_query(sb, &query);
if (ret) {
if (ret != -ENODATA)
if (ret != -ENODATA) {
apfs_err(sb, "query failed for id 0x%llx (%s)", cnid, name);
goto done;
else if (flags & XATTR_REPLACE)
} else if (flags & XATTR_REPLACE) {
goto done;
}
} else if (flags & XATTR_CREATE) {
ret = -EEXIST;
goto done;
} else if (!value) {
ret = apfs_delete_xattr(query);
if (ret)
apfs_err(sb, "xattr deletion failed");
goto done;
} else {
/* Remember the old dstream to clean it up later */
ret = apfs_xattr_dstream_from_query(query, &old_dstream);
if (ret)
if (ret) {
apfs_err(sb, "failed to get the old dstream");
goto done;
}
}
key_len = apfs_build_xattr_key(name, cnid, &raw_key);
@@ -676,11 +791,16 @@ int apfs_xattr_set(struct inode *inode, const char *name, const void *value,
ret = apfs_btree_insert(query, raw_key, key_len, raw_val, val_len);
else
ret = apfs_btree_replace(query, raw_key, key_len, raw_val, val_len);
if (ret)
if (ret) {
apfs_err(sb, "insertion/update failed for id 0x%llx (%s)", cnid, name);
goto done;
}
if (old_dstream)
if (old_dstream) {
ret = apfs_truncate(old_dstream, 0);
if (ret)
apfs_err(sb, "truncation failed for dstream 0x%llx", old_dstream->ds_id);
}
done:
kfree(dstream);
@@ -699,11 +819,16 @@ int APFS_XATTR_SET_MAXOPS(void)
static int apfs_xattr_osx_set(const struct xattr_handler *handler,
struct dentry *unused, struct inode *inode, const char *name,
const void *value, size_t size, int flags)
#else
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0)
static int apfs_xattr_osx_set(const struct xattr_handler *handler,
struct user_namespace *mnt_userns, struct dentry *unused,
struct inode *inode, const char *name, const void *value,
size_t size, int flags)
#else
static int apfs_xattr_osx_set(const struct xattr_handler *handler,
struct mnt_idmap *idmap, struct dentry *unused,
struct inode *inode, const char *name, const void *value,
size_t size, int flags)
#endif
{
struct super_block *sb = inode->i_sb;
@@ -776,12 +901,14 @@ ssize_t apfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
ret = size - free;
break;
}
if (ret)
if (ret) {
apfs_err(sb, "query failed for id 0x%llx", cnid);
break;
}
ret = apfs_xattr_from_query(query, &xattr);
if (ret) {
apfs_alert(sb, "bad xattr key in inode %llx", cnid);
apfs_err(sb, "bad xattr key in inode %llx", cnid);
break;
}