English | 中文
go-fanotify 是一个专注于 Linux fanotify 传统文件描述符事件的 Go 库。它使用一个 mount mark 监听整个挂载点,再在用户空间过滤目标目录,适合监听较大目录树中的打开、访问、修改和关闭事件。
gofanotify.FAN_OPEN
gofanotify.FAN_OPEN_EXEC // Linux 5.0+
gofanotify.FAN_ACCESS
gofanotify.FAN_MODIFY
gofanotify.FAN_CLOSE_WRITE
gofanotify.FAN_CLOSE_NOWRITE
gofanotify.FAN_CLOSE // FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE当前 v1 是 Legacy FD 模式,不支持 FAN_CREATE、FAN_DELETE、FAN_MOVED_FROM、FAN_MOVED_TO 或其他要求 FID 信息记录的事件。传入不支持的 mask 会返回 ErrUnsupportedMask,不会静默降级或漏报。
//go:build linux
package main
import (
"errors"
"log"
gofanotify "github.com/zemul/go-fanotify"
)
func main() {
notifier, err := gofanotify.New()
if err != nil {
log.Fatal(err)
}
defer notifier.Close()
err = notifier.AddWatch(
[]string{"/data/uploads"},
gofanotify.FAN_MODIFY|gofanotify.FAN_CLOSE_WRITE,
)
if err != nil {
log.Fatal(err)
}
for event := range notifier.ReadEvents() {
if event.Err != nil {
if errors.Is(event.Err, gofanotify.ErrQueueOverflow) {
log.Printf("fanotify 丢失了事件,需要重新扫描: %v", event.Err)
continue
}
log.Printf("fanotify 错误: %v", event.Err)
continue
}
log.Printf("path=%s pid=%d mask=%#x", event.Path, event.PID, event.Mask)
}
}首次调用 ReadEvents 会启动读取 goroutine;重复调用返回同一个 channel。使用 RemoveWatch 可停止指定路径的事件投递。Close 会及时停止读取、关闭尚未处理的事件 fd,并关闭 channel;重复调用 Close 是安全的。
| 环境 | Legacy FD 事件 | 说明 |
|---|---|---|
| Linux 4.19 及以上 | Legacy 核心功能支持目标 | 发布版本应在代表性内核上运行集成测试 |
| Linux 5.0 及以上 | FAN_OPEN_EXEC |
更老的内核会拒绝这个事件 mask |
| Linux 2.6.37–4.18 | 尽力兼容 | fanotify API 已存在,但项目不承诺持续测试这些内核 |
Linux 内核未启用 CONFIG_FANOTIFY |
不支持 | fanotify_init 返回 ENOSYS |
| Docker/Kubernetes | 取决于宿主机和权限 | 容器共享宿主机内核,通常默认缺少所需 capability |
| macOS/Windows | 不支持运行 | 可运行纯逻辑测试和进行 Linux 交叉编译 |
Legacy 模式使用 FAN_MARK_MOUNT,调用进程通常需要 CAP_SYS_ADMIN。是否支持还取决于宿主机内核配置、容器安全策略和文件系统。
- 可以监听同一挂载点下的多个目录,并为每个目录指定不同的事件 mask。
- 监听路径必须存在;符号链接会在注册时解析为实际路径。
Event.Path来自/proc/self/fd/<fd>,代表读取事件时内核提供的对象路径。- 收到
ErrQueueOverflow表示内核队列已经溢出并丢失事件;应用通常需要重新扫描状态。 AddWatch对路径列表逐项生效。如果中途返回错误,错误前的路径已经成功注册。RemoveWatch可重复调用,即使监听路径或符号链接已经不存在也能移除。由于 mount mark 可能被多个路径共享,它会立即停止用户态投递,但内核 mark 会保留到Close。RemoveWatch返回前已经进入 channel 的事件仍可能被读取到。FAN_OPEN_EXEC报告execve等直接执行所产生的打开事件。执行解释型脚本时会报告解释器,但脚本文件本身不一定产生FAN_OPEN_EXEC。
macOS 和普通开发环境可以运行:
go test -race ./...
GOOS=linux GOARCH=amd64 go build ./...真实 fanotify 集成测试必须在 Linux 上运行,并需要 mount mark 权限:
sudo go test -tags=integration ./...集成测试会验证真实关闭事件、移除监听、channel 关闭,以及持续出现不匹配事件时 Close 仍能及时返回;测试内核支持时还会验证 FAN_OPEN_EXEC。