登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  linux

Linux mount_setattr 怎样批量调整挂载属性

来源:17golang原创

时间:2026-11-03 13:25:01 432浏览 收藏

Linux 的 mount_setattr() 不是修改目录中每个文件的工具,而是修改一个挂载对象,或者由它派生出的整棵挂载树。需要把目录变成只读、禁止执行、禁止设备访问,或调整挂载传播类型时,可以把目标路径、属性集合和递归范围一次交给内核处理。

官方文档地址:https://www.man7.org/linux/man-pages/man2/mount_setattr.2.html

批量调整的关键是先决定“改哪一个挂载对象”,再分别填写 attr_clr、attr_set 和 propagation。AT_RECURSIVE 负责把同一组变化扩展到挂载树;它不会把普通目录递归变成挂载点,也不会替代权限检查。
  • 单个挂载点:用 dirfd + path 定位,flags 通常为 0。
  • 整棵挂载树:在定位参数上加入 AT_RECURSIVE。
  • 设置和清除:把要移除的位放进 attr_clr,把要打开的位放进 attr_set。
  • 兼容性:结构体必须零填充,并把 size 传成调用方实际使用的结构体版本大小。

先区分挂载对象属性与文件系统参数

Linux 的挂载选项不只有一层。挂载对象属性附着在某个 mount object 上,同一个文件系统实例可以派生出多个具有不同属性的挂载对象;文件系统参数则更接近 superblock,影响从同一文件系统实例派生出的对象。mount_setattr() 主要处理前者,fsconfig() 等新 Mount API 负责另一层。

因此,下面这些任务适合用 mount_setattr() 组织:

目标对应字段说明
变成只读attr_set 中的 MOUNT_ATTR_RDONLY作用在挂载对象,不等价于修改文件系统实例参数。
禁止执行或设备访问MOUNT_ATTR_NOEXEC、MOUNT_ATTR_NODEV适合按挂载边界表达运行时限制。
修改传播类型propagation从 MS_PRIVATE、MS_SHARED、MS_SLAVE、MS_UNBINDABLE 中选择一个。
配置文件系统自身参数不要只依赖 mount_setattr()这属于另一层语义,应使用相应 Mount API 或文件系统工具。
mount_setattr 修改挂载对象属性并与文件系统参数分层的静态结构图
图1:挂载对象属性与文件系统参数的静态边界说明图,不是终端截图或运行证据。

按 dirfd、path 和 flags 定位目标

系统调用原型是 mount_setattr(int dirfd, const char *path, unsigned int flags, struct mount_attr *attr, size_t size)。其中 dirfd 与 path 共同确定目标,flags 改变路径解析和递归范围。

  • dirfd=AT_FDCWD 时,相对路径从当前工作目录解析;也可以传入一个目录文件描述符,让路径解析相对于该目录。
  • path 指向挂载点或挂载树的入口,不是任意普通目录。
  • AT_EMPTY_PATH 配合空路径时,目标由 dirfd 指向的挂载对象决定。
  • AT_RECURSIVE 把变化扩展到整个挂载树;没有它时只处理目标挂载对象。
  • AT_SYMLINK_NOFOLLOW 和 AT_NO_AUTOMOUNT 用于收紧路径解析行为,是否需要它们要结合调用场景。
#include 
#include 
#include 
#include 

static int make_tree_readonly(const char *mount_path) {
    struct mount_attr attr = {0};

    // 只设置挂载对象的只读属性,不修改目录中的单个文件权限。
    attr.attr_set = MOUNT_ATTR_RDONLY;

    // AT_RECURSIVE 让同一组属性作用于目标挂载树。
    return (int)syscall(SYS_mount_setattr,
                        AT_FDCWD, mount_path, AT_RECURSIVE,
                        &attr, sizeof(attr));
}

这个例子表达的是系统调用参数关系。实际编译时要确认 libc、内核头文件和目标架构提供了 SYS_mount_setattr、struct mount_attr 及对应属性常量;如果调用返回 -1,应立即保存 errno,不要把失败误判为部分成功。

用 attr_set 与 attr_clr 组织属性变更

attr_clr 与 attr_set 是两个不同方向的位集合。内核会先清除 attr_clr 指定的属性,再设置 attr_set 指定的属性,因此同一个位不应同时放入两边。

#include 

static void prepare_sandbox_attributes(struct mount_attr *attr) {
    *attr = (struct mount_attr){0};

    // 先清掉可能残留的执行和设备访问能力,再设置只读属性。
    attr->attr_clr = MOUNT_ATTR_NOEXEC | MOUNT_ATTR_NODEV;
    attr->attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOEXEC | MOUNT_ATTR_NODEV;

    // propagation 为 0 表示不改变传播类型,避免把属性修改和传播策略混在一起。
    attr->propagation = 0;
}

上面的写法展示了“清除旧状态、设置目标状态”的意图。若只要求最终状态为只读且禁止执行,可以只在 attr_set 写入对应位;若需要把可能存在的旧 atime 设置一并替换,则必须按文档要求让 attr_clr 覆盖 MOUNT_ATTR__ATIME,不能只清除其中一小段枚举位。

同一组清除和设置请求重复执行后,属性状态应保持不变。这使得管理程序可以把变更设计成幂等操作,但幂等不代表可以忽略权限、挂载树中某个节点的锁定状态或正在进行的写入。

按单点、递归和传播需求选择调用方式

“批量”并不只有一种含义。最简单的是给一棵挂载树统一设置只读;更复杂的场景还会同时处理递归范围和传播策略。建议把三类选择拆开审阅:

场景flagsattr 内容适用判断
只改入口挂载点0attr_set 或 attr_clr子挂载点必须保持独立策略时使用。
整棵树只读AT_RECURSIVE设置 MOUNT_ATTR_RDONLY容器或沙箱的挂载树需要统一收紧时使用。
整棵树禁止执行和设备访问AT_RECURSIVE设置 MOUNT_ATTR_NOEXEC | MOUNT_ATTR_NODEV把运行时访问边界放在挂载层表达。
改变传播按路径解析需要选择设置一个 propagation 值传播类型互斥,不要同时填写多个。
mount_setattr 通过入口挂载点和 AT_RECURSIVE 扩展到挂载树的静态关系图
图2:单个挂载对象、递归挂载树和传播类型的静态关系图,不是实际系统截图。

按内核约束和权限边界落地

调用能否成功,除了字段是否填对,还取决于目标挂载对象、挂载命名空间、底层文件系统和当前权限。把错误码按原因分类,排查速度会比只打印“operation failed”更快。

错误常见原因处理方向
EINVAL目标不是挂载点、flags 或属性位不受支持、传播类型组合无效。先缩小到单个挂载点,再逐项加入 flags 和属性位。
EBUSY尝试设置只读或 ID 映射时,挂载仍有不允许的写入状态。检查打开的写句柄和挂载对象状态,不把它当成普通路径错误。
EPERM属性被锁定,或调用者在对应用户命名空间中缺少能力。确认挂载命名空间、用户命名空间和 CAP_SYS_ADMIN 边界。
E2BIG传入的扩展结构体大小超过内核支持范围,且未知字段非零。零填充结构体,并按支持的结构体版本传入 size。
#include 
#include 
#include 

static int report_mount_setattr_error(int rc) {
    if (rc == 0) {
        return 0;
    }

    // syscall 失败时 errno 才是判断权限、忙状态和参数错误的依据。
    int saved_errno = errno;
    fprintf(stderr, "mount_setattr failed: %s\n", strerror(saved_errno));

    // 保留原始 errno,方便上层按 EINVAL、EBUSY 或 EPERM 分支处理。
    errno = saved_errno;
    return -1;
}

另外,mount_attr 是可扩展结构体,初始化时应整体清零;新字段追加在结构体尾部,未知字段保持零值时内核才能安全忽略。若要使用 ID-mapped mount,还要满足用户命名空间、底层文件系统支持、分离挂载和无写入者等额外前提。

常见问题与选择结论

mount_setattr 会递归修改目录里的文件权限吗?

不会。它修改的是挂载对象属性;AT_RECURSIVE 的递归对象是挂载树,不是普通文件目录树。要修改文件权限,应使用文件权限或 ACL 相关接口。

attr_set 和 attr_clr 可以同时写同一个属性吗?

不应这样设计。内核语义是先清除再设置,但调用方应让两组位表达清晰的目标状态,避免把同一属性放入两边导致代码审阅困难。

为什么设置只读时返回 EBUSY?

目标挂载仍可能有打开的写入文件,内核会拒绝把这种状态直接切换为只读。先确认写入者和挂载树结构,再决定是等待、迁移工作负载,还是只改更窄的挂载对象。

mount_setattr 和 mount 命令的 -o ro 一样吗?

它们都可能让挂载表现为只读,但一个更明确地表达挂载对象属性,另一个可能同时涉及文件系统参数。需要按挂载对象和 superblock 的边界选择接口。

实际落地时,可以遵循“先定位、再清除、后设置、最后递归”的顺序:先用 dirfd 和 path 明确目标,再零填充 mount_attr,把目标位放进 attr_set,按需使用 attr_clr 和 AT_RECURSIVE,最后针对 errno、命名空间和写入者做失败处理。这样可以把挂载树的批量收紧控制在清晰、可回滚和可审阅的边界内。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>