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

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_REPORT_FID 事件中元数据、FID 记录、fsid 和文件句柄的静态结构关系图
图1:结构说明图,展示 FAN_REPORT_FID 事件中通用元数据与文件对象句柄的关系。

先用通知模式初始化 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 一并纳入兼容性设计。

普通 fanotify 事件文件描述符与 FAN_REPORT_FID 文件句柄记录的静态对比图
图2:边界说明图,对比普通事件文件描述符与 FID 信息记录的读取方式。

这类事件可能携带父目录的 FAN_EVENT_INFO_TYPE_DFID、带名称的目录记录,以及目标对象的 FAN_EVENT_INFO_TYPE_FID。解析代码不要假设附加记录只有一条,也不要依赖记录出现的固定顺序;应当按 header->len 逐条读取并根据 info_type 分派。

不要把 FID 事件当成权限事件

关注点普通通知 / FID 读取权限事件
主要目的知道某个文件对象发生了什么在访问前决定允许或拒绝
初始化类别FAN_CLASS_NOTIFFAN_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 记录解析。

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