Linux fanotify 怎样读取 FID 事件定位文件对象
来源:17golang原创
时间:2026-11-01 10:08:16 126浏览 收藏
Linux fanotify 默认更像一个“文件系统事件通知队列”:应用读取事件,看到被访问的对象,然后关闭事件里提供的文件描述符。启用 FAN_REPORT_FID 后,定位对象的方式会改变,事件会附带 FAN_EVENT_INFO_TYPE_FID 信息记录,其中包含文件系统标识 fsid 和不透明的 file handle。
因此,读取 FID 事件的关键不是把 metadata->fd 强行当成普通文件描述符,而是先按 event_len 遍历一条事件,再从元数据后面解析附加记录。官方资料:https://man7.org/linux/man-pages/man7/fanotify.7.html。
想用 FID 定位文件对象,初始化时加入FAN_REPORT_FID,读取时检查FAN_EVENT_INFO_TYPE_FID,保存fsid + file handle这组身份信息;目录项变更还要根据需要增加目录和目标对象报告标志。
FAN_REPORT_FID 让事件携带对象身份
普通 fanotify 事件常常通过 metadata->fd 让监听器访问被监控的文件或目录。这个 fd 是事件处理资源,读取应用需要在处理完后关闭它。FID 模式则面向“对象身份”而不是“暂时打开的对象”:metadata->fd 可能是 FAN_NOFD,真正有用的信息放在紧随通用元数据之后的可变长度记录里。
FAN_REPORT_FID 对应的记录类型是 FAN_EVENT_INFO_TYPE_FID。它的 fsid 用来表示对象所在的文件系统,file handle 是文件系统返回的不透明句柄。两者应当一起保存,不能只保存句柄而丢掉 fsid,因为句柄的含义依赖它所在的文件系统。

先用通知模式初始化 fanotify
如果目标只是记录访问、打开、创建或删除等事件,先使用默认的 FAN_CLASS_NOTIF。它只提供通知,不负责在访问发生前作出允许或拒绝决定。把 FAN_REPORT_FID 加到初始化标志中后,再通过 fanotify_mark 指定要监控的 inode、挂载点或目录。
#include#include #include #include int open_fid_notifier(int watch_fd) { // 中文注释:NOTIF 只接收通知;FID 让事件带上文件句柄信息。 unsigned int flags = FAN_CLASS_NOTIF | FAN_REPORT_FID; int fan_fd = fanotify_init(flags, O_RDONLY | O_CLOEXEC); if (fan_fd == -1) { // 中文注释:初始化失败时保留 errno,调用方可以记录权限或内核配置原因。 perror("fanotify_init"); return -1; } // 中文注释:这里只标记一个 inode,避免示例把监控范围误扩大到整个文件系统。 uint64_t mask = FAN_OPEN | FAN_ACCESS | FAN_ATTRIB; if (fanotify_mark(fan_fd, FAN_MARK_ADD, mask, AT_FDCWD, "/var/lib/app")) { // 中文注释:标记失败必须关闭已经创建的 fanotify fd,避免资源泄漏。 perror("fanotify_mark"); close(fan_fd); return -1; } // 中文注释:返回的 fd 交给事件循环,后续通过 read 读取整批事件。 (void)watch_fd; return fan_fd; }
示例把监控范围写成单个目录只是为了突出初始化关系。实际部署时要根据文件系统类型、权限和事件量选择标记范围;FID 句柄不是路径字符串,也不能直接拿来做字符串拼接。
按 event_len 遍历附加 FID 记录
一次 read 可能返回多条事件,所以不能只解析缓冲区开头的一个结构。外层循环使用 FAN_EVENT_OK 判断剩余空间,使用 FAN_EVENT_NEXT 跳到下一条事件。对于开启 FID 的组,event_len 还包含一个或多个附加信息记录。
#include#include static void read_fid_records(int fan_fd) { char buffer[4096]; ssize_t bytes = read(fan_fd, buffer, sizeof(buffer)); if (bytes fd)。 if (meta->fd != FAN_NOFD) { close(meta->fd); } char *info_pos = (char *)meta + meta->metadata_len; char *event_end = (char *)meta + meta->event_len; while (info_pos + sizeof(struct fanotify_event_info_header) len len > event_end) { break; } if (header->info_type == FAN_EVENT_INFO_TYPE_FID) { struct fanotify_event_info_fid *fid = (struct fanotify_event_info_fid *)info_pos; // 中文注释:fsid 与可变长度 file handle 是对象身份的组合,不把句柄当路径。 consume_fsid_and_handle(&fid->fsid, &fid->handle); } info_pos += header->len; } // 中文注释:宏会按 event_len 移动指针并扣减剩余字节数。 meta = FAN_EVENT_NEXT(meta, remaining); } }
这个片段把 consume_fsid_and_handle 留给业务层实现,因为不同文件系统、权限模型和路径解析策略并不相同。要点有三个:先检查记录长度,再判断 info_type,最后把 fsid 与句柄作为一组数据交给后续处理。
目录项变更为什么还需要 TARGET_FID
只报告一个 FID 时,记录通常能说明“哪个对象关联了这次事件”。但对于目录中的创建、删除、移动和重命名,读者往往还想知道“哪个父目录里的哪个子对象发生了变化”。这时可以组合 FAN_REPORT_DIR_FID 和名称报告;如果还要拿到被创建、删除或移动的子对象句柄,则需要把 FAN_REPORT_TARGET_FID 一并纳入兼容性设计。

这类事件可能携带父目录的 FAN_EVENT_INFO_TYPE_DFID、带名称的目录记录,以及目标对象的 FAN_EVENT_INFO_TYPE_FID。解析代码不要假设附加记录只有一条,也不要依赖记录出现的固定顺序;应当按 header->len 逐条读取并根据 info_type 分派。
不要把 FID 事件当成权限事件
| 关注点 | 普通通知 / FID 读取 | 权限事件 |
|---|---|---|
| 主要目的 | 知道某个文件对象发生了什么 | 在访问前决定允许或拒绝 |
| 初始化类别 | FAN_CLASS_NOTIF | FAN_CLASS_CONTENT 或 FAN_CLASS_PRE_CONTENT |
| 对象信息 | 普通 fd,或 fsid + file handle | 通常需要处理权限事件中的 fd 并写回响应 |
| 处理动作 | 读取、解析、保存或关闭资源 | 向 fanotify fd 写入 FAN_ALLOW 或 FAN_DENY |
非特权监听器的能力也有限。man-pages 说明,在较新的内核上,缺少 CAP_SYS_ADMIN 的调用者可以创建受限的 fanotify 组,但不能请求无限队列、不能使用需要权限事件的通知类别,并且需要使用文件句柄报告方式,标记范围也受到限制。因此,FID 模式常用于不希望依赖临时打开 fd 的对象识别场景,但它不等于绕过权限控制。
最小复查:确认读的是记录,不是路径
部署前可以先查看本机内核提供的 man-pages,确认要使用的标志、内核版本边界和能力要求。这个动作只是环境复查,不会把 FID 句柄变成可直接展示的路径。
# 中文注释:查看当前系统安装的 fanotify API 说明。 man 7 fanotify # 中文注释:查看 fanotify_init 的标志与能力限制。 man 2 fanotify_init
如果日志系统需要稳定关联对象,建议保存事件掩码、进程标识、fsid、句柄原始字节和记录类型;如果只保存一个临时 fd 或一条当时的路径,后续遇到重命名、移动或卸载挂载点时,很容易失去关联。
常见问题
启用 FAN_REPORT_FID 后为什么 metadata.fd 是 FAN_NOFD?
这是 FID 模式的正常表现。对象身份放在附加的 file handle 记录中,读取程序应检查 info_type,而不是把 FAN_NOFD 当成读取失败。
file handle 能直接当成文件路径吗?
不能。它是文件系统相关的不透明句柄,需要结合 fsid 和合适的内核接口处理。文章中的结构解析只能得到对象身份材料,不应把句柄字节直接拼成路径。
只监控目录创建时为什么还看不到子对象句柄?
目录项事件需要区分父目录、名称和目标对象。检查是否同时启用了目录 FID、名称报告以及目标 FID;仅有基础的 FAN_REPORT_FID 不一定会附带被创建或移动的子对象记录。
什么时候继续使用普通事件 fd?
如果业务只需要短时间访问事件对象内容,并且不要求跨重命名或卸载保持对象身份,普通通知 fd 更简单。只有当对象身份、目录项关系或受限监听环境成为重点时,再引入 FID 记录解析。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
277 收藏
-
126 收藏
-
300 收藏
-
138 收藏
-
198 收藏
-
146 收藏
-
477 收藏
-
296 收藏
-
451 收藏
-
133 收藏
-
349 收藏
-
237 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习