11 Commits
Author SHA1 Message Date
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
5 changed files with 191 additions and 121 deletions

No files matched your search

+2 -2
View File
@@ -14,9 +14,9 @@ apfs-y := btree.o compress.o dir.o extents.o file.o inode.o key.o libzbitmap.o \
spaceman.o super.o symlink.o transaction.o unicode.o xattr.o xfield.o
# If you want mounts to be writable by default, run the build as:
# make CONFIG=-DCONFIG_APFS_RW_ALWAYS
# make APFS_CONFIG=-DCONFIG_APFS_RW_ALWAYS
# This is risky and not generally recommended.
ccflags-y += $(CONFIG)
ccflags-y += $(APFS_CONFIG)
default:
./genver.sh
+4 -1
View File
@@ -380,6 +380,7 @@ struct apfs_sb_info {
unsigned int s_vol_nr; /* Index of the volume in the sb list */
kuid_t s_uid; /* uid to override on-disk uid */
kgid_t s_gid; /* gid to override on-disk gid */
unsigned int s_mount_opt;
struct apfs_crypto_state_val *s_dflt_pfk; /* default per-file key */
@@ -1040,7 +1041,9 @@ extern int apfs_setattr(struct mnt_idmap *idmap,
struct dentry *dentry, struct iattr *iattr);
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
extern int apfs_update_time(struct inode *inode, enum fs_update_time time, unsigned int flags);
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
extern int apfs_update_time(struct inode *inode, struct timespec64 *time, int flags);
#else
extern int apfs_update_time(struct inode *inode, int flags);
+1 -1
View File
@@ -1,5 +1,5 @@
PACKAGE_NAME="linux-apfs-rw"
PACKAGE_VERSION="0.3.18"
PACKAGE_VERSION="0.3.19"
BUILT_MODULE_NAME[0]="apfs"
DEST_MODULE_LOCATION[0]="/extra"
+6 -2
View File
@@ -2109,7 +2109,9 @@ fail:
}
/* TODO: this only seems to be necessary because ->write_inode() isn't firing */
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
int apfs_update_time(struct inode *inode, enum fs_update_time time, unsigned int flags)
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
int apfs_update_time(struct inode *inode, struct timespec64 *time, int flags)
#else
int apfs_update_time(struct inode *inode, int flags)
@@ -2123,7 +2125,9 @@ int apfs_update_time(struct inode *inode, int flags)
return err;
apfs_inode_join_transaction(sb, inode);
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) && !RHEL_VERSION_GE(9, 6)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
generic_update_time(inode, time, flags);
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) && !RHEL_VERSION_GE(9, 6)
generic_update_time(inode, time, flags);
#else
generic_update_time(inode, flags);
+178 -115
View File
@@ -15,6 +15,9 @@
#include <linux/seq_file.h>
#include "apfs.h"
#include "version.h"
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
#include <linux/fs_context.h>
#endif
#define APFS_MODULE_ID_STRING "linux-apfs by eafer (" GIT_COMMIT ")"
@@ -465,6 +468,9 @@ static inline void apfs_free_main_super(struct apfs_sb_info *sbi)
u32 bmap_idx;
int i;
if (!nxi)
return;
mutex_lock(&nxs_mutex);
list_del(&sbi->list);
@@ -1187,7 +1193,9 @@ static const struct super_operations apfs_sops = {
.put_super = apfs_put_super,
.sync_fs = apfs_sync_fs,
.statfs = apfs_statfs,
#if LINUX_VERSION_CODE < KERNEL_VERSION(7, 0, 0)
.remount_fs = apfs_remount,
#endif
.show_options = apfs_show_options,
};
@@ -1227,30 +1235,53 @@ static void apfs_set_nx_flags(struct super_block *sb, unsigned int flags)
mutex_unlock(&nxs_mutex);
}
static void parse_options_set_flags(struct super_block *sb, struct apfs_sb_info *sbi,
unsigned int nx_flags)
{
struct apfs_nxsb_info *nxi = sbi->s_nxi;
apfs_set_nx_flags(sb, nx_flags);
if (!(sb->s_flags & SB_RDONLY)) {
if (nxi->nx_flags & APFS_READWRITE) {
apfs_notice(sb, "experimental write support is enabled");
} else {
apfs_warn(sb, "experimental writes disabled to avoid data loss");
apfs_warn(sb, "if you really want them, check the README");
sb->s_flags |= SB_RDONLY;
}
}
}
/*
* Many of the parse_options() functions in other file systems return 0
* on error. This one returns an error code, and 0 on success.
*
* Note that even on failure, the caller is responsible for freeing all
* superblock fields.
*/
static int parse_options(struct super_block *sb, char *options)
static int parse_options(struct apfs_sb_info *sbi, char *options)
{
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_nxsb_info *nxi = sbi->s_nxi;
char *p;
substring_t args[MAX_OPT_ARGS];
int option;
int err = 0;
unsigned int nx_flags;
/* Set default values before parsing */
nx_flags = 0;
sbi->s_vol_nr = 0;
sbi->s_snap_name = NULL;
sbi->s_tier2_path = NULL;
sbi->s_mount_opt = 0;
#ifdef CONFIG_APFS_RW_ALWAYS
/* Still risky, but some packagers want writable mounts by default */
nx_flags |= APFS_READWRITE;
sbi->s_mount_opt |= APFS_READWRITE;
#endif
sbi->s_uid = INVALID_UID;
sbi->s_gid = INVALID_GID;
if (!options)
goto out;
return 0;
while ((p = strsep(&options, ",")) != NULL) {
int token;
@@ -1264,14 +1295,14 @@ static int parse_options(struct super_block *sb, char *options)
* Write support is not safe yet, so keep it disabled
* unless the user requests it explicitly.
*/
nx_flags |= APFS_READWRITE;
sbi->s_mount_opt |= APFS_READWRITE;
break;
case Opt_cknodes:
/*
* Right now, node checksums are too costly to enable
* by default. TODO: try to improve this.
*/
nx_flags |= APFS_CHECK_NODES;
sbi->s_mount_opt |= APFS_CHECK_NODES;
break;
case Opt_uid:
err = match_int(&args[0], &option);
@@ -1279,7 +1310,7 @@ static int parse_options(struct super_block *sb, char *options)
return err;
sbi->s_uid = make_kuid(current_user_ns(), option);
if (!uid_valid(sbi->s_uid)) {
apfs_err(sb, "invalid uid");
apfs_err(NULL, "invalid uid");
return -EINVAL;
}
break;
@@ -1289,36 +1320,34 @@ static int parse_options(struct super_block *sb, char *options)
return err;
sbi->s_gid = make_kgid(current_user_ns(), option);
if (!gid_valid(sbi->s_gid)) {
apfs_err(sb, "invalid gid");
apfs_err(NULL, "invalid gid");
return -EINVAL;
}
break;
case Opt_vol:
err = match_int(&args[0], &sbi->s_vol_nr);
if (err) {
apfs_err(NULL, "invalid volume number");
return -EINVAL;
}
break;
case Opt_snap:
kfree(sbi->s_snap_name);
sbi->s_snap_name = match_strdup(&args[0]);
if (!sbi->s_snap_name)
return -ENOMEM;
break;
case Opt_tier2:
/* Already read early on mount */
kfree(sbi->s_tier2_path);
sbi->s_tier2_path = match_strdup(&args[0]);
if (!sbi->s_tier2_path)
return -ENOMEM;
break;
default:
/*
* We should have already checked the mount options in
* apfs_preparse_options(), so this is a bug.
*/
apfs_alert(sb, "invalid mount option %s", p);
apfs_warn(NULL, "invalid mount option %s", p);
return -EINVAL;
}
}
out:
apfs_set_nx_flags(sb, nx_flags);
if (!(sb->s_flags & SB_RDONLY)) {
if (nxi->nx_flags & APFS_READWRITE) {
apfs_notice(sb, "experimental write support is enabled");
} else {
apfs_warn(sb, "experimental writes disabled to avoid data loss");
apfs_warn(sb, "if you really want them, check the README");
sb->s_flags |= SB_RDONLY;
}
}
return 0;
}
@@ -1550,12 +1579,7 @@ static int apfs_fill_super(struct super_block *sb, void *data, int silent)
err = apfs_setup_bdi(sb);
if (err)
goto failed_volume;
sbi->s_uid = INVALID_UID;
sbi->s_gid = INVALID_GID;
err = parse_options(sb, data);
if (err)
goto failed_volume;
parse_options_set_flags(sb, sbi, sbi->s_mount_opt);
err = apfs_map_volume_super(sb, false /* write */);
if (err)
@@ -1868,108 +1892,51 @@ out:
return ret;
}
/**
* apfs_preparse_options - Parse the options used to identify a superblock
* @sbi: superblock info
* @options: options string to parse
*
* Returns 0 on success, or a negative error code in case of failure. Even on
* failure, the caller is responsible for freeing all superblock fields.
*/
static int apfs_preparse_options(struct apfs_sb_info *sbi, char *options)
{
char *tofree = NULL;
char *p;
substring_t args[MAX_OPT_ARGS];
int err = 0;
/* Set default values before parsing */
sbi->s_vol_nr = 0;
sbi->s_snap_name = NULL;
sbi->s_tier2_path = NULL;
if (!options)
return 0;
/* Later parse_options() will need the unmodified options string */
options = kstrdup(options, GFP_KERNEL);
if (!options)
return -ENOMEM;
tofree = options;
while ((p = strsep(&options, ",")) != NULL) {
int token;
if (!*p)
continue;
token = match_token(p, tokens, args);
switch (token) {
case Opt_vol:
err = match_int(&args[0], &sbi->s_vol_nr);
if (err)
goto out;
break;
case Opt_snap:
kfree(sbi->s_snap_name);
sbi->s_snap_name = match_strdup(&args[0]);
if (!sbi->s_snap_name) {
err = -ENOMEM;
goto out;
}
break;
case Opt_tier2:
kfree(sbi->s_tier2_path);
sbi->s_tier2_path = match_strdup(&args[0]);
if (!sbi->s_tier2_path) {
err = -ENOMEM;
goto out;
}
break;
case Opt_readwrite:
case Opt_cknodes:
case Opt_uid:
case Opt_gid:
/* Not needed for sget(), will be read later */
break;
default:
apfs_warn(NULL, "invalid mount option %s", p);
err = -EINVAL;
goto out;
}
}
err = 0;
out:
kfree(tofree);
return err;
}
/*
* This function is a copy of mount_bdev() that allows multiple mounts.
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
static int apfs_test_super_fc(struct super_block *sb, struct fs_context *fc);
static int apfs_set_super_fc(struct super_block *sb, struct fs_context *fc);
static int apfs_get_tree(struct fs_context *fc)
#else
static struct dentry *apfs_mount(struct file_system_type *fs_type, int flags,
const char *dev_name, void *data)
#endif
{
struct super_block *sb;
struct apfs_sb_info *sbi;
struct apfs_blkdev_info *bd_info = NULL, *tier2_info = NULL;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0) || RHEL_VERSION_GE(9, 4)
int error = 0;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
blk_mode_t mode = sb_open_mode(fc->sb_flags);
void *data = fc->fs_private;
const char *dev_name = fc->source;
sbi = fc->s_fs_info;
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0) || RHEL_VERSION_GE(9, 4)
blk_mode_t mode = sb_open_mode(flags);
#else
fmode_t mode = FMODE_READ | FMODE_EXCL;
#endif
int error = 0;
#if LINUX_VERSION_CODE < KERNEL_VERSION(7, 0, 0)
sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
if (!sbi)
return ERR_PTR(-ENOMEM);
/* Set up the fields that sget() will need to id the superblock */
error = apfs_preparse_options(sbi, data);
error = parse_options(sbi, data);
if (error)
goto out_free_sbi;
#endif
/* Make sure that snapshots are mounted read-only */
if (sbi->s_snap_name)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
fc->sb_flags |= SB_RDONLY;
#else
flags |= SB_RDONLY;
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0) && !RHEL_VERSION_GE(9, 4)
if (!(flags & SB_RDONLY))
@@ -1981,7 +1948,12 @@ static struct dentry *apfs_mount(struct file_system_type *fs_type, int flags,
goto out_free_sbi;
/* TODO: lockfs stuff? Btrfs doesn't seem to care */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
fc->sb_flags |= SB_NOSEC;
sb = sget_fc(fc, apfs_test_super_fc, apfs_set_super_fc);
#else
sb = sget(fs_type, apfs_test_super, apfs_set_super, flags | SB_NOSEC, sbi);
#endif
if (IS_ERR(sb)) {
error = PTR_ERR(sb);
goto out_unmap_super;
@@ -1997,7 +1969,11 @@ static struct dentry *apfs_mount(struct file_system_type *fs_type, int flags,
WARN_ON(sb->s_dev != bd_info->blki_bdev->bd_dev);
if (sb->s_root) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
if ((fc->sb_flags ^ sb->s_flags) & SB_RDONLY) {
#else
if ((flags ^ sb->s_flags) & SB_RDONLY) {
#endif
error = -EBUSY;
deactivate_locked_super(sb);
goto out_unmap_super;
@@ -2009,8 +1985,14 @@ static struct dentry *apfs_mount(struct file_system_type *fs_type, int flags,
kfree(sbi->s_tier2_path);
sbi->s_tier2_path = NULL;
kfree(sbi);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
fc->s_fs_info = NULL;
#endif
sbi = NULL;
} else {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
fc->s_fs_info = NULL;
#endif
if (!sbi->s_snap_name && !tier2_info)
snprintf(sb->s_id, sizeof(sb->s_id), "%pg:%u", bd_info->blki_bdev, sbi->s_vol_nr);
else if (!tier2_info)
@@ -2022,28 +2004,47 @@ static struct dentry *apfs_mount(struct file_system_type *fs_type, int flags,
error = apfs_read_main_super(sb);
if (error) {
deactivate_locked_super(sb);
return ERR_PTR(error);
goto out_return_error;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0) && !RHEL_VERSION_GE(9, 4)
sb->s_mode = mode;
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
error = apfs_fill_super(sb, data, fc->sb_flags & SB_SILENT ? 1 : 0);
#else
error = apfs_fill_super(sb, data, flags & SB_SILENT ? 1 : 0);
#endif
if (error) {
deactivate_locked_super(sb);
return ERR_PTR(error);
goto out_return_error;
}
sb->s_flags |= SB_ACTIVE;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
fc->root = dget(sb->s_root);
return 0;
#else
return dget(sb->s_root);
#endif
out_unmap_super:
#if LINUX_VERSION_CODE < KERNEL_VERSION(7, 0, 0)
apfs_free_main_super(sbi);
#endif
out_free_sbi:
#if LINUX_VERSION_CODE < KERNEL_VERSION(7, 0, 0)
kfree(sbi->s_snap_name);
kfree(sbi->s_tier2_path);
kfree(sbi);
#endif
out_return_error:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
return error;
#else
return ERR_PTR(error);
#endif
}
/**
@@ -2091,10 +2092,72 @@ static void apfs_kill_sb(struct super_block *sb)
apfs_free_sb_info(sb);
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
static int apfs_test_super_fc(struct super_block *sb, struct fs_context *fc)
{
return apfs_test_super(sb, fc->s_fs_info);
}
static int apfs_set_super_fc(struct super_block *sb, struct fs_context *fc)
{
return apfs_set_super(sb, fc->s_fs_info);
}
static int apfs_reconfigure(struct fs_context *fc)
{
struct super_block *sb = fc->root->d_sb;
int flags = fc->sb_flags;
return apfs_remount(sb, &flags, fc->fs_private);
}
static void apfs_free_fc(struct fs_context *fc)
{
struct apfs_sb_info *sbi = fc->s_fs_info;
if (sbi) {
apfs_free_main_super(sbi);
kfree(sbi->s_snap_name);
kfree(sbi->s_tier2_path);
kfree(sbi);
}
}
static int apfs_parse_monolithic(struct fs_context *fc, void *data)
{
return parse_options(fc->s_fs_info, data);
}
static const struct fs_context_operations apfs_context_ops = {
.get_tree = apfs_get_tree,
.reconfigure = apfs_reconfigure,
.free = apfs_free_fc,
.parse_monolithic = apfs_parse_monolithic,
};
static int apfs_init_fs_context(struct fs_context *fc)
{
struct apfs_sb_info *sbi;
/* Setup happens here now instead of the top of apfs_mount */
sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
if (!sbi)
return -ENOMEM;
fc->s_fs_info = sbi;
fc->ops = &apfs_context_ops;
return 0;
}
#endif /* LINUX_VERSION_CODE >= 7.0.0 */
static struct file_system_type apfs_fs_type = {
.owner = THIS_MODULE,
.name = "apfs",
#if LINUX_VERSION_CODE < KERNEL_VERSION(7, 0, 0)
.mount = apfs_mount,
#else
.init_fs_context = apfs_init_fs_context,
#endif
.kill_sb = apfs_kill_sb,
.fs_flags = FS_REQUIRES_DEV,
};