You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

fanotify监视器所有事件描述符为-1的问题排查求助

Fanotify 事件监控问题修复方案

核心问题分析

  1. 所有事件被识别为目录事件:你使用了FAN_REPORT_FID标志,该标志会禁用metadata->fd的填充,转而通过file handle传递文件信息。原代码错误地依赖metadata->fd,导致出现/proc/self/fd/-1的无效路径错误。
  2. 标志位修改触发EINVAL:FAN_REPORT_FID与部分标志存在兼容性问题,且需内核版本5.1+支持,错误的参数组合会触发"Invalid argument"。
  3. 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,提问作者Егор

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:57:02