第 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 用 |
一、核心概念
本章围绕 5 个核心概念展开:先理解 EA 的本质与命名空间模型,再掌握四组系统调用与 shell 命令,最后关注实现限制。
| 概念 | 定义 + 重要性 | 实现提示 |
|---|---|---|
扩展属性 (EAs) |
与文件 i-node 关联的「名值对」元数据;不依赖文件类型,可附加到任何受支持的文件系统对象上;用于 ACL、文件 capabilities、MIME 类型、版本号等场景。 |
§16.1;EAs 不在 SUSv3 中;Btrfs/ext2/3/4/JFS/Reiserfs/XFS 支持;最常见的 system EA 是 |
命名空间 (namespace) |
EA 名字形如 |
§16.1;命名空间语义决定谁能访问;trusted 命名空间需 |
系统调用族 (set/get/remove/list xattr) |
|
§16.3;与 |
Shell 工具 |
|
§16.1; |
实现限制与挂载选项 |
VFS 限制:名 ≤ 255 字符,值 ≤ 64 KB;ext2/3/4 进一步限制单文件总 EA 名+值 ≤ 一个逻辑块(1024/2048/4096 字节);user EAs 只能放在普通文件/目录上;user EA 不能放在「其他用户拥有 + sticky 位设置」的目录(如 |
§16.2;ext2/3/4 早期内核限制每文件最多 32 个 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 EA 需写权限,读需读权限;ext2/3/4/Reiserfs 须以 |
|
仅 |
|
内核保留;当前主要用于 ACL( |
|
SELinux 等 LSM 使用;保存文件安全标签、可执行文件 capabilities(第 39 章)。 |
|
仅 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:四组系统调用——setxattr、getxattr、removexattr、listxattr——每组各有 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 tfile把user.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
三、关键图表
(本章无独立编号图表)
|
核心系统调用对照表
|
|
命名空间与权限
|
四、思维导图
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 三套
五、重点与易错点
-
EAs 不在 SUSv3 中——是 Linux 特有扩展;BSD 系统的对应物是
extattr(2)、Solaris 9+ 是fsattr(5);写跨 UNIX 平台代码需要抽象层。 -
四类命名空间的核心区分:user(按文件权限)、trusted(需
CAP_SYS_ADMIN)、system(内核)、security(LSM);system 命名空间的 EA 名必须内核显式允许。 -
挂载选项
user_xattr易遗漏:ext2/3/4、Reiserfs 上创建 user EA 前必须mount -o user_xattr,否则返回EOPNOTSUPP。 -
user EA 不能放在符号链接上——因为符号链接的权限固定为
rwxrwxrwx,无法用权限位控制 EA 写入;所以 EAs 直接拒绝符号链接。 -
user EA 与 sticky 位:非特权进程不能给「其他用户拥有 + sticky 位」的目录贴 user EA(防止
/tmp滥用)——这是/tmp不能贴 EA 的根本原因。 -
空字符串值与未定义不同:
setfattr -n user.x file(无-v)把值设为空串;setfattr -x user.x file删除 EA;两者语义不同。 -
listxattr返回的列表不可全信:出于安全考虑,列表可能省略调用者无权访问的 EA 名;后续getxattr仍可能失败。 -
getxattr(path, NULL, 0)探测大小——这是动态确定 EA 值长度的标准做法;但返回的大小可能因并发修改而过时,下一次getxattr仍可能ERANGE。 -
ext2/3/4 的硬性块大小限制——单文件所有 EA 名+值总长 ≤ 一个块(1024/2048/4096 字节);这是最严苛的限制,写大量 EA 的程序必须注意。
-
JFS 还有
os2命名空间——OS/2 兼容性遗留;非 JFS 文件系统不存在该命名空间。 -
「path/l/f 三套」与「stat/lstat/fstat 三套」平行——
l*版不解引用符号链接;处理符号链接本身或需注意解引用时务必用l*版。 -
系统调用错误码
ENODATA——EA 不存在(不是ENOENT);ERANGE——缓冲区过小;EOPNOTSUPP——文件系统或命名空间不支持 EA。 -
EAs 与文件内容无关——EA 改变不影响文件大小、修改时间;修改 EA 本身会更新 i-node 变更时间(
ctime),触发IN_ATTRIB事件(inotify,第 19 章)。 -
跨章衔接:第 17 章 ACL 直接用
system.posix_acl_access/system.posix_acl_defaultEA;第 39 章文件 capabilities 用security.capabilityEA;第 19 章 inotify 监听 EA 变化时会触发IN_ATTRIB事件。