Skip to content

Latest commit

 

History

History
115 lines (86 loc) · 4.31 KB

File metadata and controls

115 lines (86 loc) · 4.31 KB

go-fanotify

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_CREATEFAN_DELETEFAN_MOVED_FROMFAN_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

许可证

MIT

Linux 文档