fanotify监视器所有事件描述符为-1的问题排查求助
Fanotify 事件监控问题修复方案
核心问题分析
- 所有事件被识别为目录事件:你使用了
FAN_REPORT_FID标志,该标志会禁用metadata->fd的填充,转而通过file handle传递文件信息。原代码错误地依赖metadata->fd,导致出现/proc/self/fd/-1的无效路径错误。 - 标志位修改触发EINVAL:
FAN_REPORT_FID与部分标志存在兼容性问题,且需内核版本5.1+支持,错误的参数组合会触发"Invalid argument"。 - vim/nano修改触发创建事件:这类编辑器采用原子替换机制——先创建临时文件写入内容,再删除原文件并重命名临时文件,因此会触发
FAN_CREATE而非直接FAN_MODIFY,属于正常行为。
代码修复步骤
1. 调整fanotify初始化参数
移除FAN_REPORT_FID标志,恢复默认的文件描述符传递机制,这样文件事件的metadata->fd会被正确填充:
fanotify_fd = fanotify_init(FAN_CLASS_NOTIF, O_RDONLY | O_LARGEFILE);
2. 新增文件路径获取函数
通过/proc/self/fd/[fd]符号链接获取真实路径,同时注意关闭文件描述符避免泄漏:
char* get_file_path(int fd) { char proc_link[PATH_MAX]; char* real_path = malloc(PATH_MAX); if (!real_path) return NULL; snprintf(proc_link, sizeof(proc_link), "/proc/self/fd/%d", fd); ssize_t len = readlink(proc_link, real_path, PATH_MAX - 1); if (len == -1) { free(real_path); return NULL; } real_path[len] = '\0'; return real_path; }
3. 修改事件处理逻辑
区分文件与目录事件,正确输出路径,并过滤编辑器临时文件:
// 遍历缓冲区中的事件 for (metadata = (struct fanotify_event_metadata*)buffer; FAN_EVENT_OK(metadata, length); metadata = FAN_EVENT_NEXT(metadata, length)) { char* file_path = NULL; // 处理文件事件(fd有效) if (metadata->fd != -1) { file_path = get_file_path(metadata->fd); close(metadata->fd); // 必须关闭,避免文件描述符泄漏 } // 输出事件详情与路径 if (metadata->mask & FAN_MODIFY) { printf(" - 事件: 文件修改 | 路径: %s\n", file_path ? file_path : "未知"); } if (metadata->mask & FAN_CREATE) { // 过滤编辑器临时文件(如swap、tmp文件) if (file_path && strstr(file_path, ".swp") == NULL && strstr(file_path, ".tmp") == NULL) { printf(" - 事件: 文件创建 | 路径: %s\n", file_path); } } if (metadata->mask & FAN_DELETE) { printf(" - 事件: 文件删除 | 路径: %s\n", file_path ? file_path : "未知"); } if (metadata->mask & FAN_CLOSE_WRITE) { printf(" - 事件: 文件写入后关闭 | 路径: %s\n", file_path ? file_path : "未知"); } free(file_path); }
4. 关于编辑器原子替换的说明
vim/nano的行为是为了保证文件完整性,避免写入中断导致文件损坏。如果要将这类操作识别为"修改",可以添加FAN_RENAME事件监控,结合临时文件与原文件的关联关系判断,但实现复杂度较高。
完整修复代码
#define _GNU_SOURCE #include <errno.h> #include <fcntl.h> #include <limits.h> #include <poll.h> #include <stdio.h> #include <stdlib.h> #include <sys/fanotify.h> #include <unistd.h> #include <string.h> #define MONITOR_PATH "/home/user/Desktop/test" char* get_file_path(int fd) { char proc_link[PATH_MAX]; char* real_path = malloc(PATH_MAX); if (!real_path) return NULL; snprintf(proc_link, sizeof(proc_link), "/proc/self/fd/%d", fd); ssize_t len = readlink(proc_link, real_path, PATH_MAX - 1); if (len == -1) { free(real_path); return NULL; } real_path[len] = '\0'; return real_path; } void run() { int fanotify_fd; char buffer[4096]; ssize_t length; // 初始化fanotify,移除FAN_REPORT_FID,使用默认fd传递机制 fanotify_fd = fanotify_init(FAN_CLASS_NOTIF, O_RDONLY | O_LARGEFILE); if (fanotify_fd == -1) { perror("fanotify_init"); exit(EXIT_FAILURE); } // 添加监控,增加FAN_RENAME以捕获编辑器的重命名操作 if (fanotify_mark(fanotify_fd, FAN_MARK_ADD, FAN_OPEN | FAN_MODIFY | FAN_CREATE | FAN_DELETE | FAN_CLOSE_WRITE | FAN_RENAME, AT_FDCWD, MONITOR_PATH) == -1) { perror("fanotify_mark"); exit(EXIT_FAILURE); } printf("监控文件系统事件于 '%s'。按Ctrl+C停止。\n", MONITOR_PATH); struct pollfd fds[] = {{fanotify_fd, POLLIN, 0}}; while (1) { int ret = poll(fds, 1, -1); if (ret == -1) { perror("poll failed"); break; } if (fds[0].revents & POLLIN) { length = read(fanotify_fd, buffer, sizeof(buffer)); if (length == -1) { if (errno == EINTR) continue; perror("read failed"); break; } struct fanotify_event_metadata *metadata = (struct fanotify_event_metadata *)buffer; for (; FAN_EVENT_OK(metadata, length); metadata = FAN_EVENT_NEXT(metadata, length)) { char* file_path = NULL; if (metadata->fd != -1) { file_path = get_file_path(metadata->fd); close(metadata->fd); } if (metadata->mask & FAN_MODIFY) { printf(" - 事件: 文件修改 | 路径: %s\n", file_path ? file_path : "未知"); } if (metadata->mask & FAN_CREATE) { if (file_path && strstr(file_path, ".swp") == NULL && strstr(file_path, ".tmp") == NULL) { printf(" - 事件: 文件创建 | 路径: %s\n", file_path); } } if (metadata->mask & FAN_DELETE) { printf(" - 事件: 文件删除 | 路径: %s\n", file_path ? file_path : "未知"); } if (metadata->mask & FAN_CLOSE_WRITE) { printf(" - 事件: 文件写入后关闭 | 路径: %s\n", file_path ? file_path : "未知"); } if (metadata->mask & FAN_RENAME) { printf(" - 事件: 文件重命名 | 路径: %s\n", file_path ? file_path : "未知"); } free(file_path); } } } if (close(fanotify_fd) == -1) { perror("关闭fanotify文件描述符失败"); } } int main() { run(); return 0; }
内容的提问来源于stack exchange,提问作者Егор
相关产品推荐
相关产品推荐

