文件系统挂载 API

概述

现在,创建新的挂载需要一个多步骤的过程

  1. 创建文件系统上下文。

  2. 解析参数并将它们附加到上下文中。参数预计会从用户空间单独传递,但也可以处理旧的二进制参数。

  3. 验证和预处理上下文。

  4. 获取或创建超级块和可挂载的根。

  5. 执行挂载。

  6. 返回附加到上下文的错误消息。

  7. 销毁上下文。

为了支持这一点,file_system_type 结构增加了两个新字段

int (*init_fs_context)(struct fs_context *fc);
const struct fs_parameter_description *parameters;

第一个字段用于设置文件系统上下文的特定于文件系统的部分,包括额外的空间,第二个字段指向参数描述,用于在注册时进行验证,并由未来的系统调用进行查询。

请注意,安全性初始化是在文件系统被调用之后完成的,以便可以首先调整命名空间。

文件系统上下文

超级块的创建和重新配置由文件系统上下文控制。这由 fs_context 结构表示

struct fs_context {
        const struct fs_context_operations *ops;
        struct file_system_type *fs_type;
        void                    *fs_private;
        struct dentry           *root;
        struct user_namespace   *user_ns;
        struct net              *net_ns;
        const struct cred       *cred;
        char                    *source;
        char                    *subtype;
        void                    *security;
        void                    *s_fs_info;
        unsigned int            sb_flags;
        unsigned int            sb_flags_mask;
        unsigned int            s_iflags;
        enum fs_context_purpose purpose:8;
        ...
};

fs_context 字段如下

  • const struct fs_context_operations *ops
    

    这些是可以在文件系统上下文中完成的操作(见下文)。这必须由 ->init_fs_context() file_system_type 操作设置。

  • struct file_system_type *fs_type
    

    指向正在构建或重新配置的文件系统的 file_system_type 的指针。这保留了对类型所有者的引用。

  • void *fs_private
    

    指向文件系统私有数据的指针。这是文件系统需要存储其解析的任何选项的地方。

  • struct dentry *root
    

    指向可挂载树的根(并间接指向其超级块)的指针。这由 ->get_tree() 操作填充。如果设置了此项,还必须持有对 root->d_sb 的活动引用。

  • struct user_namespace *user_ns
    struct net *net_ns
    

    调用进程正在使用的一部分命名空间。它们保留对每个命名空间的引用。订阅的命名空间可以由文件系统替换,以反映其他来源,例如自动挂载上的父挂载超级块。

  • const struct cred *cred
    

    挂载者的凭据。这保留了对凭据的引用。

  • char *source
    

    这指定了源。它可以是块设备(例如 /dev/sda1)或更特殊的东西,例如 NFS 所需的“host:/path”。

  • char *subtype
    

    这是一个要添加到 /proc/mounts 中显示的类型以对其进行限定的字符串(由 FUSE 使用)。如果需要,文件系统可以使用它来设置。

  • void *security
    

    供 LSM 将其超级块的安全数据挂起的位置。相关的安全操作将在下面描述。

  • void *s_fs_info
    

    新超级块的建议的 s_fs_info,在超级块中由 sget_fc() 设置。这可以用于区分超级块。

  • unsigned int sb_flags
    unsigned int sb_flags_mask
    

    哪些位 SB_* 标志将在 super_block::s_flags 中设置/清除。

  • unsigned int s_iflags
    

    当创建超级块时,这些位将与 s->s_iflags 进行按位或运算。

  • enum fs_context_purpose
    

    这表示上下文的预期用途。可用值为

    FS_CONTEXT_FOR_MOUNT,

    用于显式挂载的新超级块

    FS_CONTEXT_FOR_SUBMOUNT

    现有挂载的新自动子挂载

    FS_CONTEXT_FOR_RECONFIGURE

    更改现有挂载

挂载上下文是通过调用 vfs_new_fs_context() 或 vfs_dup_fs_context() 创建的,并通过 put_fs_context() 销毁。请注意,该结构不是引用计数的。

VFS、安全性和文件系统挂载选项通过 vfs_parse_mount_option() 单独设置。旧的 mount(2) 系统调用作为数据页提供的选项可以使用 generic_parse_monolithic() 解析。

挂载时,允许文件系统从任何指针获取数据并将其附加到超级块(或其他),前提是它清除了挂载上下文中的指针。

还允许文件系统分配资源并使用挂载上下文将它们固定。例如,NFS 可能会固定适当的协议版本模块。

文件系统上下文操作

文件系统上下文指向操作表

struct fs_context_operations {
        void (*free)(struct fs_context *fc);
        int (*dup)(struct fs_context *fc, struct fs_context *src_fc);
        int (*parse_param)(struct fs_context *fc,
                           struct fs_parameter *param);
        int (*parse_monolithic)(struct fs_context *fc, void *data);
        int (*get_tree)(struct fs_context *fc);
        int (*reconfigure)(struct fs_context *fc);
};

这些操作由挂载过程的各个阶段调用,以管理文件系统上下文。它们如下所示

  • void (*free)(struct fs_context *fc);
    

    当上下文被销毁时,调用它来清理文件系统上下文的特定于文件系统的部分。它应该知道上下文的某些部分可能已被删除,并且被 ->get_tree() 设置为 NULL。

  • int (*dup)(struct fs_context *fc, struct fs_context *src_fc);
    

    当文件系统上下文被复制时调用,以复制文件系统私有数据。可以返回错误以指示无法执行此操作。

    警告

    请注意,即使此操作失败,也会立即调用 put_fs_context(),因此 ->dup() 必须使文件系统私有数据对于 ->free() 是安全的。

  • int (*parse_param)(struct fs_context *fc,
                       struct fs_parameter *param);
    

    当参数添加到文件系统上下文时调用。param 指向键名,可能是一个值对象。VFS 特定的选项将被剔除,并且 fc->sb_flags 在上下文中更新。安全选项也将被剔除,并且 fc->security 将被更新。

    可以使用 fs_parse() 和 fs_lookup_param() 解析参数。请注意,源作为名为“source”的参数呈现。

    如果成功,应返回 0,否则返回负错误代码。

  • int (*parse_monolithic)(struct fs_context *fc, void *data);
    

    当调用 mount(2) 系统调用以一次传递整个数据页时调用。如果这预计只是一个以逗号分隔的“key[=val]”项目列表,则可以将其设置为 NULL。

    返回值与 ->parse_param() 相同。

    如果文件系统(例如 NFS)需要先检查数据,然后发现它是标准的键值列表,则可以将其传递给 generic_parse_monolithic()。

  • int (*get_tree)(struct fs_context *fc);
    

    调用它来获取或创建可挂载的根和超级块,使用存储在文件系统上下文中的信息(重新配置通过不同的向量进行)。它可以从文件系统上下文中分离它所需的任何资源,并将其转移到它创建的超级块。

    成功时,它应该将 fc->root 设置为可挂载的根并返回 0。如果发生错误,它应该返回负错误代码。

    用户空间驱动的上下文中的阶段将设置为仅允许在任何特定上下文中调用此方法一次。

  • int (*reconfigure)(struct fs_context *fc);
    

    调用它以使用存储在文件系统上下文中的信息来执行超级块的重新配置。它可以从文件系统上下文中分离它所需的任何资源,并将其转移到超级块。可以从 fc->root->d_sb 中找到超级块。

    成功时,它应该返回 0。如果发生错误,它应该返回负错误代码。

    注意

    重新配置旨在替代 remount_fs。

文件系统上下文安全性

文件系统上下文包含一个安全指针,LSM 可以使用它来为要挂载的超级块构建安全上下文。新的挂载代码为此目的使用了许多操作

  • int security_fs_context_alloc(struct fs_context *fc,
                                  struct dentry *reference);
    

    调用它来初始化 fc->security(预设为 NULL)并分配任何需要的资源。成功时应返回 0,失败时返回负错误代码。

    如果为超级块重新配置 (FS_CONTEXT_FOR_RECONFIGURE) 创建上下文,则引用将为非 NULL,在这种情况下,它指示要重新配置的超级块的根 dentry。如果为子挂载 (FS_CONTEXT_FOR_SUBMOUNT) 创建上下文,它也将为非 NULL,在这种情况下,它指示自动挂载点。

  • int security_fs_context_dup(struct fs_context *fc,
                                struct fs_context *src_fc);
    

    调用它来初始化 fc->security(预设为 NULL)并分配任何需要的资源。原始文件系统上下文由 src_fc 指向,可用于引用。成功时应返回 0,失败时返回负错误代码。

  • void security_fs_context_free(struct fs_context *fc);
    

    调用它来清理附加到 fc->security 的任何内容。请注意,这些内容可能已转移到超级块,并且在 get_tree 期间清除了该指针。

  • int security_fs_context_parse_param(struct fs_context *fc,
                                        struct fs_parameter *param);
    

    为每个挂载参数(包括源)调用。参数与 ->parse_param() 方法相同。应返回 0 以指示应将参数传递给文件系统,返回 1 以指示应丢弃该参数,或者返回错误以指示应拒绝该参数。

    可以修改 param 指向的值(如果是一个字符串)或窃取它(前提是将值指针设置为 NULL)。如果被窃取,则必须返回 1 以防止将其传递给文件系统。

  • int security_fs_context_validate(struct fs_context *fc);
    

    在解析完所有选项后调用,以验证整个集合并执行任何必要的分配,以便 security_sb_get_tree() 和 security_sb_reconfigure() 不太可能失败。它应该返回 0 或负错误代码。

    在重新配置的情况下,可以通过 fc->root 访问目标超级块。

  • int security_sb_get_tree(struct fs_context *fc);
    

    在挂载过程中调用,以验证是否允许挂载指定的超级块并将安全数据传输到那里。它应该返回 0 或负错误代码。

  • void security_sb_reconfigure(struct fs_context *fc);
    

    调用它以将任何重新配置应用于 LSM 的上下文。它绝不能失败。错误检查和资源分配必须通过参数解析和验证挂钩预先完成。

  • int security_sb_mountpoint(struct fs_context *fc,
                               struct path *mountpoint,
                               unsigned int mnt_flags);
    

    在挂载过程中调用,以验证是否允许将附加到上下文的根 dentry 附加到指定的挂载点。成功时应返回 0,失败时返回负错误代码。

VFS 文件系统上下文 API

有四个用于创建文件系统上下文的操作和一个用于销毁上下文的操作

  • struct fs_context *fs_context_for_mount(struct file_system_type *fs_type,
                                            unsigned int sb_flags);
    

    分配文件系统上下文,用于设置新的挂载,无论是使用新的超级块还是共享现有的超级块。这设置超级块标志,初始化安全性,并调用 fs_type->init_fs_context() 来初始化文件系统私有数据。

    fs_type 指定将管理上下文的文件系统类型,sb_flags 预设其中存储的超级块标志。

  • struct fs_context *fs_context_for_reconfigure(
             struct dentry *dentry,
             unsigned int sb_flags,
             unsigned int sb_flags_mask);
    

    分配一个文件系统上下文,用于重新配置现有的超级块。dentry 提供对要配置的超级块的引用。sb_flags 和 sb_flags_mask 指示需要更改哪些超级块标志以及更改为何值。

  • struct fs_context *fs_context_for_submount(
             struct file_system_type *fs_type,
             struct dentry *reference);
    

    分配一个文件系统上下文,用于为自动挂载点或其他派生的超级块创建新的挂载。fs_type 指定将管理上下文的文件系统类型,并且引用 dentry 提供参数。命名空间也从引用 dentry 的超级块传播。

    请注意,引用 dentry 的文件系统类型与 fs_type 不必相同。

  • struct fs_context *vfs_dup_fs_context(struct fs_context *src_fc);
    

    复制文件系统上下文,复制任何记录的选项,并复制或额外引用其中持有的任何资源。这适用于文件系统必须在挂载中获取挂载的情况,例如 NFS4 通过内部挂载目标服务器的根目录,然后对目标目录进行私有路径遍历来实现。

    新上下文中的用途是从旧上下文中继承的。

  • void put_fs_context(struct fs_context *fc);
    

    销毁文件系统上下文,释放它持有的任何资源。这将调用 ->free() 操作。这旨在由任何创建文件系统上下文的人调用。

    警告

    文件系统上下文不进行引用计数,因此这会导致无条件销毁。

在上述所有操作中,除了 put 操作之外,返回值是挂载上下文指针或负错误代码。

对于其余的操作,如果发生错误,将返回负错误代码。

  • int vfs_parse_fs_param(struct fs_context *fc,
                           struct fs_parameter *param);
    

    向文件系统上下文提供单个挂载参数。这包括将源/设备指定为“source”参数(如果文件系统支持,可以多次指定)。

    param 指定参数键名和值。首先检查该参数是否对应于标准挂载标志(在这种情况下,它用于设置 SB_xxx 标志并被使用)或安全选项(在这种情况下,LSM 使用它),然后再将其传递给文件系统。

    参数值是类型化的,可以是以下之一

    fs_value_is_flag

    参数未给定值

    fs_value_is_string

    值是一个字符串

    fs_value_is_blob

    值是一个二进制 blob

    fs_value_is_filename

    值是一个文件名* + dirfd

    fs_value_is_file

    值是一个打开的文件(file*)

    如果存在值,则该值将存储在结构中的联合中,位于 param->{string,blob,name,file} 中的一个。请注意,该函数可能会窃取并清除指针,但随后会负责处理该对象。

  • int vfs_parse_fs_string(struct fs_context *fc, const char *key,
                            const char *value, size_t v_size);
    

    vfs_parse_fs_param() 的包装器,它复制传递给它的值字符串。

  • int generic_parse_monolithic(struct fs_context *fc, void *data);
    

    解析 sys_mount() 数据页,假设格式是文本列表,其中包含以逗号分隔的 key[=val] 选项。列表中的每个项目都传递给 vfs_mount_option()。这是 ->parse_monolithic() 方法为 NULL 时的默认值。

  • int vfs_get_tree(struct fs_context *fc);
    

    获取或创建可挂载的根和超级块,使用文件系统上下文中的参数来选择/配置超级块。这将调用 ->get_tree() 方法。

  • struct vfsmount *vfs_create_mount(struct fs_context *fc);
    

    根据指定的文件系统上下文中的参数创建挂载。请注意,这不会将挂载附加到任何内容。

超级块创建助手

文件系统可以使用许多 VFS 助手来创建或查找超级块。

  • struct super_block *
    sget_fc(struct fs_context *fc,
            int (*test)(struct super_block *sb, struct fs_context *fc),
            int (*set)(struct super_block *sb, struct fs_context *fc));
    

    这是核心例程。如果 test 为非 NULL,它会使用 test 函数来匹配它们,搜索与 fs_context 中保存的标准匹配的现有超级块。如果未找到匹配项,则会创建一个新的超级块,并调用 set 函数来设置它。

    在调用 set 函数之前,fc->s_fs_info 将被传输到 sb->s_fs_info,如果 set 返回成功(即 0),则 fc->s_fs_info 将被清除。

以下助手都包装了 sget_fc()

  1. vfs_get_single_super

    系统中只能存在一个这样的超级块。任何进一步尝试获取新的超级块都会获取此超级块(并且任何参数差异都会被忽略)。

  2. vfs_get_keyed_super

    可以存在此类型的多个超级块,它们以其 s_fs_info 指针为键(例如,这可能指的是命名空间)。

  3. vfs_get_independent_super

    可以存在此类型的多个独立超级块。此函数永远不会匹配现有的超级块,并且始终创建一个新的超级块。

参数描述

参数使用 linux/fs_parser.h 中定义的结构进行描述。有一个将所有内容链接在一起的核心描述结构

struct fs_parameter_description {
        const struct fs_parameter_spec *specs;
        const struct fs_parameter_enum *enums;
};

例如

enum {
        Opt_autocell,
        Opt_bar,
        Opt_dyn,
        Opt_foo,
        Opt_source,
};

static const struct fs_parameter_description afs_fs_parameters = {
        .specs          = afs_param_specs,
        .enums          = afs_param_enums,
};

成员如下

  1. const struct fs_parameter_specification *specs;
    

    参数规范表,以空条目终止,其中条目类型为

    struct fs_parameter_spec {
            const char              *name;
            u8                      opt;
            enum fs_parameter_type  type:8;
            unsigned short          flags;
    };
    

    “name”字段是一个字符串,与参数键完全匹配(没有通配符、模式,也没有不区分大小写),“opt”是在成功匹配的情况下由 fs_parser() 函数返回的值。

    “type”字段指示所需的值类型,必须是以下之一

    类型名称

    预期值

    结果在

    fs_param_is_flag

    无值

    n/a

    fs_param_is_bool

    布尔值

    result->boolean

    fs_param_is_u32

    32 位无符号整数

    result->uint_32

    fs_param_is_u32_octal

    32 位八进制整数

    result->uint_32

    fs_param_is_u32_hex

    32 位十六进制整数

    result->uint_32

    fs_param_is_s32

    32 位有符号整数

    result->int_32

    fs_param_is_u64

    64 位无符号整数

    result->uint_64

    fs_param_is_enum

    枚举值名称

    result->uint_32

    fs_param_is_string

    任意字符串

    param->string

    fs_param_is_blob

    二进制 blob

    param->blob

    fs_param_is_blockdev

    块设备路径

    • 需要查找

    fs_param_is_path

    路径

    • 需要查找

    fs_param_is_fd

    文件描述符

    result->int_32

    fs_param_is_uid

    用户 ID (u32)

    result->uid

    fs_param_is_gid

    组 ID (u32)

    result->gid

    请注意,如果值是 fs_param_is_bool 类型,fs_parse() 将尝试将任何字符串值与“0”、“1”、“no”、“yes”、“false”、“true”进行匹配。

    每个参数还可以使用“flags”进行限定

    fs_param_v_optional

    该值是可选的

    fs_param_neg_with_no

    如果键以“no”为前缀,则设置 result->negated

    fs_param_neg_with_empty

    如果值是 "",则设置 result->negated

    fs_param_deprecated

    该参数已弃用。

    这些都包装在许多方便的包装器中

    指定

    fsparam_flag()

    fs_param_is_flag

    fsparam_flag_no()

    fs_param_is_flag, fs_param_neg_with_no

    fsparam_bool()

    fs_param_is_bool

    fsparam_u32()

    fs_param_is_u32

    fsparam_u32oct()

    fs_param_is_u32_octal

    fsparam_u32hex()

    fs_param_is_u32_hex

    fsparam_s32()

    fs_param_is_s32

    fsparam_u64()

    fs_param_is_u64

    fsparam_enum()

    fs_param_is_enum

    fsparam_string()

    fs_param_is_string

    fsparam_blob()

    fs_param_is_blob

    fsparam_bdev()

    fs_param_is_blockdev

    fsparam_path()

    fs_param_is_path

    fsparam_fd()

    fs_param_is_fd

    fsparam_uid()

    fs_param_is_uid

    fsparam_gid()

    fs_param_is_gid

    所有这些都接受两个参数,名称字符串和选项编号 - 例如

    static const struct fs_parameter_spec afs_param_specs[] = {
            fsparam_flag    ("autocell",    Opt_autocell),
            fsparam_flag    ("dyn",         Opt_dyn),
            fsparam_string  ("source",      Opt_source),
            fsparam_flag_no ("foo",         Opt_foo),
            {}
    };
    

    提供了一个额外的宏 __fsparam(),它接受一对额外的参数,以指定类型和标志,用于任何与上述宏不匹配的内容。

  2. const struct fs_parameter_enum *enums;
    

    枚举值名称到整数映射的表,以空条目终止。类型为

    struct fs_parameter_enum {
            u8              opt;
            char            name[14];
            u8              value;
    };
    

    其中数组是 { parameter ID, name } 键元素的不排序列表,指示要映射到的值,例如

    static const struct fs_parameter_enum afs_param_enums[] = {
            { Opt_bar,   "x",      1},
            { Opt_bar,   "y",      23},
            { Opt_bar,   "z",      42},
    };
    

    如果遇到 fs_param_is_enum 类型的参数,fs_parse() 将尝试在枚举表中查找该值,并将结果存储在解析结果中。

解析器应由 file_system_type 结构中的解析器指针指向,因为这将提供注册时的验证 (如果 CONFIG_VALIDATE_FS_PARSER=y),并且允许使用 fsinfo() 系统调用从用户空间查询描述。

参数助手函数

提供了许多助手函数来帮助文件系统或 LSM 处理给定的参数。

  • int lookup_constant(const struct constant_table tbl[],
                        const char *name, int not_found);
    

    按名称在名称 -> 整数映射表中查找常量。该表是以下类型的元素数组

    struct constant_table {
            const char      *name;
            int             value;
    };
    

    如果找到匹配项,则返回对应的值。如果未找到匹配项,则改为返回 not_found 值。

  • bool validate_constant_table(const struct constant_table *tbl,
                                 size_t tbl_size,
                                 int low, int high, int special);
    

    验证常量表。检查所有元素是否已正确排序,是否没有重复项,并且值是否在 low 和 high 之间(含),尽管为该范围之外的一个允许的特殊值做了规定。如果不需要特殊值,则应将 special 设置为位于 low 到 high 范围之内。

    如果一切正常,则返回 true。如果表无效,则会将错误记录到内核日志缓冲区,并返回 false。

  • bool fs_validate_description(const char *name,
                                 const struct fs_parameter_description *desc);
    

    这会对参数描述执行一些验证检查。如果描述良好,则返回 true,否则返回 false。如果验证失败,则会将错误记录到内核日志缓冲区。

  • int fs_parse(struct fs_context *fc,
                 const struct fs_parameter_description *desc,
                 struct fs_parameter *param,
                 struct fs_parse_result *result);
    

    这是参数的主要解释器。它使用参数描述按键名查找参数,并将其转换为选项编号(它返回该编号)。

    如果成功,并且参数类型指示结果是布尔值、整数、枚举、uid 或 gid 类型,则此函数会将该值转换并将结果存储在 result->{boolean,int_32,uint_32,uint_64,uid,gid} 中。

    如果最初没有匹配,则键以“no”为前缀,并且没有值,则将尝试查找删除前缀的键。如果这匹配类型设置了 fs_param_neg_with_no 标志的参数,则将进行匹配,并且 result->negated 将设置为 true。

    如果参数不匹配,将返回 -ENOPARAM;如果参数匹配,但值错误,将返回 -EINVAL;否则,将返回参数的选项编号。

  • int fs_lookup_param(struct fs_context *fc,
                        struct fs_parameter *value,
                        bool want_bdev,
                        unsigned int flags,
                        struct path *_path);
    

    这会获取一个携带字符串或文件名类型的参数,并尝试对其执行路径查找。如果参数需要一个块设备,则会检查 inode 是否实际表示一个块设备。

    如果成功,则返回 0,并且将设置 *_path;如果失败,则返回负错误代码。