131 Commits
Author SHA1 Message Date
Ernesto A. Fernández 8a37600293 0.3.21
This release adds support for the upcoming 7.2 kernel version.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-08-12 15:42:53 -03:00
Ernesto A. Fernández f0e88ca368 Switch to bh_submit() for kernel 7.2
The build is broken for the 7.2 release candidate. The reason is that
submit_bh() is gone now, replaced with a new bh_submit() that also takes
the I/O end handler as an argument. Adjust apfs_submit_bh() to these
changes.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-07-14 19:38:12 -03:00
Ernesto A. Fernández 96d5614d04 Put macos macro guards around uintmax_t typedef
I've been trying to build the driver for userland lately. Most of the
code can be left as it is as long as I spoof the kernel headers and
everything else that the kernel provides. The one exception is my
uintmax_t typedef, because macos is very determined to provide its own
definition for that type. I guess that makes sense, given that the lzfse
code came from Apple in the first place.

Anyway, none of this is a problem for the driver, but since I want to
keep both codebases identical, put the same macro guards in here.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-06-24 17:41:11 -03:00
Ernesto A. Fernández b2418aaaa7 Clarify the modprobe situation in the README
I notice a lot of traffic to the following issue:

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

Recent distro kernels seem to ship with a built-in libcrc32c, so the
modprobe step described in the README is not necessary and will fail. I
assumed it would be clear to the users that they can just keep going
when that happens, but some of them are being extra careful, which is
probably the right attitude to have when dealing with a filesystem.

Rephrase the README to make it clear that the modprobe step is not
usually needed anymore.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-05-27 16:58:08 -03:00
Ernesto A. Fernández 628b6810e4 0.3.20
This quick release adds support for the fsconfig() syscall, otherwise
newer versions of the mount tool won't accept any options.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-04-25 16:28:02 -03:00
Ernesto A. Fernández 7aca589f9e Use ->parse_param(), not ->parse_monolithic()
SergDL reports that mounts seem broken for the 7.0 kernel:

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

I never encountered this problem during testing, probably because my
version of the mount tool is older. It seems that mount options are now
set one by one using the fsconfig() syscall, and that's not possible if
we parse them all at once with ->parse_monolithic().

Implement ->parse_param() instead, at least for the latest kernel. I do
wonder if this issue won't pop up with older kernels though, since the
fsconfig() syscall has been around since 5.2. I guess I'll wait to see
if somebody complains about it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-04-24 17:10:07 -03:00
Ernesto A. Fernández 36df7e0b35 Set default mount options as soon as possible
Instead of setting the default mount options right before parsing the
actual options, set them on the superblock as soon as it gets allocated.
This will allow the parsing code to be called more than once by the new
mount api.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-04-24 14:56:03 -03:00
Ernesto A. Fernández ae381fbc3d Move default mount options into separate function
The new mount api forces me to make one function call for each option to
parse. This change means that the default options will need to get set
elsewhere, otherwise each new call would reset to default.

As a first step, move that code from parse_options() into a new
apfs_set_default_opts().

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-04-24 14:48:27 -03:00
Ernesto A. Fernández f95bcc94c9 0.3.19
This release adds support for the upcoming 7.0 kernel version, with a
patch by Alessio Faina. Aditya Garg also contributes a fix for a build
issue caused by the makefile.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-04-10 17:55:21 -03:00
Ernesto A. Fernández 911bbe1743 Simplify cleanup labels in mount/get_tree()
Reorganize the version check in the out_free_sbi label to better match
the simpler one at out_unmap_super.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-04-03 21:01:11 -03:00
Ernesto A. Fernández 6d54029be9 Unify parse_options() and preparse_options()
Following the previous patch, which made parse_options() always run
right after preparse_options(), merge both functions into one.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-04-03 20:58:00 -03:00
Ernesto A. Fernández 88b36a788e Run parse_options() earlier for kernels < 7.0
Since the mount api conversion, kernel version 7.0 and above parse all
the options at once before moving on to the rest of the mount process.
There is no reason that other kernel versions can't do the same; in fact
that was originally my intention, as explained in the commit message for
af2ee526c0 ("Avoid double parsing of mount options"). So always call
parse_options() right after preparse_options(). The plan is to unify
these functions next.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-04-03 20:26:01 -03:00
Ernesto A. Fernández ef79bb1bee Always call parse_options() with the sb info
Inside parse_options(), some kernel versions retrieve the superblock
info from the vfs superblock, while others retrieve it from the
filesystem context. Instead just pass the info as an argument and
simplify the version checks.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-04-03 20:06:10 -03:00
Ernesto A. Fernández 67eb48bb34 Fix whitespace issues from mount api conversion
I seem to have missed a few whitespace issues during my review of the
recent mount api conversion. Fix them now.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-04-03 19:47:39 -03:00
Ernesto A. Fernández 9aadac614c Get rid of nx_flags variable from parse_options()
At this point, the nx_flags variable only exists to get copied into
s_mount_opt of the superblock. Just work with s_mount_opt directly and
get rid of the goto label.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-04-03 19:44:47 -03:00
Ernesto A. Fernández 1158bd03a6 Use s_mount_opt for all kernel versions
I'm trying to unify mount option processing across kernel versions as
much as possible. Instead of calling parse_options_set_flags() directly
from parse_options() whenever possible, always set the new s_mount_opt
and use it to call parse_options_set_flags() later.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-04-03 19:26:43 -03:00
Ernesto A. Fernández 351c7cf826 Always init s_uid/s_gid inside parse_options()
Move s_uid/s_gid initialization inside parse_options() for all kernel
versions, getting rid of a version check for kernels 7.0 and above.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-04-03 19:09:40 -03:00
Alessio Faina 61672f2caf Add support to kernel 7.0 fs new mount APIs (LP: #2142837) (#114)
As all the in-tree modules has been ported to the new Kernel 7
fs mount API, the old API has been removed.

This patch add the support for the new set of APIs, while
keeping previous kernel support intact.

The code is in review on the Canonical Launchpad bug
https://launchpad.net/bugs/2142837 as well

Signed-off-by: Alessio Faina <alessio.faina@canonical.com>
2026-03-17 13:12:50 -03:00
Aditya Garg 0459ba0998 Rename $(CONFIG) to $(APFS_CONFIG) (#116) 2026-03-12 13:08:09 -03:00
Ernesto A. Fernández 1db8a6d92a 0.3.18
This release adds support for the upcoming 6.19 kernel version.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-01-30 18:44:25 -03:00
Ernesto A. Fernández a3bca211bd Don't access inode state directly in 6.19
The build is broken for the 6.19 release candidate:

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

The problem is that the inode i_state field can no longer be accessed
directly. Switch to the new helpers, using a wrapper to keep the code
consistent for all kernel versions.

Note that I always use the *_raw version of the helpers to avoid a lockdep
check that will not pass. I can't tell for sure if this is a bug.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-12-30 20:51:14 -03:00
Ernesto A. Fernández de721cd995 0.3.17
This release adds support for the upcoming 6.18 kernel version.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-11-26 18:28:54 -03:00
Ernesto A. Fernández 0daf28cef7 Always treat ->d_name as const
The build is broken for the 6.18 release candidate:

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

The problem is that ->d_name has become const, and that qualifier gets
dropped when we pass it around in the driver. Just treat is as const for
all kernel versions.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-10-27 22:30:27 -03:00
Ernesto A. Fernández c76aecff25 0.3.16
This release adds support for the upcoming 6.17 kernel version.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-09-27 13:17:31 -03:00
Ernesto A. Fernández 714202fefa Set dentry operations in the new way
Once more, the build is still broken for the 6.17 release candidate.
This time the problem is that we are no longer allowed to set s_d_op
directly. Use the new helper instead.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-09-16 14:02:10 -03:00
Ernesto A. Fernández 44db9dc02e Fix the apfs_write_begin/end() type for 6.17
The build is still broken for the 6.17 release candidate. The problem is
that ->write_begin() and ->write_end() now take a kiocb struct as the
first argument instead of a file. Add the proper version checks. The
driver does nothing with this argument but pass it along, so continue to
call it "file" to keep the patch simple.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-09-16 13:43:22 -03:00
Ernesto A. Fernández aefae5433f Rename "struct fileattr" to "struct file_kattr"
The build is broken for the 6.17 release candidate:

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

Upstream has renamed the fileattr struct to file_kattr, so that needs to
happen here as well. To avoid excessive nesting of version checks, don't
change the actual code and just rewrite it on build time with a macro. I
hope this doesn't cause any trouble in the future...

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-09-15 20:15:01 -03:00
Ernesto A. Fernández de4083a506 0.3.15
This release adds support for the upcoming 6.16 kernel version.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-07-25 19:27:24 -03:00
Ernesto A. Fernández a3c23dffc3 Don't reference page index in 6.16
The build is broken for the 6.16 release candidate:

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

The problem is that the page struct no longer has a member called
"index". Use folios for this instead.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-07-01 20:45:23 -03:00
Ernesto A. Fernández 7d8dc8821e 0.3.14
This release adds support for the upcoming 6.15 kernel version, and for
RHEL 9.6.

A longstanding objtool warning has also been fixed.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-05-23 21:32:10 -03:00
Ernesto A. Fernández 26bf3c5ba7 Fix build if kernel has no __annotate_jump_table
I recently started to use the __annotate_jump_table macro to silence an
objtool warning, but it seems that this macro was only added for the 5.3
kernel. Just define it to nothing for older releases, the objtool
warnings probably don't matter that much.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-05-16 17:40:41 -03:00
Ernesto A. Fernández c87d7aa77f Fix build for RHEL 9.6
The build has broken again for RHEL:

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

Fix it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-05-14 20:43:28 -03:00
Ernesto A. Fernández 87cc3f6940 Get rid of "pragma once" in lzfse code
Use regular macro guards instead.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-05-09 18:56:43 -03:00
Ernesto A. Fernández 51a5b742ec Fix the returned type of ->mkdir() for 6.15
The ->mkdir() method now needs to return a dentry struct instead of an
int, but this is meant for clustered filesystems or something so just
return NULL on success, like most filesystems do.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-21 19:24:48 -03:00
Ernesto A. Fernández e41b8f1f0e Handle page functions newly removed in 6.15
It seems that wait_for_stable_page(), page_has_buffers() and
grab_cache_page_write_begin() are no longer available, so put
implementations inside the driver.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-21 18:31:15 -03:00
Ernesto A. Fernández 68ad507875 Get rid of typedef for fse_value_decoder_entry
I don't intend to remove all typedefs from the lzfse code, but this one
is triggering a false positive for another issue in checkpatch. Get rid
of it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-16 17:32:24 -03:00
Ernesto A. Fernández 1039569db9 Correct brace placement in an lzfse for loop
Checkpatch complains about the brace placement in this for loop. Change
it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-16 17:20:17 -03:00
Ernesto A. Fernández 86cee6ecf8 Add SPDX identifiers to lzfse files
They are all under the 3-clause BSD license, so annotate them as such.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-15 20:58:12 -03:00
Ernesto A. Fernández 37e9e2a726 Unify lzfse style for sizeof
Always use sizeof with brackets, like checkpatch prefers.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-15 20:39:43 -03:00
Ernesto A. Fernández 991eebd92a Don't use __attribute__ in lzfse
The kernel has macros for these things, so use them instead and silence
the checkpatch warnings.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-15 20:30:08 -03:00
Ernesto A. Fernández f47daaf15c Don't use "unsigned" alone in lzfse
Checkpatch prefers "unsigned int" over "unsigned", so fix this where
needed.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-15 20:02:59 -03:00
Ernesto A. Fernández be38209c0b Don't use unnecessary braces in lzfse
Another checkpatch warning: remove all braces around blocks that have a
single statement, except for two cases where it makes more sense to
reorganize long comments so that the warnings stop.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-15 19:20:22 -03:00
Ernesto A. Fernández 84e57c48c4 Add blank lines after declarations in lzfse
Continue to silence checkpatch warnings with this change.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-15 19:08:09 -03:00
Ernesto A. Fernández 5aa52b928f Change the comment style of lzfse
Change the comment style in the lzfse code (by hand) to more or less
match the rest of the driver.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-11 20:00:54 -03:00
Ernesto A. Fernández c42445a577 Run clang-format on the remaining lzfse code
Continuing with the lzfse code cleanup: it seems that clang-format can
at least fix the whitespace reliably, so run it and get that out of the
way. But set the column limit to 100 like checkpatch uses, to avoid too
much awkward line wrapping on the parts of the code that go far to the
right.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-10 19:39:14 -03:00
Ernesto A. Fernández 6384c5dd50 Get rid of the lzfse inline macros
The kernel provides its own __always_inline macro, so get rid of those
that came with lzfse.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-09 18:12:44 -03:00
Ernesto A. Fernández acdad48cbc Get rid of LZFSE_API macro
The LZFSE_API macro doesn't seem to be used anywhere right now, so clean
it up.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-09 17:34:09 -03:00
Ernesto A. Fernández 92583d1c8f Get rid of C++ stuff in lzfse
None of this code will ever get called from C++, so clean that up.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-09 16:52:50 -03:00
Ernesto A. Fernández efc3c7b5ac Get rid of compiler checks in the lzfse code
I don't think any rare compilers are used to build the kernel, so just
assume gcc/clang everywhere. Also get rid of the dll checks.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-09 16:25:36 -03:00
Ernesto A. Fernández d3ea85f068 Allow jumptables without preprocessor checks
Jumptables are used elsewhere in the kernel, so it's safe to assume that
the compiler supports them. Clean up all the dead fallback code for
lzvn decompression.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-08 20:53:57 -03:00
Ernesto A. Fernández 21f190f58b Annotate jump table in lzvn decompression code
Since I first copied the lzfse code into the driver, the build has been
throwing the following error:

  lzfse/lzvn_decode_base.o: warning: objtool: lzvn_decode+0x10: sibling call from callable instruction with modified stack frame

I've been ignoring this because objtool warnings are probably not a big
deal for us; I think they are mostly for live patches. I'm also not
familiar with the lzfse code so I assumed this would be hard to fix.
It's not though: just annotate the jump table correctly.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-08 20:40:29 -03:00
Ernesto A. Fernández f0982e87d4 Clear unused lzfse/lzvn compression code
I have no real intention of ever supporting compressed writes. Last I
checked, the official implementation simply decompresses files when you
try to write to them, so maybe I will do that here as well at some
point. For now users that need this can work with a copy of the file
(and delete the original).

So, get rid of all the unused lzfse/lzvn compression code. This will
make it easier to fix the code style in the following patches.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2025-04-08 19:39:22 -03:00
Ernesto A. Fernández 61d8e5b7c4 0.3.13
This release adds support for the upcoming 6.14 upstream kernel, and for
a handful of RHEL 9 kernels that have been broken for a while.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-07-30 17:55:14 -03:00
Woody Suwalski 3e5c902a9a Kernel 6.11: use folio for page ops (#74) 2024-07-30 17:23:17 -03:00
Ernesto A. Fernández 695455bfdb 0.3.10
Transactions are now committed regularly, so it should no longer be
strictly necessary to unmount the filesystem cleanly for writes to take
effect. It's still recommended, of course.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-05-16 20:32:39 -03:00
Ernesto A. Fernández d533b6be3a 0.3.9
This release fixes the build for the soon to be released 6.9 kernel.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  The time that the program last modified this volume.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-03-18 18:45:37 -03:00
Ernesto A. Fernández 3f9d529bcb 0.3.8
This release reworks ephemeral objects to support larger containers of
up to ~7.8 TiB, and to allow the creation of checkpoint mapping blocks.

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

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

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

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

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

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

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

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

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

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

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2024-02-20 19:46:42 -03:00
37 changed files with 5326 additions and 6532 deletions

No files matched your search

+5
View File
@@ -13,6 +13,11 @@ apfs-y := btree.o compress.o dir.o extents.o file.o inode.o key.o libzbitmap.o \
lzfse/lzvn_decode_base.o message.o namei.o node.o object.o snapshot.o \
spaceman.o super.o symlink.o transaction.o unicode.o xattr.o xfield.o
# If you want mounts to be writable by default, run the build as:
# make APFS_CONFIG=-DCONFIG_APFS_RW_ALWAYS
# This is risky and not generally recommended.
ccflags-y += $(APFS_CONFIG)
default:
./genver.sh
make -C $(KERNEL_DIR) M=$(PWD)
+33 -13
View File
@@ -4,13 +4,8 @@ Apple File System for Linux
The Apple File System (APFS) is the copy-on-write filesystem currently used on
all Apple devices. This module provides a degree of experimental support on
Linux.
It's supposed to work with a range of kernel versions starting at 4.9 or before,
but only a few of those have actually been tested. Also, kernel versions below
4.12 will be slower for some operations. If you run into any problem, please
send a report to <linux-apfs@googlegroups.com> or file a github issue at
https://github.com/eafer/linux-apfs-rw/issues.
Linux. It's intended to work with all kernel versions since 4.12, but testing
is focused on 5.18 and above.
To help test write support, a set of userland tools is also under development.
The git tree can be retrieved from https://github.com/eafer/apfsprogs.git.
@@ -19,14 +14,32 @@ Known limitations
=================
This module is the result of reverse engineering and testing has been limited.
If you make use of the write support, expect data corruption. Please report any
issues that you find, but I can't promise a quick resolution at this stage.
If you make use of the write support, there is a real risk of data corruption,
so mounts are read-only by default. Please report any issues that you find.
Encryption is not yet implemented even in read-only mode.
Writes to fusion drives will likely never be supported, though I might
reconsider this if people actually need it. Encryption is not yet implemented
even in read-only mode.
Reporting bugs
==============
If you encounter any problem, the first thing you should do is run (as root)::
dmesg | grep -i apfs
to see all the error messages. If that doesn't help you, please file a github
issue at https://github.com/eafer/linux-apfs-rw/issues. Or send me an email if
you prefer.
Build
=====
Note that at this point the driver has been packaged for several distros, so
it may be more convenient to install it from there instead of building it
yourself. The distro packages are often outdated though, so be sure to check
the version.
In order to build a module out-of-tree, you will first need the Linux kernel
headers. Depending on your distro, you can get them by running (as root)::
@@ -44,10 +57,15 @@ Now you can just cd to the linux-apfs-rw directory and run::
make
The resulting module is the apfs.ko file. Before you can use it you must insert
it into the kernel, as well as its dependencies. Again as root::
it into the kernel. Again as root::
insmod apfs.ko
If you get an "unknown symbol in module" error, run::
modprobe libcrc32c
insmod apfs.ko
and then try the insmod command again.
Mount
=====
@@ -64,12 +82,14 @@ vol=n Volume number to mount. The default is volume 0.
snap=label Volume snapshot to mount (in read-only mode).
tier2=path Path to the tier 2 device. For fusion drives only.
uid=n, gid=n Override on-disk inode ownership data with given uid/gid.
cknodes Verify the checksum on all metadata nodes. Right now this has a
severe performance cost, so it's not recommended.
readwrite Enable the experimental write support. This **will** corrupt your
readwrite Enable the experimental write support. This may corrupt your
container.
============ =================================================================
+287 -74
View File
File diff suppressed because it is too large. Load diff
+56 -12
View File
@@ -268,6 +268,10 @@ struct apfs_phys_ext_val {
#define APFS_FILE_EXTENT_LEN_MASK 0x00ffffffffffffffULL
#define APFS_FILE_EXTENT_FLAG_MASK 0xff00000000000000ULL
#define APFS_FILE_EXTENT_FLAG_SHIFT 56
#define APFS_FILE_EXTENT_CRYPTO_FLAG 0x01 /* Made-up name */
#define APFS_FILE_EXTENT_PREALLOCATED 0x02 /* Made-up name */
#define APFS_VALID_FILE_EXTENT_FLAGS (APFS_FILE_EXTENT_CRYPTO_FLAG \
| APFS_FILE_EXTENT_PREALLOCATED)
/*
* Put a bound on maximum file size so that a growing truncation will always
@@ -1051,13 +1055,19 @@ struct apfs_checkpoint_map_phys {
#define APFS_FS_SPILLEDOVER 0x00000010LL
#define APFS_FS_RUN_SPILLOVER_CLEANER 0x00000020LL
#define APFS_FS_ALWAYS_CHECK_EXTENTREF 0x00000040LL
#define APFS_FS_PREVIOUSLY_SEALED 0x00000080LL /* Made-up name */
#define APFS_FS_PFK 0x00000100LL /* Made-up name */
#define APFS_FS_UNKNOWN_200 0x00000200LL
#define APFS_FS_FLAGS_VALID_MASK (APFS_FS_UNENCRYPTED \
| APFS_FS_EFFACEABLE \
| APFS_FS_RESERVED_4 \
| APFS_FS_ONEKEY \
| APFS_FS_SPILLEDOVER \
| APFS_FS_RUN_SPILLOVER_CLEANER \
| APFS_FS_ALWAYS_CHECK_EXTENTREF)
| APFS_FS_EFFACEABLE \
| APFS_FS_RESERVED_4 \
| APFS_FS_ONEKEY \
| APFS_FS_SPILLEDOVER \
| APFS_FS_RUN_SPILLOVER_CLEANER \
| APFS_FS_ALWAYS_CHECK_EXTENTREF \
| APFS_FS_PREVIOUSLY_SEALED \
| APFS_FS_PFK \
| APFS_FS_UNKNOWN_200)
#define APFS_FS_CRYPTOFLAGS (APFS_FS_UNENCRYPTED \
| APFS_FS_EFFACEABLE \
@@ -1124,7 +1134,9 @@ struct apfs_checkpoint_map_phys {
#define APFS_INCOMPAT_NORMALIZATION_INSENSITIVE 0x00000008LL
#define APFS_INCOMPAT_INCOMPLETE_RESTORE 0x00000010LL
#define APFS_INCOMPAT_SEALED_VOLUME 0x00000020LL
#define APFS_INCOMPAT_RESERVED_40 0x00000040LL
#define APFS_INCOMPAT_PFK 0x00000040LL /* Made-up name */
#define APFS_INCOMPAT_EXTENT_PREALLOC_FLAG 0x00000080LL /* Made-up name */
#define APFS_INCOMPAT_SECONDARY_FSROOT 0x00000100LL /* Made-up name */
#define APFS_SUPPORTED_INCOMPAT_MASK (APFS_INCOMPAT_CASE_INSENSITIVE \
| APFS_INCOMPAT_DATALESS_SNAPS \
@@ -1132,7 +1144,9 @@ struct apfs_checkpoint_map_phys {
| APFS_INCOMPAT_NORMALIZATION_INSENSITIVE \
| APFS_INCOMPAT_INCOMPLETE_RESTORE \
| APFS_INCOMPAT_SEALED_VOLUME \
| APFS_INCOMPAT_RESERVED_40)
| APFS_INCOMPAT_PFK \
| APFS_INCOMPAT_EXTENT_PREALLOC_FLAG \
| APFS_INCOMPAT_SECONDARY_FSROOT)
#define APFS_MODIFIED_NAMELEN 32
@@ -1162,6 +1176,18 @@ struct apfs_modified_by {
#define APFS_CRYPTO_RESERVED_5 5
#define APFS_UNASSIGNED_CRYPTO_ID (~0ULL)
/* Doc id index flags. I'm making up the names for now. */
#define APFS_DOC_ID_HAS_PREV_TREE 0x00000001
#define APFS_DOC_ID_UNKNOWN_02 0x00000002
#define APFS_DOC_ID_UNKNOWN_04 0x00000004
#define APFS_DOC_ID_UNKNOWN_08 0x00000008
#define APFS_DOC_ID_UNKNOWN_10 0x00000010
#define APFS_DOC_ID_VALID_FLAGS (APFS_DOC_ID_HAS_PREV_TREE \
| APFS_DOC_ID_UNKNOWN_02 \
| APFS_DOC_ID_UNKNOWN_04 \
| APFS_DOC_ID_UNKNOWN_08 \
| APFS_DOC_ID_UNKNOWN_10)
/*
* Structure used to store the encryption state
*/
@@ -1241,15 +1267,24 @@ struct apfs_superblock {
__le64 apfs_snap_meta_ext_oid;
char apfs_volume_group_id[UUID_SIZE];
/*3F0*/ char apfs_volume_group_id[UUID_SIZE];
__le64 apfs_integrity_meta_oid;
/*400*/ __le64 apfs_integrity_meta_oid;
__le64 apfs_fext_tree_oid;
__le32 apfs_fext_tree_type;
/*410*/ __le32 apfs_fext_tree_type;
__le32 reserved_type;
__le64 reserved_oid;
/*420*/ __le64 apfs_doc_id_index_xid;
__le32 apfs_doc_id_index_flags;
__le32 apfs_doc_id_tree_type;
/*430*/ __le64 apfs_doc_id_tree_oid; /* Made-up name */
__le64 apfs_prev_doc_id_tree_oid;
__le64 apfs_doc_id_fixup_cursor;
__le64 apfs_sec_root_tree_oid;
/*450*/ __le32 apfs_sec_root_tree_type;
} __packed;
/* Extended attributes constants */
@@ -1268,9 +1303,10 @@ enum {
APFS_XATTR_DATA_EMBEDDED = 0x00000002,
APFS_XATTR_FILE_SYSTEM_OWNED = 0x00000004,
APFS_XATTR_RESERVED_8 = 0x00000008,
APFS_XATTR_UNKNOWN_10 = 0x00000010,
};
#define APFS_XATTR_VALID_FLAGS 0x0000000f
#define APFS_XATTR_VALID_FLAGS 0x0000001f
/*
* Structure of the value of an extended attributes record
@@ -1438,6 +1474,8 @@ struct apfs_compress_rsrc_data {
} __packed block[];
} __packed;
#define APFS_FUSION_TIER2_DEVICE_BYTE_ADDR 0x4000000000000000ULL
/*
* TODO: this is a placeholder, create some snapshots with the official
* implementation to find the actual limit.
@@ -1459,6 +1497,12 @@ struct apfs_snap_metadata_val {
u8 name[];
} __packed;
/* Snapshot metadata record flags */
enum {
APFS_SNAP_META_PENDING_DATALESS = 0x00000001,
APFS_SNAP_MERGE_IN_PROGRESS = 0x00000002,
};
/*
* Structure of the value of a snapshot name record
*/
+65 -14
View File
@@ -50,7 +50,7 @@ static int apfs_child_from_query(struct apfs_query *query, u64 *child)
index_val = (struct apfs_btn_index_node_val *)(raw + query->off);
*child = le64_to_cpu(index_val->binv_child_oid) + apfs_catalog_base_oid(query);
} else {
if (query->len != 8) { /* The data on a nonleaf node is the child id */
if (query->len != 8) { /* The value on a nonleaf node is the child id */
apfs_err(sb, "bad index value length (%d)", query->len);
return -EFSCORRUPTED;
}
@@ -398,8 +398,6 @@ struct apfs_query *apfs_alloc_query(struct apfs_node *node,
/* To be released by free_query. */
query->node = node;
/* Start the search with the last record and go backwards */
query->index = node->records;
if (parent) {
query->key = parent->key;
@@ -408,6 +406,15 @@ struct apfs_query *apfs_alloc_query(struct apfs_node *node,
query->depth = parent->depth + 1;
}
/*
* We start the search with the last record and go backwards, but
* some queries later use the PREV flag later to list them in order.
*/
if (query->flags & APFS_QUERY_PREV)
query->index = -1;
else
query->index = node->records;
return query;
}
@@ -514,7 +521,8 @@ next_node:
* than enough to map every block.
*/
apfs_err(sb, "btree is too high");
return -EFSCORRUPTED;
err = -EFSCORRUPTED;
goto fail;
}
err = apfs_node_query(sb, *query);
@@ -526,12 +534,16 @@ next_node:
err = apfs_query_set_before_first(sb, query);
if (err) {
apfs_err(sb, "failed to set before the first record");
return err;
goto fail;
}
return -ENODATA;
err = -ENODATA;
goto fail;
} else if (err == -EAGAIN) {
if (!(*query)->parent) /* We are at the root of the tree */
return -ENODATA;
if (!(*query)->parent) {
/* We are at the root of the tree */
err = -ENODATA;
goto fail;
}
/* Move back up one level and continue the query */
parent = (*query)->parent;
@@ -540,7 +552,7 @@ next_node:
*query = parent;
goto next_node;
} else if (err) {
return err;
goto fail;
}
if (apfs_node_is_leaf((*query)->node)) /* All done */
return 0;
@@ -549,14 +561,15 @@ next_node:
if (err) {
apfs_alert(sb, "bad index block: 0x%llx",
(*query)->node->object.block_nr);
return err;
goto fail;
}
/* Now go a level deeper and search the child */
node = apfs_read_node(sb, child_id, storage, false /* write */);
if (IS_ERR(node)) {
apfs_err(sb, "failed to read node 0x%llx", child_id);
return PTR_ERR(node);
err = PTR_ERR(node);
goto fail;
}
if (node->object.oid != child_id)
@@ -571,10 +584,16 @@ next_node:
if (!*query) {
apfs_node_free(node);
*query = parent;
return -ENOMEM;
err = -ENOMEM;
goto fail;
}
node = NULL;
goto next_node;
fail:
/* Don't leave stale record info here or some callers will use it */
(*query)->key_len = (*query)->len = 0;
return err;
}
static int __apfs_btree_replace(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
@@ -589,11 +608,22 @@ int apfs_query_join_transaction(struct apfs_query *query)
struct super_block *sb = node->object.sb;
u64 oid = node->object.oid;
u32 storage = apfs_query_storage(query);
struct apfs_obj_phys *raw = NULL;
/*
* Ephemeral objects are checkpoint data, and all of their xids get
* updated on commit. There is no real need to do it here as well, but
* it's better for consistency with the other object types.
*/
if (storage == APFS_OBJ_EPHEMERAL) {
ASSERT(node->object.ephemeral);
raw = (void *)node->object.data;
raw->o_xid = cpu_to_le64(APFS_NXI(sb)->nx_xid);
return 0;
}
if (buffer_trans(node->object.o_bh)) /* Already in the transaction */
return 0;
/* Ephemeral objects are always checkpoint data */
ASSERT(storage != APFS_OBJ_EPHEMERAL);
/* Root nodes should join the transaction before the query is created */
ASSERT(!apfs_node_is_root(node));
@@ -1121,3 +1151,24 @@ int apfs_btree_replace(struct apfs_query *query, void *key, int key_len, void *v
apfs_btree_change_rec_count(query, 0 /* change */, key_len, val_len);
return 0;
}
/**
* apfs_query_direct_forward - Set a query to start listing records forwards
* @query: a successfully executed query
*
* Multiple queries list records backwards, but queries marked with this
* function after execution will go in the opposite direction.
*/
void apfs_query_direct_forward(struct apfs_query *query)
{
if (query->flags & APFS_QUERY_PREV)
return;
apfs_assert_query_is_valid(query);
ASSERT(apfs_node_is_leaf(query->node));
while (query) {
query->flags |= APFS_QUERY_PREV;
query = query->parent;
}
}
+9 -5
View File
@@ -409,7 +409,7 @@ static ssize_t apfs_compress_file_read_page(struct file *filp, char *buf, loff_t
return step;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
static int apfs_compress_read_folio(struct file *filp, struct folio *folio)
{
struct page *page = &folio->page;
@@ -417,16 +417,20 @@ static int apfs_compress_read_folio(struct file *filp, struct folio *folio)
static int apfs_compress_readpage(struct file *filp, struct page *page)
{
#endif
void *addr = NULL;
char *addr = NULL;
ssize_t ret;
loff_t off;
/* Mostly copied from ext4_read_inline_page() */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 16, 0)
off = page_folio(page)->index << PAGE_SHIFT;
#else
off = page->index << PAGE_SHIFT;
addr = kmap_atomic(page);
#endif
addr = kmap(page);
ret = apfs_compress_file_read_page(filp, addr, off);
flush_dcache_page(page);
kunmap_atomic(addr);
kunmap(page);
if (ret >= 0) {
zero_user_segment(page, ret, PAGE_SIZE);
SetPageUptodate(page);
@@ -438,7 +442,7 @@ static int apfs_compress_readpage(struct file *filp, struct page *page)
}
const struct address_space_operations apfs_compress_aops = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
.read_folio = apfs_compress_read_folio,
#else
.readpage = apfs_compress_readpage,
+90 -68
View File
@@ -261,7 +261,7 @@ const struct file_operations apfs_dir_operations = {
*
* Returns the length of the key, or a negative error code in case of failure.
*/
static int apfs_build_dentry_unhashed_key(struct qstr *qname, u64 parent_id,
static int apfs_build_dentry_unhashed_key(const struct qstr *qname, u64 parent_id,
struct apfs_drec_key **key_p)
{
struct apfs_drec_key *key;
@@ -290,7 +290,7 @@ static int apfs_build_dentry_unhashed_key(struct qstr *qname, u64 parent_id,
*
* Returns the length of the key, or a negative error code in case of failure.
*/
static int apfs_build_dentry_hashed_key(struct qstr *qname, u64 hash, u64 parent_id,
static int apfs_build_dentry_hashed_key(const struct qstr *qname, u64 hash, u64 parent_id,
struct apfs_drec_hashed_key **key_p)
{
struct apfs_drec_hashed_key *key;
@@ -363,7 +363,7 @@ static int apfs_build_dentry_val(struct inode *inode, u64 sibling_id,
*
* Returns 0 on success or a negative error code in case of failure.
*/
static int apfs_create_dentry_rec(struct inode *inode, struct qstr *qname,
static int apfs_create_dentry_rec(struct inode *inode, const struct qstr *qname,
u64 parent_id, u64 sibling_id)
{
struct super_block *sb = inode->i_sb;
@@ -414,7 +414,6 @@ fail:
apfs_free_query(query);
return ret;
}
#define APFS_CREATE_DENTRY_REC_MAXOPS 1
/**
* apfs_build_sibling_val - Allocate and initialize a sibling link's value
@@ -427,7 +426,7 @@ static int apfs_build_sibling_val(struct dentry *dentry,
struct apfs_sibling_val **val_p)
{
struct apfs_sibling_val *val;
struct qstr *qname = &dentry->d_name;
const struct qstr *qname = &dentry->d_name;
u16 namelen = qname->len + 1; /* We count the null-termination */
struct inode *parent = d_inode(dentry->d_parent);
int val_len;
@@ -491,7 +490,6 @@ fail:
apfs_free_query(query);
return ret;
}
#define APFS_CREATE_SIBLING_LINK_REC_MAXOPS 1
/**
* apfs_create_sibling_map_rec - Create a sibling map record for a dentry
@@ -534,7 +532,6 @@ fail:
apfs_free_query(query);
return ret;
}
#define APFS_CREATE_SIBLING_MAP_REC_MAXOPS 1
/**
* apfs_create_sibling_recs - Create sibling link and map records for a dentry
@@ -567,8 +564,6 @@ static int apfs_create_sibling_recs(struct dentry *dentry,
*sibling_id = cnid;
return 0;
}
#define APFS_CREATE_SIBLING_RECS_MAXOPS (APFS_CREATE_SIBLING_LINK_REC_MAXOPS + \
APFS_CREATE_SIBLING_MAP_REC_MAXOPS)
/**
* apfs_create_dentry - Create all records for a new dentry
@@ -613,9 +608,6 @@ static int apfs_create_dentry(struct dentry *dentry, struct inode *inode)
apfs_inode_join_transaction(parent->i_sb, parent);
return 0;
}
#define APFS_CREATE_DENTRY_MAXOPS (APFS_CREATE_SIBLING_RECS_MAXOPS + \
APFS_CREATE_DENTRY_REC_MAXOPS + \
APFS_UPDATE_INODE_MAXOPS())
/**
* apfs_undo_create_dentry - Clean up apfs_create_dentry()
@@ -643,15 +635,9 @@ int apfs_mkany(struct inode *dir, struct dentry *dentry, umode_t mode,
{
struct super_block *sb = dir->i_sb;
struct inode *inode;
struct apfs_max_ops maxops;
int err;
maxops.cat = APFS_CREATE_INODE_REC_MAXOPS() + APFS_CREATE_DENTRY_MAXOPS;
if (symname)
maxops.cat += APFS_XATTR_SET_MAXOPS();
maxops.blks = 0;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
return err;
@@ -712,7 +698,7 @@ out_abort:
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0)
int apfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode,
dev_t rdev)
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0)
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0) && !RHEL_VERSION_GE(9, 6)
int apfs_mknod(struct user_namespace *mnt_userns, struct inode *dir,
struct dentry *dentry, umode_t mode, dev_t rdev)
#else
@@ -730,7 +716,7 @@ int apfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
return apfs_mknod(dir, dentry, mode | S_IFDIR, 0 /* rdev */);
}
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0)
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0) && !RHEL_VERSION_GE(9, 6)
int apfs_mkdir(struct user_namespace *mnt_userns, struct inode *dir,
struct dentry *dentry, umode_t mode)
@@ -738,7 +724,7 @@ int apfs_mkdir(struct user_namespace *mnt_userns, struct inode *dir,
return apfs_mknod(mnt_userns, dir, dentry, mode | S_IFDIR, 0 /* rdev */);
}
#else
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 15, 0)
int apfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode)
@@ -746,6 +732,13 @@ int apfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
return apfs_mknod(idmap, dir, dentry, mode | S_IFDIR, 0 /* rdev */);
}
#else
struct dentry *apfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode)
{
return ERR_PTR(apfs_mknod(idmap, dir, dentry, mode | S_IFDIR, 0 /* rdev */));
}
#endif
@@ -757,7 +750,7 @@ int apfs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
return apfs_mknod(dir, dentry, mode, 0 /* rdev */);
}
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0)
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0) && !RHEL_VERSION_GE(9, 6)
int apfs_create(struct user_namespace *mnt_userns, struct inode *dir,
struct dentry *dentry, umode_t mode, bool excl)
@@ -818,8 +811,6 @@ static int apfs_prepare_dentry_for_link(struct dentry *dentry)
return apfs_create_dentry_rec(d_inode(dentry), &dentry->d_name,
apfs_ino(parent), sibling_id);
}
#define APFS_PREPARE_DENTRY_FOR_LINK_MAXOPS (1 + APFS_CREATE_SIBLING_RECS_MAXOPS + \
APFS_CREATE_DENTRY_REC_MAXOPS)
/**
* __apfs_undo_link - Clean up __apfs_link()
@@ -875,22 +866,15 @@ fail:
drop_nlink(inode);
return err;
}
#define __APFS_LINK_MAXOPS (APFS_UPDATE_INODE_MAXOPS() + \
APFS_PREPARE_DENTRY_FOR_LINK_MAXOPS + \
APFS_CREATE_DENTRY_MAXOPS)
int apfs_link(struct dentry *old_dentry, struct inode *dir,
struct dentry *dentry)
{
struct super_block *sb = dir->i_sb;
struct inode *inode = d_inode(old_dentry);
struct apfs_max_ops maxops;
int err;
maxops.cat = __APFS_LINK_MAXOPS;
maxops.blks = 0;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
return err;
@@ -954,7 +938,6 @@ fail:
apfs_free_query(query);
return ret;
}
#define APFS_DELETE_SIBLING_LINK_REC_MAXOPS 1
/**
* apfs_delete_sibling_map_rec - Delete the sibling map record for a dentry
@@ -995,7 +978,6 @@ fail:
apfs_free_query(query);
return ret;
}
#define APFS_DELETE_SIBLING_MAP_REC_MAXOPS 1
/**
* apfs_delete_dentry - Delete all records for a dentry
@@ -1048,9 +1030,6 @@ static int apfs_delete_dentry(struct dentry *dentry)
apfs_inode_join_transaction(sb, parent);
return err;
}
#define APFS_DELETE_DENTRY_MAXOPS (1 + APFS_DELETE_SIBLING_LINK_REC_MAXOPS + \
APFS_DELETE_SIBLING_MAP_REC_MAXOPS + \
APFS_UPDATE_INODE_MAXOPS())
/**
* apfs_undo_delete_dentry - Clean up apfs_delete_dentry()
@@ -1158,13 +1137,13 @@ fail:
/**
* apfs_orphan_name - Get the name for an orphan inode's invisible link
* @inode: the vfs inode
* @qname: on return, the name assigned to the link
* @ino: the inode number
* @qname: on return, the name assigned to the link
*
* Returns 0 on success; the caller must remember to free @qname->name after
* use. Returns a negative error code in case of failure.
*/
static int apfs_orphan_name(struct inode *inode, struct qstr *qname)
static int apfs_orphan_name(u64 ino, struct qstr *qname)
{
int max_len;
char *name;
@@ -1174,7 +1153,7 @@ static int apfs_orphan_name(struct inode *inode, struct qstr *qname)
name = kmalloc(max_len, GFP_KERNEL);
if (!name)
return -ENOMEM;
qname->len = snprintf(name, max_len, "0x%llx-dead", apfs_ino(inode));
qname->len = snprintf(name, max_len, "0x%llx-dead", ino);
qname->name = name;
return 0;
}
@@ -1193,7 +1172,7 @@ static int apfs_create_orphan_link(struct inode *inode)
struct qstr qname;
int err = 0;
err = apfs_orphan_name(inode, &qname);
err = apfs_orphan_name(apfs_ino(inode), &qname);
if (err)
return err;
err = apfs_create_dentry_rec(inode, &qname, apfs_ino(priv_dir), 0 /* sibling_id */);
@@ -1217,8 +1196,6 @@ fail:
kfree(qname.name);
return err;
}
#define APFS_CREATE_ORPHAN_LINK_MAXOPS (APFS_CREATE_DENTRY_REC_MAXOPS + \
APFS_UPDATE_INODE_MAXOPS())
/**
* apfs_delete_orphan_link - Delete the link for an orphan inode
@@ -1236,7 +1213,7 @@ int apfs_delete_orphan_link(struct inode *inode)
struct apfs_drec drec;
int err;
err = apfs_orphan_name(inode, &qname);
err = apfs_orphan_name(apfs_ino(inode), &qname);
if (err)
return err;
@@ -1269,10 +1246,6 @@ fail:
kfree(qname.name);
return err;
}
int APFS_DELETE_ORPHAN_LINK_MAXOPS(void)
{
return 1 + APFS_UPDATE_INODE_MAXOPS();
}
/**
* __apfs_undo_unlink - Clean up __apfs_unlink()
@@ -1282,9 +1255,10 @@ static void __apfs_undo_unlink(struct dentry *dentry)
{
struct inode *inode = d_inode(dentry);
inode->i_state |= I_LINKABLE; /* Silence warning about nlink 0->1 */
/* Silence warning about nlink 0->1 */
apfs_inode_state_set_raw(inode, I_LINKABLE);
inc_nlink(inode);
inode->i_state &= ~I_LINKABLE;
apfs_inode_state_clear_raw(inode, I_LINKABLE);
apfs_undo_delete_dentry(dentry);
}
@@ -1368,20 +1342,13 @@ fail:
__apfs_undo_unlink(dentry);
return err;
}
#define __APFS_UNLINK_MAXOPS (APFS_DELETE_DENTRY_MAXOPS + \
APFS_CREATE_ORPHAN_LINK_MAXOPS + \
APFS_UPDATE_INODE_MAXOPS())
int apfs_unlink(struct inode *dir, struct dentry *dentry)
{
struct super_block *sb = dir->i_sb;
struct apfs_max_ops maxops;
int err;
maxops.cat = __APFS_UNLINK_MAXOPS;
maxops.blks = 0;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_DEL);
if (err)
return err;
@@ -1414,7 +1381,7 @@ int apfs_rmdir(struct inode *dir, struct dentry *dentry)
int apfs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry,
unsigned int flags)
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0)
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0) && !RHEL_VERSION_GE(9, 6)
int apfs_rename(struct user_namespace *mnt_userns, struct inode *old_dir,
struct dentry *old_dentry, struct inode *new_dir,
struct dentry *new_dentry, unsigned int flags)
@@ -1427,7 +1394,6 @@ int apfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
struct super_block *sb = old_dir->i_sb;
struct inode *old_inode = d_inode(old_dentry);
struct inode *new_inode = d_inode(new_dentry);
struct apfs_max_ops maxops;
int err;
if (new_inode && APFS_I(new_inode)->i_nchildren)
@@ -1436,12 +1402,7 @@ int apfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
if (flags & ~RENAME_NOREPLACE) /* TODO: support RENAME_EXCHANGE */
return -EINVAL;
maxops.cat = __APFS_UNLINK_MAXOPS + __APFS_LINK_MAXOPS;
if (new_inode)
maxops.cat += __APFS_UNLINK_MAXOPS;
maxops.blks = 0;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
return err;
@@ -1481,3 +1442,64 @@ out_abort:
apfs_transaction_abort(sb);
return err;
}
/**
* apfs_any_orphan_ino - Find the inode number for any orphaned regular file
* @sb: filesytem superblock
* @ino_p: on return, the inode number found
*
* Returns 0 on success, or a negative error code in case of failure, which may
* be -ENODATA if there are no orphan files.
*/
u64 apfs_any_orphan_ino(struct super_block *sb, u64 *ino_p)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_query *query = NULL;
struct apfs_drec drec = {0};
struct qstr qname = {0};
bool hashed = apfs_is_normalization_insensitive(sb);
bool found = false;
int err;
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
apfs_init_drec_key(sb, APFS_PRIV_DIR_INO_NUM, NULL /* name */, 0 /* name_len */, &query->key);
query->flags = APFS_QUERY_CAT | APFS_QUERY_MULTIPLE | APFS_QUERY_EXACT;
while (!found) {
err = apfs_btree_query(sb, &query);
if (err) {
if (err == -ENODATA)
goto out;
apfs_err(sb, "drec query failed for private dir");
goto out;
}
err = apfs_drec_from_query(query, &drec, hashed);
if (err) {
apfs_alert(sb, "bad dentry record in private dir");
goto out;
}
/* These files are deleted immediately by ->evict_inode() */
if (drec.type != DT_REG)
continue;
/*
* Confirm that this is an orphan file, because the official
* reference allows other uses for the private directory.
*/
err = apfs_orphan_name(drec.ino, &qname);
if (err)
goto out;
found = strcmp(drec.name, qname.name) == 0;
kfree(qname.name);
qname.name = NULL;
}
*ino_p = drec.ino;
out:
apfs_free_query(query);
query = NULL;
return err;
}
+1 -1
View File
@@ -1,5 +1,5 @@
PACKAGE_NAME="linux-apfs-rw"
PACKAGE_VERSION="0.3.7"
PACKAGE_VERSION="0.3.21"
BUILT_MODULE_NAME[0]="apfs"
DEST_MODULE_LOCATION[0]="/extra"
+133 -29
View File
@@ -8,8 +8,6 @@
#include <linux/blk_types.h>
#include "apfs.h"
#define MAX(X, Y) ((X) <= (Y) ? (Y) : (X))
/**
* apfs_ext_is_hole - Does this extent represent a hole in a sparse file?
* @extent: the extent to check
@@ -387,7 +385,7 @@ static int apfs_shrink_extent_tail(struct apfs_query *query, struct apfs_dstream
}
/**
* apfs_query_found_extent - Is this query pointing to an extent record?
* apfs_query_found_extent - Did this query find an extent with the right id?
* @query: the (successful) query that found the record
*/
static inline bool apfs_query_found_extent(struct apfs_query *query)
@@ -398,7 +396,12 @@ static inline bool apfs_query_found_extent(struct apfs_query *query)
if (query->key_len < sizeof(*hdr))
return false;
hdr = raw + query->key_off;
return apfs_cat_type(hdr) == APFS_TYPE_FILE_EXTENT;
if (apfs_cat_type(hdr) != APFS_TYPE_FILE_EXTENT)
return false;
if (apfs_cat_cnid(hdr) != query->key.id)
return false;
return true;
}
/**
@@ -755,7 +758,6 @@ static int apfs_update_extent(struct apfs_dstream_info *dstream, const struct ap
}
return apfs_update_mid_extent(dstream, extent);
}
#define APFS_UPDATE_EXTENTS_MAXOPS (1 + 2 * APFS_CRYPTO_ADJ_REFCNT_MAXOPS())
static int apfs_extend_phys_extent(struct apfs_query *query, u64 bno, u64 blkcnt, u64 dstream_id)
{
@@ -1133,7 +1135,6 @@ int apfs_flush_extent_cache(struct apfs_dstream_info *dstream)
dstream->ds_ext_dirty = false;
return 0;
}
#define APFS_FLUSH_EXTENT_CACHE APFS_UPDATE_EXTENTS_MAXOPS
/**
* apfs_create_hole - Create and insert a hole extent for the dstream
@@ -1448,10 +1449,6 @@ static int apfs_dstream_get_new_block(struct apfs_dstream_info *dstream, u64 dsb
dstream->ds_ext_dirty = true;
return 0;
}
int APFS_GET_NEW_BLOCK_MAXOPS(void)
{
return APFS_FLUSH_EXTENT_CACHE;
}
/**
* apfs_dstream_get_new_bno - Allocate a new block inside a dstream
@@ -1625,6 +1622,125 @@ int apfs_truncate(struct apfs_dstream_info *dstream, loff_t new_size)
return apfs_create_hole(dstream, old_blks, new_blks);
}
/**
* apfs_dstream_delete_front - Deletes as many leading extents as possible
* @sb: filesystem superblock
* @ds_id: id for the dstream to delete
*
* Returns 0 on success, or a negative error code in case of failure, which may
* be -ENODATA if there are no more extents, or -EAGAIN if the free queue is
* getting too full.
*/
static int apfs_dstream_delete_front(struct super_block *sb, u64 ds_id)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_spaceman *sm = APFS_SM(sb);
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
struct apfs_spaceman_free_queue *fq = NULL;
struct apfs_query *query = NULL;
struct apfs_file_extent head;
bool first_match = true;
int ret;
fq = &sm_raw->sm_fq[APFS_SFQ_MAIN];
if (le64_to_cpu(fq->sfq_count) > APFS_TRANS_MAIN_QUEUE_MAX)
return -EAGAIN;
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
apfs_init_file_extent_key(ds_id, 0, &query->key);
query->flags = APFS_QUERY_CAT;
next_extent:
ret = apfs_btree_query(sb, &query);
if (ret && ret != -ENODATA) {
apfs_err(sb, "query failed for first extent of id 0x%llx", ds_id);
goto out;
}
apfs_query_direct_forward(query);
if (!apfs_query_found_extent(query)) {
/*
* After the original lookup, the query may not be set to the
* first extent, but instead to the record that comes right
* before.
*/
if (first_match) {
first_match = false;
goto next_extent;
}
ret = -ENODATA;
goto out;
}
first_match = false;
ret = apfs_extent_from_query(query, &head);
if (ret) {
apfs_err(sb, "bad head extent record on dstream 0x%llx", ds_id);
goto out;
}
ret = apfs_btree_remove(query);
if (ret) {
apfs_err(sb, "removal failed for id 0x%llx, addr 0x%llx", ds_id, head.logical_addr);
goto out;
}
/*
* The official fsck doesn't complain about wrong sparse byte counts
* for orphans, so I guess we don't need to update them here
*/
if (!apfs_ext_is_hole(&head)) {
ret = apfs_range_put_reference(sb, head.phys_block_num, head.len);
if (ret) {
apfs_err(sb, "failed to put range 0x%llx-0x%llx", head.phys_block_num, head.len);
goto out;
}
ret = apfs_crypto_adj_refcnt(sb, head.crypto_id, -1);
if (ret) {
apfs_err(sb, "failed to take crypto id 0x%llx", head.crypto_id);
goto out;
}
}
if (le64_to_cpu(fq->sfq_count) <= APFS_TRANS_MAIN_QUEUE_MAX)
goto next_extent;
ret = -EAGAIN;
out:
apfs_free_query(query);
return ret;
}
/**
* apfs_inode_delete_front - Deletes as many leading extents as possible
* @inode: inode to delete
*
* Tries to delete all extents for @inode, in which case it returns 0. If the
* free queue is getting too full, deletes as much as is reasonable and returns
* -EAGAIN. May return other negative error codes as well.
*/
int apfs_inode_delete_front(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct apfs_dstream_info *dstream = NULL;
struct apfs_inode_info *ai = APFS_I(inode);
int ret;
if (!ai->i_has_dstream)
return 0;
dstream = &ai->i_dstream;
ret = apfs_flush_extent_cache(dstream);
if (ret) {
apfs_err(sb, "extent cache flush failed for dstream 0x%llx", dstream->ds_id);
return ret;
}
ret = apfs_dstream_delete_front(sb, dstream->ds_id);
if (ret == -ENODATA)
return 0;
return ret;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
loff_t apfs_remap_file_range(struct file *src_file, loff_t off, struct file *dst_file, loff_t destoff, loff_t len, unsigned int remap_flags)
#else
@@ -1639,8 +1755,6 @@ int apfs_clone_file_range(struct file *src_file, loff_t off, struct file *dst_fi
struct apfs_dstream_info *dst_ds = &dst_ai->i_dstream;
struct super_block *sb = src_inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
/* TODO: remember to update the maxops in the future */
struct apfs_max_ops maxops = {0};
const u64 xfield_flags = APFS_INODE_MAINTAIN_DIR_STATS | APFS_INODE_IS_SPARSE | APFS_INODE_HAS_PURGEABLE_FLAGS;
int err;
@@ -1674,7 +1788,7 @@ int apfs_clone_file_range(struct file *src_file, loff_t off, struct file *dst_fi
return -EOPNOTSUPP;
}
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
return err;
apfs_inode_join_transaction(sb, src_inode);
@@ -1787,7 +1901,7 @@ out:
* @paddr_end: first block after the extent to put
* @paddr_min: don't put references before this block
*
* Puts a reference to the physical extent range that ends in paddr. Sets
* Puts a reference to the physical extent range that ends in @paddr_end. Sets
* @paddr_end to the beginning of the extent, so that the caller can continue
* with the previous one. Returns 0 on success, or a negative error code in
* case of failure.
@@ -1849,7 +1963,7 @@ restart:
prev_end = prev_ext.bno + prev_ext.blkcount;
if (prev_end < *paddr_end) {
/* The extent to put is part of a snapshot */
tmp.phys_block_num = MAX(prev_end, paddr_min);
tmp.phys_block_num = max(prev_end, paddr_min);
tmp.len = (*paddr_end - tmp.phys_block_num) << sb->s_blocksize_bits;
ret = apfs_create_update_pext(query, &tmp, -1);
*paddr_end = tmp.phys_block_num;
@@ -1929,7 +2043,7 @@ static int apfs_range_put_reference(struct super_block *sb, u64 paddr, u64 lengt
* @paddr_end: first block after the extent to take
* @paddr_min: don't take references before this block
*
* Takes a reference to the physical extent range that ends in paddr. Sets
* Takes a reference to the physical extent range that ends in @paddr_end. Sets
* @paddr_end to the beginning of the extent, so that the caller can continue
* with the previous one. Returns 0 on success, or a negative error code in
* case of failure.
@@ -1989,7 +2103,7 @@ restart:
prev_end = prev_ext.bno + prev_ext.blkcount;
if (prev_end < *paddr_end) {
/* The extent to take is part of a snapshot */
tmp.phys_block_num = MAX(prev_end, paddr_min);
tmp.phys_block_num = max(prev_end, paddr_min);
tmp.len = (*paddr_end - tmp.phys_block_num) << sb->s_blocksize_bits;
ret = apfs_create_update_pext(query, &tmp, +1);
*paddr_end = tmp.phys_block_num;
@@ -2173,11 +2287,7 @@ int apfs_nonsparse_dstream_read(struct apfs_dstream_info *dstream, void *buf, si
goto out;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
bhs[idx] = __getblk_gfp(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
#else
bhs[idx] = bdev_getblk(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
#endif
bhs[idx] = __apfs_getblk(sb, bno);
if (!bhs[idx]) {
apfs_err(sb, "failed to map block 0x%llx", bno);
ret = -EIO;
@@ -2188,7 +2298,6 @@ int apfs_nonsparse_dstream_read(struct apfs_dstream_info *dstream, void *buf, si
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);
}
}
@@ -2249,17 +2358,12 @@ void apfs_nonsparse_dstream_preread(struct apfs_dstream_info *dstream)
if (ret || bno == 0)
return;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
bh = __getblk_gfp(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
#else
bh = bdev_getblk(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
#endif
bh = __apfs_getblk(sb, bno);
if (!bh)
return;
if (!buffer_uptodate(bh)) {
get_bh(bh);
lock_buffer(bh);
bh->b_end_io = end_buffer_read_sync;
apfs_submit_bh(REQ_OP_READ, 0, bh);
}
brelse(bh);
+30 -11
View File
@@ -5,10 +5,20 @@
#include "apfs.h"
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
#include <linux/splice.h>
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 17, 0)
typedef int vm_fault_t;
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 15, 0)
static void wait_for_stable_page(struct page *page)
{
return folio_wait_stable(page_folio(page));
}
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0)
static vm_fault_t apfs_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf)
{
@@ -25,8 +35,6 @@ static vm_fault_t apfs_page_mkwrite(struct vm_fault *vmf)
struct super_block *sb = inode->i_sb;
struct buffer_head *bh, *head;
vm_fault_t ret = VM_FAULT_LOCKED;
struct apfs_max_ops maxops;
int blkcount = PAGE_SIZE >> inode->i_blkbits;
unsigned int blocksize, block_start, len;
u64 size;
int err = 0;
@@ -34,12 +42,7 @@ static vm_fault_t apfs_page_mkwrite(struct vm_fault *vmf)
sb_start_pagefault(inode->i_sb);
file_update_time(vma->vm_file);
/* Placeholder values, I need to get back to this in the future */
maxops.cat = APFS_UPDATE_INODE_MAXOPS() +
blkcount * APFS_GET_NEW_BLOCK_MAXOPS();
maxops.blks = blkcount;
err = apfs_transaction_start(sb, maxops);
err = apfs_transaction_start(sb, APFS_TRANS_REG);
if (err)
goto out;
apfs_inode_join_transaction(sb, inode);
@@ -69,7 +72,11 @@ static vm_fault_t apfs_page_mkwrite(struct vm_fault *vmf)
}
size = i_size_read(inode);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 16, 0)
if (page_folio(page)->index == size >> PAGE_SHIFT)
#else
if (page->index == size >> PAGE_SHIFT)
#endif
len = size & ~PAGE_MASK;
else
len = PAGE_SIZE;
@@ -110,7 +117,7 @@ out_abort:
apfs_transaction_abort(sb);
out:
if (err)
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) && !RHEL_VERSION_GE(9, 5)
ret = block_page_mkwrite_return(err);
#else
ret = vmf_fs_error(err);
@@ -129,7 +136,7 @@ int apfs_file_mmap(struct file *file, struct vm_area_struct *vma)
{
struct address_space *mapping = file->f_mapping;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
if (!mapping->a_ops->read_folio)
#else
if (!mapping->a_ops->readpage)
@@ -152,6 +159,16 @@ int apfs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
return apfs_sync_fs(sb, true /* wait */);
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
static ssize_t apfs_copy_file_range(struct file *src_file, loff_t src_off,
struct file *dst_file, loff_t dst_off,
size_t len, unsigned int flags)
{
return (splice_copy_file_range(src_file, src_off,
dst_file, dst_off, len));
}
#endif
const struct file_operations apfs_file_operations = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
@@ -161,7 +178,9 @@ const struct file_operations apfs_file_operations = {
.fsync = apfs_fsync,
.unlocked_ioctl = apfs_file_ioctl,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
.copy_file_range = apfs_copy_file_range,
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
.copy_file_range = generic_copy_file_range,
#endif
+320 -116
View File
File diff suppressed because it is too large. Load diff
+7 -4
View File
@@ -180,7 +180,7 @@ int apfs_read_free_queue_key(void *raw, int size, struct apfs_key *key)
{
struct apfs_spaceman_free_queue_key *raw_key;
if (size < sizeof(struct apfs_spaceman_free_queue_key)) {
if (size != sizeof(*raw_key)) {
apfs_err(NULL, "bad key length (%d)", size);
return -EFSCORRUPTED;
}
@@ -204,14 +204,17 @@ int apfs_read_free_queue_key(void *raw, int size, struct apfs_key *key)
*/
int apfs_read_omap_key(void *raw, int size, struct apfs_key *key)
{
if (size < sizeof(struct apfs_omap_key)) {
struct apfs_omap_key *raw_key;
if (size != sizeof(*raw_key)) {
apfs_err(NULL, "bad key length (%d)", size);
return -EFSCORRUPTED;
}
raw_key = raw;
key->id = le64_to_cpu(((struct apfs_omap_key *)raw)->ok_oid);
key->id = le64_to_cpu(raw_key->ok_oid);
key->type = 0;
key->number = le64_to_cpu(((struct apfs_omap_key *)raw)->ok_xid);
key->number = le64_to_cpu(raw_key->ok_xid);
key->name = NULL;
return 0;
-2
View File
@@ -12,8 +12,6 @@
#include <linux/string.h>
#include "libzbitmap.h"
#define MIN(x, y) ((x) > (y) ? (y) : (x))
#define ZBM_MAGIC "ZBM\x09"
#define ZBM_MAGIC_SZ 4
+25 -94
View File
@@ -1,23 +1,24 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/*
Copyright (c) 2015-2016, Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
* Copyright (c) 2015-2016, Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef LZFSE_H
#define LZFSE_H
@@ -25,71 +26,6 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSI
#include <linux/stddef.h>
#include <linux/types.h>
#ifdef __cplusplus
extern "C" {
#endif
#if defined(_MSC_VER) && !defined(__clang__)
# define __attribute__(X)
# pragma warning(disable : 4068)
#endif
#if defined(LZFSE_DLL)
# if defined(_WIN32) || defined(__CYGWIN__)
# if defined(LZFSE_DLL_EXPORTS)
# define LZFSE_API __declspec(dllexport)
# else
# define LZFSE_API __declspec(dllimport)
# endif
# endif
#endif
#if !defined(LZFSE_API)
# if __GNUC__ >= 4
# define LZFSE_API __attribute__((visibility("default")))
# else
# define LZFSE_API
# endif
#endif
/*! @abstract Get the required scratch buffer size to compress using LZFSE. */
size_t lzfse_encode_scratch_size(void);
/*! @abstract Compress a buffer using LZFSE.
*
* @param dst_buffer
* Pointer to the first byte of the destination buffer.
*
* @param dst_size
* Size of the destination buffer in bytes.
*
* @param src_buffer
* Pointer to the first byte of the source buffer.
*
* @param src_size
* Size of the source buffer in bytes.
*
* @param scratch_buffer
* If non-NULL, a pointer to scratch space for the routine to use as workspace;
* the routine may use up to lzfse_encode_scratch_size( ) bytes of workspace
* during its operation, and will not perform any internal allocations. If
* NULL, the routine may allocate its own memory to use during operation via
* a single call to malloc( ), and will release it by calling free( ) prior
* to returning. For most use, passing NULL is perfectly satisfactory, but if
* you require strict control over allocation, you will want to pass an
* explicit scratch buffer.
*
* @return
* The number of bytes written to the destination buffer if the input is
* successfully compressed. If the input cannot be compressed to fit into
* the provided buffer, or an error occurs, zero is returned, and the
* contents of dst_buffer are unspecified. */
size_t lzfse_encode_buffer(uint8_t *__restrict dst_buffer,
size_t dst_size,
const uint8_t *__restrict src_buffer,
size_t src_size,
void *__restrict scratch_buffer);
/*! @abstract Get the required scratch buffer size to decompress using LZFSE. */
size_t lzfse_decode_scratch_size(void);
@@ -122,15 +58,10 @@ size_t lzfse_decode_scratch_size(void);
* successfully decompressed. If there is not enough space in the destination
* buffer to hold the entire expanded output, only the first dst_size bytes
* will be written to the buffer and dst_size is returned. Note that this
* behavior differs from that of lzfse_encode_buffer. */
size_t lzfse_decode_buffer(uint8_t *__restrict dst_buffer,
size_t dst_size,
const uint8_t *__restrict src_buffer,
size_t src_size,
void *__restrict scratch_buffer);
#ifdef __cplusplus
} /* extern "C" */
#endif
* behavior differs from that of lzfse_encode_buffer.
*/
size_t lzfse_decode_buffer(uint8_t *__restrict dst_buffer, size_t dst_size,
const uint8_t *__restrict src_buffer, size_t src_size,
void *__restrict scratch_buffer);
#endif /* LZFSE_H */
+65 -59
View File
@@ -1,74 +1,80 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
Copyright (c) 2015-2016, Apple Inc. All rights reserved.
* Copyright (c) 2015-2016, Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// LZFSE decode API
/* LZFSE decode API */
#include <linux/slab.h>
#include "lzfse.h"
#include "lzfse_internal.h"
size_t lzfse_decode_scratch_size(void) { return sizeof(lzfse_decoder_state); }
size_t lzfse_decode_scratch_size(void)
{
return sizeof(lzfse_decoder_state);
}
static size_t lzfse_decode_buffer_with_scratch(uint8_t *__restrict dst_buffer,
size_t dst_size, const uint8_t *__restrict src_buffer,
size_t src_size, void *__restrict scratch_buffer) {
int status;
lzfse_decoder_state *s = (lzfse_decoder_state *)scratch_buffer;
memset(s, 0x00, sizeof(*s));
static size_t lzfse_decode_buffer_with_scratch(uint8_t *__restrict dst_buffer, size_t dst_size,
const uint8_t *__restrict src_buffer,
size_t src_size, void *__restrict scratch_buffer)
{
int status;
lzfse_decoder_state *s = (lzfse_decoder_state *)scratch_buffer;
// Initialize state
s->src = src_buffer;
s->src_begin = src_buffer;
s->src_end = s->src + src_size;
s->dst = dst_buffer;
s->dst_begin = dst_buffer;
s->dst_end = dst_buffer + dst_size;
memset(s, 0x00, sizeof(*s));
// Decode
status = lzfse_decode(s);
if (status == LZFSE_STATUS_DST_FULL)
return dst_size;
if (status != LZFSE_STATUS_OK)
return 0; // failed
return (size_t)(s->dst - dst_buffer); // bytes written
/* Initialize state */
s->src = src_buffer;
s->src_begin = src_buffer;
s->src_end = s->src + src_size;
s->dst = dst_buffer;
s->dst_begin = dst_buffer;
s->dst_end = dst_buffer + dst_size;
/* Decode */
status = lzfse_decode(s);
if (status == LZFSE_STATUS_DST_FULL)
return dst_size;
if (status != LZFSE_STATUS_OK)
return 0; /* failed */
return (size_t)(s->dst - dst_buffer); /* bytes written */
}
size_t lzfse_decode_buffer(uint8_t *__restrict dst_buffer, size_t dst_size,
const uint8_t *__restrict src_buffer,
size_t src_size, void *__restrict scratch_buffer) {
int has_malloc = 0;
size_t ret = 0;
const uint8_t *__restrict src_buffer, size_t src_size,
void *__restrict scratch_buffer)
{
int has_malloc = 0;
size_t ret = 0;
// Deal with the possible NULL pointer
if (scratch_buffer == NULL) {
// +1 in case scratch size could be zero
scratch_buffer = kmalloc(lzfse_decode_scratch_size() + 1, GFP_KERNEL);
has_malloc = 1;
}
if (scratch_buffer == NULL)
return 0;
ret = lzfse_decode_buffer_with_scratch(dst_buffer,
dst_size, src_buffer,
src_size, scratch_buffer);
if (has_malloc)
kfree(scratch_buffer);
return ret;
/* Deal with the possible NULL pointer */
if (scratch_buffer == NULL) {
/* +1 in case scratch size could be zero */
scratch_buffer = kmalloc(lzfse_decode_scratch_size() + 1, GFP_KERNEL);
has_malloc = 1;
}
if (scratch_buffer == NULL)
return 0;
ret = lzfse_decode_buffer_with_scratch(dst_buffer, dst_size, src_buffer, src_size,
scratch_buffer);
if (has_malloc)
kfree(scratch_buffer);
return ret;
}
+647 -556
View File
File diff suppressed because it is too large. Load diff
-163
View File
@@ -1,163 +0,0 @@
/*
Copyright (c) 2015-2016, Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// LZFSE encode API
#include "lzfse.h"
#include "lzfse_internal.h"
size_t lzfse_encode_scratch_size() {
size_t s1 = sizeof(lzfse_encoder_state);
size_t s2 = lzvn_encode_scratch_size();
return (s1 > s2) ? s1 : s2; // max(lzfse,lzvn)
}
size_t lzfse_encode_buffer_with_scratch(uint8_t *__restrict dst_buffer,
size_t dst_size, const uint8_t *__restrict src_buffer,
size_t src_size, void *__restrict scratch_buffer) {
const size_t original_size = src_size;
// If input is really really small, go directly to uncompressed buffer
// (because LZVN will refuse to encode it, and we will report a failure)
if (src_size < LZVN_ENCODE_MIN_SRC_SIZE)
goto try_uncompressed;
// If input is too small, try encoding with LZVN
if (src_size < LZFSE_ENCODE_LZVN_THRESHOLD) {
// need header + end-of-stream marker
size_t extra_size = 4 + sizeof(lzvn_compressed_block_header);
if (dst_size <= extra_size)
goto try_uncompressed; // DST is really too small, give up
size_t sz = lzvn_encode_buffer(
dst_buffer + sizeof(lzvn_compressed_block_header),
dst_size - extra_size, src_buffer, src_size, scratch_buffer);
if (sz == 0 || sz >= src_size)
goto try_uncompressed; // failed, or no compression, fall back to
// uncompressed block
// If we could encode, setup header and end-of-stream marker (we left room
// for them, no need to test)
lzvn_compressed_block_header header;
header.magic = LZFSE_COMPRESSEDLZVN_BLOCK_MAGIC;
header.n_raw_bytes = (uint32_t)src_size;
header.n_payload_bytes = (uint32_t)sz;
memcpy(dst_buffer, &header, sizeof(header));
store4(dst_buffer + sizeof(lzvn_compressed_block_header) + sz,
LZFSE_ENDOFSTREAM_BLOCK_MAGIC);
return sz + extra_size;
}
// Try encoding with LZFSE
{
lzfse_encoder_state *state = scratch_buffer;
memset(state, 0x00, sizeof *state);
if (lzfse_encode_init(state) != LZFSE_STATUS_OK)
goto try_uncompressed;
state->dst = dst_buffer;
state->dst_begin = dst_buffer;
state->dst_end = &dst_buffer[dst_size];
state->src = src_buffer;
state->src_encode_i = 0;
if (src_size >= 0xffffffffU) {
// lzfse only uses 32 bits for offsets internally, so if the input
// buffer is really huge, we need to process it in smaller chunks.
// Note that we switch over to this path for sizes much smaller
// 2GB because it's actually faster to change algorithms well before
// it's necessary for correctness.
// The first chunk, we just process normally.
const lzfse_offset encoder_block_size = 262144;
state->src_end = encoder_block_size;
if (lzfse_encode_base(state) != LZFSE_STATUS_OK)
goto try_uncompressed;
src_size -= encoder_block_size;
while (src_size >= encoder_block_size) {
// All subsequent chunks require a translation to keep the offsets
// from getting too big. Note that we are always going from
// encoder_block_size up to 2*encoder_block_size so that the
// offsets remain positive (as opposed to resetting to zero and
// having negative offsets).
state->src_end = 2 * encoder_block_size;
if (lzfse_encode_base(state) != LZFSE_STATUS_OK)
goto try_uncompressed;
lzfse_encode_translate(state, encoder_block_size);
src_size -= encoder_block_size;
}
// Set the end for the final chunk.
state->src_end = encoder_block_size + (lzfse_offset)src_size;
}
// If the source buffer is small enough to use 32-bit offsets, we simply
// encode the whole thing in a single chunk.
else
state->src_end = (lzfse_offset)src_size;
// This is either the trailing chunk (if the source file is huge), or
// the whole source file.
if (lzfse_encode_base(state) != LZFSE_STATUS_OK)
goto try_uncompressed;
if (lzfse_encode_finish(state) != LZFSE_STATUS_OK)
goto try_uncompressed;
// No error occured, return compressed size.
return state->dst - dst_buffer;
}
try_uncompressed:
// Compression failed for some reason. If we can fit the data into the
// output buffer uncompressed, go ahead and do that instead.
if (original_size + 12 <= dst_size && original_size < INT32_MAX) {
uncompressed_block_header header = {.magic = LZFSE_UNCOMPRESSED_BLOCK_MAGIC,
.n_raw_bytes = (uint32_t)src_size};
uint8_t *dst_end = dst_buffer;
memcpy(dst_end, &header, sizeof header);
dst_end += sizeof header;
memcpy(dst_end, src_buffer, original_size);
dst_end += original_size;
store4(dst_end, LZFSE_ENDOFSTREAM_BLOCK_MAGIC);
dst_end += 4;
return dst_end - dst_buffer;
}
// Otherwise, there's nothing we can do, so return zero.
return 0;
}
size_t lzfse_encode_buffer(uint8_t *__restrict dst_buffer, size_t dst_size,
const uint8_t *__restrict src_buffer,
size_t src_size, void *__restrict scratch_buffer) {
int has_malloc = 0;
size_t ret = 0;
// Deal with the possible NULL pointer
if (scratch_buffer == NULL) {
// +1 in case scratch size could be zero
scratch_buffer = malloc(lzfse_encode_scratch_size() + 1);
has_malloc = 1;
}
if (scratch_buffer == NULL)
return 0;
ret = lzfse_encode_buffer_with_scratch(dst_buffer,
dst_size, src_buffer,
src_size, scratch_buffer);
if (has_malloc)
free(scratch_buffer);
return ret;
}
File diff suppressed because it is too large. Load diff
-218
View File
@@ -1,218 +0,0 @@
/*
Copyright (c) 2015-2016, Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef LZFSE_ENCODE_TABLES_H
#define LZFSE_ENCODE_TABLES_H
#if defined(_MSC_VER) && !defined(__clang__)
# define inline __inline
#endif
static inline uint8_t l_base_from_value(int32_t value) {
static const uint8_t sym[LZFSE_ENCODE_MAX_L_VALUE + 1] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16,
16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19};
return sym[value];
}
static inline uint8_t m_base_from_value(int32_t value) {
static const uint8_t sym[LZFSE_ENCODE_MAX_M_VALUE + 1] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16,
16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19};
return sym[value];
}
static inline uint8_t d_base_from_value(int32_t value) {
static const uint8_t sym[64 * 4] = {
0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9,
9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12,
13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15,
15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 17, 18, 19, 20, 20, 21, 21,
22, 22, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27,
27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29,
30, 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 32, 32,
32, 32, 32, 33, 34, 35, 36, 36, 37, 37, 38, 38, 39, 39, 40, 40, 40, 40,
41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43, 44, 44, 44, 44, 44, 44,
44, 44, 45, 45, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 46, 46, 46, 46,
47, 47, 47, 47, 47, 47, 47, 47, 48, 48, 48, 48, 48, 49, 50, 51, 52, 52,
53, 53, 54, 54, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58,
59, 59, 59, 59, 60, 60, 60, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61, 61,
61, 61, 62, 62, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 63,
0, 0, 0, 0};
int index = 0;
int in_range_k;
in_range_k = (value >= 0 && value < 60);
index |= (((value - 0) >> 0) + 0) & -in_range_k;
in_range_k = (value >= 60 && value < 1020);
index |= (((value - 60) >> 4) + 64) & -in_range_k;
in_range_k = (value >= 1020 && value < 16380);
index |= (((value - 1020) >> 8) + 128) & -in_range_k;
in_range_k = (value >= 16380 && value < 262140);
index |= (((value - 16380) >> 12) + 192) & -in_range_k;
return sym[index & 255];
}
#endif // LZFSE_ENCODE_TABLES_H
+112 -203
View File
@@ -1,217 +1,126 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
Copyright (c) 2015-2016, Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
* Copyright (c) 2015-2016, Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "lzfse_internal.h"
// Initialize encoder table T[NSYMBOLS].
// NSTATES = sum FREQ[i] is the number of states (a power of 2)
// NSYMBOLS is the number of symbols.
// FREQ[NSYMBOLS] is a normalized histogram of symbol frequencies, with FREQ[i]
// >= 0.
// Some symbols may have a 0 frequency. In that case, they should not be
// present in the data.
void fse_init_encoder_table(int nstates, int nsymbols,
const uint16_t *__restrict freq,
fse_encoder_entry *__restrict t) {
int offset = 0; // current offset
int n_clz = __builtin_clz(nstates);
int i;
for (i = 0; i < nsymbols; i++) {
int f = (int)freq[i];
int k;
if (f == 0)
continue; // skip this symbol, no occurrences
k = __builtin_clz(f) - n_clz; // shift needed to ensure N <= (F<<K) < 2*N
t[i].s0 = (int16_t)((f << k) - nstates);
t[i].k = (int16_t)k;
t[i].delta0 = (int16_t)(offset - f + (nstates >> k));
t[i].delta1 = (int16_t)(offset - f + (nstates >> (k - 1)));
offset += f;
}
/*
* Initialize decoder table T[NSTATES].
* NSTATES = sum FREQ[i] is the number of states (a power of 2)
* NSYMBOLS is the number of symbols.
* FREQ[NSYMBOLS] is a normalized histogram of symbol frequencies, with FREQ[i]
* >= 0.
* Some symbols may have a 0 frequency. In that case, they should not be
* present in the data.
*/
int fse_init_decoder_table(int nstates, int nsymbols, const uint16_t *__restrict freq,
int32_t *__restrict t)
{
int n_clz = __builtin_clz(nstates);
int sum_of_freq = 0;
int i, j0, j;
for (i = 0; i < nsymbols; i++) {
int f = (int)freq[i];
int k;
if (f == 0)
continue; /* skip this symbol, no occurrences */
sum_of_freq += f;
if (sum_of_freq > nstates)
return -1;
k = __builtin_clz(f) - n_clz; /* shift needed to ensure N <= (F<<K) < 2*N */
j0 = ((2 * nstates) >> k) - f;
/* Initialize all states S reached by this symbol: OFFSET <= S < OFFSET + F */
for (j = 0; j < f; j++) {
fse_decoder_entry e;
e.symbol = (uint8_t)i;
if (j < j0) {
e.k = (int8_t)k;
e.delta = (int16_t)(((f + j) << k) - nstates);
} else {
e.k = (int8_t)(k - 1);
e.delta = (int16_t)((j - j0) << (k - 1));
}
memcpy(t, &e, sizeof(e));
t++;
}
}
return 0; /* OK */
}
// Initialize decoder table T[NSTATES].
// NSTATES = sum FREQ[i] is the number of states (a power of 2)
// NSYMBOLS is the number of symbols.
// FREQ[NSYMBOLS] is a normalized histogram of symbol frequencies, with FREQ[i]
// >= 0.
// Some symbols may have a 0 frequency. In that case, they should not be
// present in the data.
int fse_init_decoder_table(int nstates, int nsymbols,
const uint16_t *__restrict freq,
int32_t *__restrict t) {
int n_clz = __builtin_clz(nstates);
int sum_of_freq = 0;
int i, j0, j;
for (i = 0; i < nsymbols; i++) {
int f = (int)freq[i];
int k;
if (f == 0)
continue; // skip this symbol, no occurrences
/*
* Initialize value decoder table T[NSTATES].
* NSTATES = sum FREQ[i] is the number of states (a power of 2)
* NSYMBOLS is the number of symbols.
* FREQ[NSYMBOLS] is a normalized histogram of symbol frequencies, with FREQ[i]
* >= 0.
* SYMBOL_VBITS[NSYMBOLS] and SYMBOLS_VBASE[NSYMBOLS] are the number of value
* bits to read and the base value for each symbol.
* Some symbols may have a 0 frequency. In that case, they should not be
* present in the data.
*/
void fse_init_value_decoder_table(int nstates, int nsymbols, const uint16_t *__restrict freq,
const uint8_t *__restrict symbol_vbits,
const int32_t *__restrict symbol_vbase,
struct fse_value_decoder_entry *__restrict t)
{
int n_clz = __builtin_clz(nstates);
int i;
sum_of_freq += f;
for (i = 0; i < nsymbols; i++) {
struct fse_value_decoder_entry ei = { 0 };
int f = (int)freq[i];
int k, j0, j;
if (sum_of_freq > nstates) {
return -1;
}
if (f == 0)
continue; /* skip this symbol, no occurrences */
k = __builtin_clz(f) - n_clz; // shift needed to ensure N <= (F<<K) < 2*N
j0 = ((2 * nstates) >> k) - f;
k = __builtin_clz(f) - n_clz; /* shift needed to ensure N <= (F<<K) < 2*N */
j0 = ((2 * nstates) >> k) - f;
// Initialize all states S reached by this symbol: OFFSET <= S < OFFSET + F
for (j = 0; j < f; j++) {
fse_decoder_entry e;
ei.value_bits = symbol_vbits[i];
ei.vbase = symbol_vbase[i];
e.symbol = (uint8_t)i;
if (j < j0) {
e.k = (int8_t)k;
e.delta = (int16_t)(((f + j) << k) - nstates);
} else {
e.k = (int8_t)(k - 1);
e.delta = (int16_t)((j - j0) << (k - 1));
}
/* Initialize all states S reached by this symbol: OFFSET <= S < OFFSET + F */
for (j = 0; j < f; j++) {
struct fse_value_decoder_entry e = ei;
memcpy(t, &e, sizeof(e));
t++;
}
}
if (j < j0) {
e.total_bits = (uint8_t)k + e.value_bits;
e.delta = (int16_t)(((f + j) << k) - nstates);
} else {
e.total_bits = (uint8_t)(k - 1) + e.value_bits;
e.delta = (int16_t)((j - j0) << (k - 1));
}
return 0; // OK
}
// Initialize value decoder table T[NSTATES].
// NSTATES = sum FREQ[i] is the number of states (a power of 2)
// NSYMBOLS is the number of symbols.
// FREQ[NSYMBOLS] is a normalized histogram of symbol frequencies, with FREQ[i]
// >= 0.
// SYMBOL_VBITS[NSYMBOLS] and SYMBOLS_VBASE[NSYMBOLS] are the number of value
// bits to read and the base value for each symbol.
// Some symbols may have a 0 frequency. In that case, they should not be
// present in the data.
void fse_init_value_decoder_table(int nstates, int nsymbols,
const uint16_t *__restrict freq,
const uint8_t *__restrict symbol_vbits,
const int32_t *__restrict symbol_vbase,
fse_value_decoder_entry *__restrict t) {
int n_clz = __builtin_clz(nstates);
int i;
for (i = 0; i < nsymbols; i++) {
fse_value_decoder_entry ei = {0};
int f = (int)freq[i];
int k, j0, j;
if (f == 0)
continue; // skip this symbol, no occurrences
k = __builtin_clz(f) - n_clz; // shift needed to ensure N <= (F<<K) < 2*N
j0 = ((2 * nstates) >> k) - f;
ei.value_bits = symbol_vbits[i];
ei.vbase = symbol_vbase[i];
// Initialize all states S reached by this symbol: OFFSET <= S < OFFSET + F
for (j = 0; j < f; j++) {
fse_value_decoder_entry e = ei;
if (j < j0) {
e.total_bits = (uint8_t)k + e.value_bits;
e.delta = (int16_t)(((f + j) << k) - nstates);
} else {
e.total_bits = (uint8_t)(k - 1) + e.value_bits;
e.delta = (int16_t)((j - j0) << (k - 1));
}
memcpy(t, &e, 8);
t++;
}
}
}
// Remove states from symbols until the correct number of states is used.
static void fse_adjust_freqs(uint16_t *freq, int overrun, int nsymbols) {
int shift;
for (shift = 3; overrun != 0; shift--) {
int sym;
for (sym = 0; sym < nsymbols; sym++) {
if (freq[sym] > 1) {
int n = (freq[sym] - 1) >> shift;
if (n > overrun)
n = overrun;
freq[sym] -= n;
overrun -= n;
if (overrun == 0)
break;
}
}
}
}
// Normalize a table T[NSYMBOLS] of occurrences to FREQ[NSYMBOLS].
void fse_normalize_freq(int nstates, int nsymbols, const uint32_t *__restrict t,
uint16_t *__restrict freq) {
uint32_t s_count = 0;
int remaining = nstates; // must be signed; this may become < 0
int max_freq = 0;
int max_freq_sym = 0;
int shift = __builtin_clz(nstates) - 1;
uint32_t highprec_step;
int i;
// Compute the total number of symbol occurrences
for (i = 0; i < nsymbols; i++)
s_count += t[i];
if (s_count == 0)
highprec_step = 0; // no symbols used
else
highprec_step = ((uint32_t)1 << 31) / s_count;
for (i = 0; i < nsymbols; i++) {
// Rescale the occurrence count to get the normalized frequency.
// Round up if the fractional part is >= 0.5; otherwise round down.
// For efficiency, we do this calculation using integer arithmetic.
int f = (((t[i] * highprec_step) >> shift) + 1) >> 1;
// If a symbol was used, it must be given a nonzero normalized frequency.
if (f == 0 && t[i] != 0)
f = 1;
freq[i] = f;
remaining -= f;
// Remember the maximum frequency and which symbol had it.
if (f > max_freq) {
max_freq = f;
max_freq_sym = i;
}
}
// If there remain states to be assigned, then just assign them to the most
// frequent symbol. Alternatively, if we assigned more states than were
// actually available, then either remove states from the most frequent symbol
// (for minor overruns) or use the slower adjustment algorithm (for major
// overruns).
if (-remaining < (max_freq >> 2)) {
freq[max_freq_sym] += remaining;
} else {
fse_adjust_freqs(freq, -remaining, nsymbols);
}
memcpy(t, &e, 8);
t++;
}
}
}
Loaded 20 of 37 files, more files were not shown because too many files have changed in this diff. Show more