erofs: accept source file descriptor via fsconfig

Allow userspace to pass an already-opened file descriptor as the mount
source instead of a path string.  This is useful for tools that already
hold an fd to the image, such as composefs reusing an existing erofs
backing file.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
Reviewed-by: Gao Xiang <xiang@kernel.org>
Reviewed-by: Christian Brauner (Amutable) <brauner@kernel.org>
Acked-by: Jan Kara <jack@suse.cz>
Reviewed-by: Chao Yu <chao@kernel.org>
Signed-off-by: Gao Xiang <xiang@kernel.org>
This commit is contained in:
Giuseppe Scrivano 2026-08-04 07:58:42 +02:00 committed by Gao Xiang
parent 9ba8471dab
commit 058884d234
2 changed files with 79 additions and 11 deletions

View File

@ -137,8 +137,25 @@ fsoffset=%llu Specify block-aligned filesystem offset for the primary d
inode_share Enable inode page sharing for this filesystem. Inodes with
identical content within the same domain ID can share the
page cache.
source=%s (For file-backed mounts) Specify the backing image as a path
or as an already-opened file descriptor.
=================== =========================================================
File-backed mounts
==================
When CONFIG_EROFS_FS_BACKED_BY_FILE is enabled, EROFS file-backed images
can be mounted directly without a loopback block device. The backing file
can be given either as a path, or as an already-opened file descriptor.
When a file descriptor is used, the kernel resolves its path and records it
so that /proc/mounts and similar interfaces can still report the mount
source.
Only regular files are accepted as backing files; to mount an image that
resides on a block device, use the traditional block device mount path
instead.
Sysfs Entries
=============

View File

@ -386,6 +386,7 @@ static void erofs_default_options(struct erofs_sb_info *sbi)
enum {
Opt_user_xattr, Opt_acl, Opt_cache_strategy, Opt_dax, Opt_dax_enum,
Opt_device, Opt_domain_id, Opt_directio, Opt_fsoffset, Opt_inode_share,
Opt_source,
};
static const struct constant_table erofs_param_cache_strategy[] = {
@ -402,17 +403,18 @@ static const struct constant_table erofs_dax_param_enums[] = {
};
static const struct fs_parameter_spec erofs_fs_parameters[] = {
fsparam_flag_no("user_xattr", Opt_user_xattr),
fsparam_flag_no("acl", Opt_acl),
fsparam_enum("cache_strategy", Opt_cache_strategy,
fsparam_flag_no("user_xattr", Opt_user_xattr),
fsparam_flag_no("acl", Opt_acl),
fsparam_enum("cache_strategy", Opt_cache_strategy,
erofs_param_cache_strategy),
fsparam_flag("dax", Opt_dax),
fsparam_enum("dax", Opt_dax_enum, erofs_dax_param_enums),
fsparam_string("device", Opt_device),
fsparam_string("domain_id", Opt_domain_id),
fsparam_flag_no("directio", Opt_directio),
fsparam_u64("fsoffset", Opt_fsoffset),
fsparam_flag("inode_share", Opt_inode_share),
fsparam_flag("dax", Opt_dax),
fsparam_enum("dax", Opt_dax_enum, erofs_dax_param_enums),
fsparam_string("device", Opt_device),
fsparam_string("domain_id", Opt_domain_id),
fsparam_flag_no("directio", Opt_directio),
fsparam_u64("fsoffset", Opt_fsoffset),
fsparam_flag("inode_share", Opt_inode_share),
fsparam_file_or_string("source", Opt_source),
{}
};
@ -437,6 +439,40 @@ static bool erofs_fc_set_dax_mode(struct fs_context *fc, unsigned int mode)
return false;
}
static int erofs_fc_parse_source(struct fs_context *fc,
struct fs_parameter *param)
{
struct erofs_sb_info *sbi = fc->s_fs_info;
if (fc->source || sbi->dif0.file)
return invalf(fc, "Multiple sources");
switch (param->type) {
case fs_value_is_string:
fc->source = param->string;
param->string = NULL;
return 0;
case fs_value_is_file: {
char *buf __free(kfree) = kmalloc(PATH_MAX, GFP_KERNEL);
char *p;
if (!buf)
return -ENOMEM;
p = file_path(param->file, buf, PATH_MAX);
if (IS_ERR(p))
return PTR_ERR(p);
fc->source = kstrdup(p, GFP_KERNEL);
if (!fc->source)
return -ENOMEM;
sbi->dif0.file = no_free_ptr(param->file);
return 0;
}
default:
WARN_ON_ONCE(true);
return -EINVAL;
}
}
static int erofs_fc_parse_param(struct fs_context *fc,
struct fs_parameter *param)
{
@ -524,6 +560,8 @@ static int erofs_fc_parse_param(struct fs_context *fc,
else
set_opt(&sbi->opt, INODE_SHARE);
break;
case Opt_source:
return erofs_fc_parse_source(fc, param);
}
return 0;
}
@ -743,13 +781,26 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
static int erofs_fc_get_tree(struct fs_context *fc)
{
struct erofs_sb_info *sbi = fc->s_fs_info;
int ret;
if (sbi->dif0.file) {
if (!IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE)) {
errorfc(fc, "source fd option not supported");
return -EINVAL;
}
if (!S_ISREG(file_inode(sbi->dif0.file)->i_mode) ||
!sbi->dif0.file->f_mapping->a_ops->read_folio) {
errorfc(fc, "source is unsupported");
return -EINVAL;
}
return get_tree_nodev(fc, erofs_fc_fill_super);
}
ret = get_tree_bdev_flags(fc, erofs_fc_fill_super,
IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE) ?
GET_TREE_BDEV_QUIET_LOOKUP : 0);
if (IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE) && ret == -ENOTBLK) {
struct erofs_sb_info *sbi = fc->s_fs_info;
struct file *file;
if (!fc->source)