inotify_add_watch默认只监控指定路径本身,不递归子目录,因inotify机制设计上非递归;需手动对新创建/移入的子目录再次调用inotify_add_watch并管理watch descriptor。

inotify_add_watch 为什么只监控一级目录,子目录不生效
因为 inotify_add_watch 默认不递归。它只监听你显式传入的路径,哪怕那个路径是目录,也不会自动包含其子项。Linux inotify 本身就不支持递归监控——这是设计限制,不是用法错误。
实际做法是:先用 inotify_add_watch 监控目标目录,收到 IN_CREATE 事件后,判断新建项是否为目录(可通过 stat() 检查 st_mode),若是,再对它调用一次 inotify_add_watch;同理,收到 IN_MOVED_TO 且是目录时也要补加。删除时记得用 inotify_rm_watch 清理对应 wd,否则会泄漏 watch descriptor。
- 每个子目录需独立
wd,wd是进程级整数,总量受/proc/sys/fs/inotify/max_user_watches限制 - 不要在事件回调里直接递归遍历并批量 add_watch —— 可能触发竞态,建议把“待监控的新目录”暂存队列,由主循环统一处理
-
IN_MOVED_TO和IN_CREATE都可能带目录,但IN_MOVED_TO更可靠(mv 目录不会触发IN_CREATE)
read() 返回 0 或 -1 时怎么判断是缓冲区空还是出错
inotify 的 fd 是阻塞式(默认),read() 在没事件时会挂起;但如果设了 O_NONBLOCK,就可能返回 -1 并置 errno = EAGAIN 或 EWOULDBLOCK。返回 0 表示 inotify 实例被关闭(比如另一线程调了 close()),这在多线程中容易被忽略。
正确检查逻辑必须区分三类情况:
立即学习“C++免费学习笔记(深入)”;
- 返回值 > 0:有事件,按
struct inotify_event头部长度逐个解析(注意len字段,事件名是变长尾部) - 返回值 == 0:立即退出事件循环,清理资源 —— 这是 inotify 实例已销毁的明确信号
- 返回值 == -1:
errno == EAGAIN || errno == EWOULDBLOCK→ 无事件,继续轮询;其他 errno(如EBADF、ENOMEM)需记录并处理
别用 while (read(...) > 0) 硬循环,漏掉 0 和 -1 会导致卡死或内存越界读。
Linux 安全审计工具,检查 SSH 配置、开放/监听端口、防火墙规则(ufw/iptables/nftables)、失败登录尝试、sudoers 权限...
IN_ATTRIB 事件太多,怎么过滤掉 chmod/chown 导致的干扰
IN_ATTRIB 会在文件权限、所有者、时间戳(尤其是 utimensat() 触发的 st_ctime 更新)变化时触发,而编辑器保存常伴随 chmod 或 touch,导致大量无效事件。
解决办法不是禁用 IN_ATTRIB,而是结合事件中的 mask 位和文件元数据做二次过滤:
- 只关注
mask & IN_ATTRIB且mask & IN_ISDIR == 0(排除目录属性变更) - 对事件里的
name调用stat(),比较st_mtime是否变化(修改内容通常改 mtime,chmod 只动 ctime) - 更轻量的做法:记录上次已知的
st_ctime和st_mtime,仅当 mtime 变化才视为“真实修改”
注意 stat() 本身有开销,高频场景建议用 inotify_event.mask 中的 IN_CLOSE_WRITE 替代 —— 大多数写操作最终都会触发它,且它不依赖外部系统调用。
多线程调用 inotify 时,event 结构体的 name 字段为什么总乱码
struct inotify_event 的 name 是紧贴在结构体后的裸字节数组,没有终止符。如果直接当 C 字符串用(比如 printf("%s", event->name)),会一直打印到内存里下一个 \0,结果就是乱码或崩溃。
- 必须用
event->len截断:std::string name(event->name, event->len)或手动 memcpy + 添加 \0 - 如果事件是针对 inotify 实例根路径(即监控的是 /tmp/dir,事件 name 为空),
event->len == 0,此时event->name不可用,直接跳过字符串处理 - 多线程下还要注意:同一
inotify_fd可被多个线程 read,但事件缓冲区是共享的,必须用互斥锁保护 read + 解析全过程,否则event->len和后续name内存可能被撕裂
最稳妥的方式是单线程 read + 分发事件对象(含深拷贝的 name),避免任何裸指针跨线程传递。

















