第 16 章 扩展属性 (Extended Attributes)

      +

      核心结论

      • 扩展属性 (EAs):Linux 自 2.6 内核起支持 EAs,允许把任意「名值对」形式的元数据附加到文件 i-node 上;存储与文件内容、权限位彼此独立。

      • 四类命名空间:user(普通进程按文件权限访问)、trusted(需 CAP_SYS_ADMIN)、system(内核使用,目前主要为 ACL)、security(SELinux 文件标签与可执行文件 capabilities);JFS 还支持 os2 命名空间(OS/2 兼容)。

      • 挂载选项:在 ext2/3/4、Reiserfs 上创建 user EA 须用 mount -o user_xattr;若未启用,所有 user 命名空间操作将失败(EOPNOTSUPP)。

      • 系统调用族:每种 EA 操作都提供 path 版l* 版(不解引用符号链接)、f* 版(基于 fd)三套接口,对应 stat/lstat/fstat 的语义切分。

      • 实现限制:VFS 限定 EA 名最长 255 字符、值最长 64 KB;ext2/3/4 进一步将单个文件全部 EA 的名 + 值限制在一个逻辑磁盘块大小内(1024/2048/4096 字节)。

      • 支持的文件系统:Btrfs、ext2/3/4、JFS、Reiserfs、XFS 支持 EAs;Reiserfs 自 2.6.7 起支持;每种文件系统可独立通过内核 File systems 菜单配置。

      本章主旨

      本章介绍 Linux 的扩展属性机制——一种把任意键值元数据挂到 i-node 上的通用能力。EAs 是 Linux 后续诸多特性的底层承载:第 17 章 ACL 用 system.posix_acl_access / system.posix_acl_default 实现,第 39 章文件 capabilities 用 security.capability。读者应掌握四种命名空间语义、setxattr/getxattr/removexattr/listxattr 四组系统调用族、以及大小/数量限制;EAs 不在 SUSv3 中,是 Linux 特有的扩展(BSD 有 extattr(2)、Solaris 9+ 有 fsattr(5))。

      一、核心概念

      本章围绕 5 个核心概念展开:先理解 EA 的本质与命名空间模型,再掌握四组系统调用与 shell 命令,最后关注实现限制。

      概念 定义 + 重要性 实现提示

      扩展属性 (EAs)

      与文件 i-node 关联的「名值对」元数据;不依赖文件类型,可附加到任何受支持的文件系统对象上;用于 ACL、文件 capabilities、MIME 类型、版本号等场景。

      §16.1;EAs 不在 SUSv3 中;Btrfs/ext2/3/4/JFS/Reiserfs/XFS 支持;最常见的 system EA 是 system.posix_acl_access

      命名空间 (namespace)

      EA 名字形如 namespace.name,命名空间负责把 EAs 划分为功能隔离的类别;四类:user、trusted、system、security;user/trusted 允许任意字符串,system 只接受内核许可的名字。

      §16.1;命名空间语义决定谁能访问;trusted 命名空间需 CAP_SYS_ADMIN;system 命名空间为内核保留。

      系统调用族 (set/get/remove/list xattr)

      setxattr/getxattr/removexattr/listxattr 每组都提供 path/l/f 三种版本;path 版解引用符号链接,l* 版不解引用,f* 版基于打开的 fd。

      §16.3;与 stat/lstat/fstat 平行;XATTR_CREATE/XATTR_REPLACE 控制 setxattr 行为;空字符串值合法但不同于「未定义」。

      Shell 工具

      setfattr 设置/修改 EA;getfattr 列出 EA(默认只看 user 命名空间);getfattr -m - 显示所有命名空间。

      §16.1;getfattr -m 'pattern' 用正则匹配名字;setfattr -x name file 删除 EA。

      实现限制与挂载选项

      VFS 限制:名 ≤ 255 字符,值 ≤ 64 KB;ext2/3/4 进一步限制单文件总 EA 名+值 ≤ 一个逻辑块(1024/2048/4096 字节);user EAs 只能放在普通文件/目录上;user EA 不能放在「其他用户拥有 + sticky 位设置」的目录(如 /tmp)。

      §16.2;ext2/3/4 早期内核限制每文件最多 32 个 EA;mount -o user_xattr 是创建 user EA 的前提。

      二、详细笔记

      16.1 扩展属性概述

      What:扩展属性(Extended Attributes, EAs)是 Linux 2.6 起引入的机制,允许把任意的「名值对」附加到文件 i-node;可用于 ACL、文件 capabilities、MIME 类型、版本号等元数据。

      Why:传统 i-node 只保存 owner、group、权限位、时间戳、大小等固定字段,无法承载应用特定的元数据。EAs 把这部分「自由扩展」的需求标准化——同一套 API 可用于 ACL(系统级)、MIME(应用级)、安全标签(安全模块)等多种场景,避免每个子系统发明自己的文件。

      How:EA 名形如 namespace.name,命名空间用于功能切分:

      命名空间 谁能访问 / 用途

      user

      非特权进程可访问;写 user EA 需写权限,读需读权限;ext2/3/4/Reiserfs 须以 mount -o user_xattr 挂载。

      trusted

      CAP_SYS_ADMIN 可访问;用于受信任进程保存敏感元数据。

      system

      内核保留;当前主要用于 ACL(system.posix_acl_accesssystem.posix_acl_default)。

      security

      SELinux 等 LSM 使用;保存文件安全标签、可执行文件 capabilities(第 39 章)。

      os2

      仅 JFS;兼容 OS/2 文件系统 EAs。

      When:需要保存「文件内容之外、应用相关的元数据」时——文件 ACL、可执行 capabilities、版本号、字符编码、MIME 类型、自定义应用标签。

      Example:用 setfattr/getfattr 创建并查看 user EA:

      $ setfattr -n user.x -v "The past is not dead." tfile
      $ setfattr -n user.y -v "In fact, it's not even past." tfile
      $ getfattr -d tfile
      # file: tfile
      user.x="The past is not dead."
      user.y="In fact, it's not even past."

      16.2 实现细节与限制

      What:VFS 强制 EA 名 ≤ 255 字符、值 ≤ 64 KB;各文件系统可施加更严格的限制。

      Why:内核需为 EA 分配磁盘空间;放任会导致文件系统元数据膨胀、单文件 i-node 信息过大。

      How

      文件系统 限制

      ext2/3/4

      单文件全部 EA 名+值总字节 ≤ 一个逻辑块(1024/2048/4096 字节)

      JFS

      ≤ 128 KB

      Btrfs

      受块大小约束,实践中类似 ext4

      XFS / Reiserfs

      受 VFS 64 KB 限制

      user EA 还受对象类型限制——只能放在普通文件/目录上;不能放在符号链接、设备、FIFO、套接字上。原因:

      • 符号链接权限固定为 rwxrwxrwx,无法用权限位控制 user EA 的写入。

      • 设备、FIFO、套接字的权限位用于控制 I/O 访问,挪用给 EA 会与 I/O 语义冲突。

      此外,「非特权进程不能给 其他用户拥有 + sticky 位设置 的目录添加 user EA」——这条规则防止用户在 /tmp 等共享目录乱贴 EA。

      When:用 setfattr 设置前先用 df/tune2fs -l 确认块大小;批量 EAs 时计算总大小是否超限。

      Example:ext4 默认块 4096 字节,所有 EA 名+值总长须 < 4096 - 少量头部开销。

      16.3 操纵 EAs 的系统调用

      What:四组系统调用——setxattrgetxattrremovexattrlistxattr——每组各有 path/l/f 三种变体。

      Why:完整覆盖「按路径 / 不解引用符号链接 / 基于 fd」三种使用模式,与 stat/lstat/fstat 的设计哲学一致。

      How

      // 摘自《The Linux Programming Interface》第 16 章
      #include <sys/xattr.h>
      
      // 设置/创建 EA;flags = 0 / XATTR_CREATE / XATTR_REPLACE
      int setxattr(const char *pathname, const char *name,
                   const void *value, size_t size, int flags);
      
      // 读取 EA;size 为缓冲区长度,size=0 时返回所需大小但不读值
      ssize_t getxattr(const char *pathname, const char *name,
                       void *value, size_t size);
      
      // 删除 EA;EA 不存在时返回 ENODATA
      int removexattr(const char *pathname, const char *name);
      
      // 列出所有 EA 名;size 为缓冲区长度,size=0 时返回所需大小
      ssize_t listxattr(const char *pathname, char *list, size_t size);

      flags 参数控制 setxattr 的行为:

      • 0:默认行为——已存在则替换,不存在则创建。

      • XATTR_CREATE:要求 EA 尚不存在;已存在则返回 EEXIST

      • XATTR_REPLACE:要求 EA 已存在;不存在则返回 ENODATA

      getxattr 错误码:

      • ENODATA:文件无指定名字的 EA。

      • ERANGE:缓冲区过小。

      • 调用约定:size=0 时不读值,仅返回所需字节数(用于探测大小)。

      listxattr 错误约定:返回的列表中可能省略调用者无权访问的 EA 名(如未特权的 listxattr 通常会省略 trusted 命名空间);但「可能」意味着并非强制——后续用 getxattr 取值仍可能失败。

      When

      • 路径名稳定、fd 不可得——用 path 版。

      • 需要操作符号链接本身——用 l* 版。

      • 已打开 fd、需要原子操作或避免 TOCTOU——用 f* 版。

      Example:安全地读取 EA(先探测大小):

      // 摘自《The Linux Programming Interface》第 16 章
      char name[] = "user.x";
      char buf[10000];
      ssize_t len;
      
      // 第一步:探测大小
      len = getxattr(pathname, name, NULL, 0);
      if (len == -1) { /* ENODATA 或其他错误 */ }
      if (len > sizeof(buf)) { /* 缓冲区不足,扩容 */ }
      
      // 第二步:实际读取(仍可能因并发失败)
      len = getxattr(pathname, name, buf, sizeof(buf));
      if (len == -1) { /* 处理 */ }

      16.4 xattr_view 示例程序

      What:第 16 章 Listing 16-1 的 xattr_view 程序:列出文件的所有 EA 名与值。

      Why:演示 listxattr + 循环 getxattr 的完整模式——这是读取「全部 EA」的标准范式。

      How:核心循环:

      // 摘自《The Linux Programming Interface》第 16 章(Listing 16-1, 精简)
      #define XATTR_SIZE 10000
      
      for (j = optind; j < argc; j++) {
          listLen = listxattr(argv[j], list, XATTR_SIZE);
          /* list 中的字符串以 '\0' 分隔 */
          for (ns = 0; ns < listLen; ns += strlen(&list[ns]) + 1) {
              valueLen = getxattr(argv[j], &list[ns], value, XATTR_SIZE);
              if (valueLen == -1)
                  printf("couldn't get value");
              else
                  printf("value=%.*s", (int) valueLen, value);
          }
      }

      注意点:

      • list 缓冲区保存的是「以 \0 分隔的字符串序列」,遍历时按 strlen 步进。

      • printf("%.*s", (int) valueLen, value) 用「精确长度」打印——避免把 buffer 中可能残留的字节当成字符串内容。

      • -x 选项把值以十六进制打印,用于二进制 EA 值。

      When:写需要「全量枚举 + 处理每个 EA」的工具——比如备份系统、安全审计工具。

      Example:运行 xattr_view

      $ ./xattr_view tfile
      tfile:
              name=user.x; value=The past is not dead.
              name=user.y; value=In fact, it's not even past.

      16.5 EA 名值的语义细节

      What:空字符串值与「未定义」不同;getfattr 默认只显示 user 命名空间;EA 列表可能被文件系统过滤。

      Why:这些细节是排查 EA 类 bug 的常见来源。

      How

      • 空字符串值 ≠ 未定义setfattr -n user.x tfileuser.x 值设为空字符串(合法);setfattr -x user.y tfile 删除 user.y(未定义)。

      • 默认命名空间getfattr 默认只看 user;getfattr -m - file 显示所有命名空间;getfattr -m '^system\.' 用正则筛选。

      • 列表过滤:列表可能省略无权限访问的 EA;不要假设「列表里有 → 一定能 getxattr 取到」。

      • 大块传输:单次 read() 可返回多个事件,遍历时按 sizeof(struct inotify_event) + len 步进(第 19 章)——同理,listxattr 返回的 buffer 也是紧凑布局。

      When:调试 EA 问题时检查三件事——挂载选项(mount | grep user_xattr)、文件系统类型、getcap/getfattr 输出格式。

      Example:在 /tmp 上创建 user EA 失败(sticky 位 + 其他用户拥有):

      // 用户 maigi 在 /tmp 创建 test 后试图贴 user EA
      int fd = open("/tmp/test", O_RDWR | O_CREAT, 0644);
      setxattr("/tmp/test", "user.x", "v", 2, 0);
      // 返回 EACCES —— 因为 /tmp 有 sticky 位且不属于 maigi

      三、关键图表

      (本章无独立编号图表)

      核心系统调用对照表
      调用 用途 / 关键参数

      setxattr(path, name, value, size, flags)

      设置/创建 EA;flags=0 / XATTR_CREATE / XATTR_REPLACE

      lsetxattr / fsetxattr

      不解引用符号链接 / 基于 fd

      getxattr(path, name, value, size)

      读取 EA 值;size=0 探测大小

      lgetxattr / fgetxattr

      同上两个变体

      removexattr(path, name)

      删除 EA;不存在返回 ENODATA

      lremovexattr / fremovexattr

      同上两个变体

      listxattr(path, list, size)

      列出所有 EA 名

      llistxattr / flistxattr

      同上两个变体

      命名空间与权限
      命名空间 谁能访问

      user

      进程按文件 owner/group/other 权限访问

      trusted

      CAP_SYS_ADMIN

      system

      内核保留(ACL 等)

      security

      LSM(SELinux)使用

      os2

      JFS 专属,OS/2 兼容

      四、思维导图

      mindmap
        root((第 16 章 扩展属性))
          命名空间
            user 受文件权限约束
            trusted CAP_SYS_ADMIN
            system ACL 内核
            security LSM
            os2 JFS 专属
          系统调用
            setxattr lsetxattr fsetxattr
            getxattr lgetxattr fgetxattr
            removexattr lremovexattr fremovexattr
            listxattr llistxattr flistxattr
          标志位
            XATTR_CREATE 强制新建
            XATTR_REPLACE 强制替换
            size 0 探测大小
          实现限制
            名 255 字符
            值 64 KB
            ext 块大小
            JFS 128 KB
          挂载
            mount user_xattr
            ext234 Reiserfs 必需
          Shell 工具
            setfattr
            getfattr
            getfattr m 正则
          EA 语义
            空字符串 合法
            默认 user 命名空间
            listxattr 过滤
            系统调用 path l f 三套

      五、重点与易错点

      1. EAs 不在 SUSv3 中——是 Linux 特有扩展;BSD 系统的对应物是 extattr(2)、Solaris 9+ 是 fsattr(5);写跨 UNIX 平台代码需要抽象层。

      2. 四类命名空间的核心区分:user(按文件权限)、trusted(需 CAP_SYS_ADMIN)、system(内核)、security(LSM);system 命名空间的 EA 名必须内核显式允许。

      3. 挂载选项 user_xattr 易遗漏:ext2/3/4、Reiserfs 上创建 user EA 前必须 mount -o user_xattr,否则返回 EOPNOTSUPP

      4. user EA 不能放在符号链接上——因为符号链接的权限固定为 rwxrwxrwx,无法用权限位控制 EA 写入;所以 EAs 直接拒绝符号链接。

      5. user EA 与 sticky 位:非特权进程不能给「其他用户拥有 + sticky 位」的目录贴 user EA(防止 /tmp 滥用)——这是 /tmp 不能贴 EA 的根本原因。

      6. 空字符串值与未定义不同setfattr -n user.x file(无 -v)把值设为空串;setfattr -x user.x file 删除 EA;两者语义不同。

      7. listxattr 返回的列表不可全信:出于安全考虑,列表可能省略调用者无权访问的 EA 名;后续 getxattr 仍可能失败。

      8. getxattr(path, NULL, 0) 探测大小——这是动态确定 EA 值长度的标准做法;但返回的大小可能因并发修改而过时,下一次 getxattr 仍可能 ERANGE

      9. ext2/3/4 的硬性块大小限制——单文件所有 EA 名+值总长 ≤ 一个块(1024/2048/4096 字节);这是最严苛的限制,写大量 EA 的程序必须注意。

      10. JFS 还有 os2 命名空间——OS/2 兼容性遗留;非 JFS 文件系统不存在该命名空间。

      11. 「path/l/f 三套」与「stat/lstat/fstat 三套」平行——l* 版不解引用符号链接;处理符号链接本身或需注意解引用时务必用 l* 版。

      12. 系统调用错误码 ENODATA——EA 不存在(不是 ENOENT);ERANGE——缓冲区过小;EOPNOTSUPP——文件系统或命名空间不支持 EA。

      13. EAs 与文件内容无关——EA 改变不影响文件大小、修改时间;修改 EA 本身会更新 i-node 变更时间(ctime),触发 IN_ATTRIB 事件(inotify,第 19 章)。

      14. 跨章衔接:第 17 章 ACL 直接用 system.posix_acl_access / system.posix_acl_default EA;第 39 章文件 capabilities 用 security.capability EA;第 19 章 inotify 监听 EA 变化时会触发 IN_ATTRIB 事件。