Linux C 使用 inotify 监控文件或目录变化

您所在的位置:网站首页 操作变动 Linux C 使用 inotify 监控文件或目录变化

Linux C 使用 inotify 监控文件或目录变化

2024-07-11 13:56| 来源: 网络整理| 查看: 265

1 运行环境 操作系统:Ubuntu 18 2 inotify 简介

inotify 是一个 Linux 内核特性(监视文件系统事件),它用于监控文件系统,比如删除、读、写操作等,当发生对应事件时,则会触发 inotify。当监控目录时,与该目录自身以及该目录下面的文件都会被监控,其上有事件发生时都会通知给应用程序

inotify 监控机制为非递归,若想监控整个目录子树内的事件,则需对该树中的每个目录发起 inotify_add_watch() 调用

使用 inotify:创建一个文件描述符,附加一个或多个监视器(一个监视器 是一个路径和一组事件),然后使用 read() 方法从描述符获取事件信息。read() 并不会用光整个周期,它在事件发生之前是被阻塞的。

因为 inotify 通过传统的文件描述符工作,可使用 select(),poll(),epoll() 以及由信号驱动的 I/O 来监控 inotify 文件描述符

要使用 inotify,必须具备一台带有 2.6.13 或更新内核的 Linux 机器(以前的 Linux 内核版本使用更低级的文件监控器 dnotify)。如果您不知道内核的版本,请转到 shell,输入 uname -a

3 inotify API 3.1 inotify_init

创建一个 inotify 实例并返回一个引用 inotify 实例的文件描述符

函数原型:

#include int inotify_init(void);

返回值:

成功:该函数的返回值为一个文件描述符,该文件描述符所指代的文件中将会保存所监控的 文件/目录 所发生的 事件集。

失败:返回 -1,并且将 errno 设置为对应错误。

使用及解释:

int fd = inotify_init();

fd 为所指的 inotify 实例的 监控列表,系统调用 inotify_add_watch() 可以向该 fd 追加 新的监控项。

3.2 inotify_add_watch

针对 fd 所指的 inotify 实例的 监控列表 追加 新的监控项。

函数原型:

#include int inotify_add_watch(int fd,const char *pathname,uint32_t mask);

返回值:

成功:返回值为一个用于 唯一指代此 监控项 的描述符

失败:返回值 < 0 ,则代表添加该监控项失败,需要检测 pathname 是否有可读权限,是否存在,系统的监控队列是否已满等

参数:

pathname 为想要创建的监控项所对应的文件,特别注意调用该接口必须要对该文件有读权限,该函数只对文件做一次检查,如果在监控时修改了所监控的文件读权限,则不会影响继续监控此文件

mask 为一位掩码,针对 pathname 定义了想要监控的事件,此函数的返回值为一个用于唯一指代此监控项的描述符(将在 4 inotify 事件 中介绍)

4 inotify 常用监控事件

IN_ACCESS:文件 被访问时 触发事件,例如 read,execve

IN_ATTRIB:文件属性 发生变化 触发事件。例如 权限 chmod,时间戳 setxattr,链接数 link 等

IN_CLOSE_WRITE:一个文件被打开 写入操作结束,文件被关闭时 触发事件

IN_CLOSE_NOWRITE:一个文件被打开 没有任何写操作,文件被关闭时 触发事件

IN_CREATE:在监控列表下 创建一个文件或目录 时 触发事件,例如 open O_CREAT,mkdir 等

IN_DELETE:在监控列表下 文件或目录 被删除时 触发事件

IN_DELETE_SELF:监控文件或目录 本身被删除时 触发事件,而且,如果一个文件或目录被移到其它地方,比如使用 mv 命令,也会触发该事件,因为 mv 命令本质上是拷贝一份当前文件,然后删除当前文件的操作。此时监控终止,并且将收到一个 IN_IGNORED 事件。

IN_MODIFY:文件 被修改时 触发事件,例如:有写操作(write)或者文件内容被清空(truncate)操作。不过需要注意的是,IN_MODIFY 可能会连续触发多次。

IN_MODIFY_SELF:所监控的文件或目录本身 发生移动时 触发事件

IN_MOVED_FROM:将文件或目录 移除 监控列表 触发事件

IN_MOVED_TO:将文件或目录 移入 监控列表 触发事件

IN_OPEN:文件被打开 触发事件

IN_ALL_EVENTS:监控所有事件

IN_MOVE:IN_MOVED_FROM | IN_MOVED_TO 事件的统称

5 存储 inotify 事件 结构体 struct inotify_event

将 监控项 在 监控列表 中登记后,应用程序可以用 read() 从 inotify 的文件描述符 中读取事件以判定发生了那些事件。若读取之时还没有发生任何事件,则 read() 会阻塞,直至有事件产生。事件发生后,每次调用 read() 会返回一个缓存区,内含一个或多个如下类型的结构体:

struct inotify_event { int wd; // 指向发生事件的监控项的文件描述符,该字段值由之前对 inotify_add_watch() 的调用返回。用于区分是哪个监控项触发了该事件 uint32_t mask; // inotify 事件的一位掩码 uint32_t cookie; // 唯一的关联 inotify 事件的值 uint32_t len; // 分配给 name 的字节数 char name[]; // 标识触发该事件的文件名 };

注意:

如果是监控目录,此时目录下的文件触发事件,会输出对应的文件名。但是如果只监控文件,则无法根据 event->name 输出对应更改的文件名,原因参考 7.1 监控文件时,无法根据 event->name 输出对应更改的文件名

6 inotify 示例 6.1 代码 #include #include #include #include #include #include #define EVENT_NUM 12 const char *event_str[EVENT_NUM] = { "IN_ACCESS", "IN_MODIFY", "IN_ATTRIB", "IN_CLOSE_WRITE", "IN_CLOSE_NOWRITE", "IN_OPEN", "IN_MOVED_FROM", "IN_MOVED_TO", "IN_CREATE", "IN_DELETE", "IN_DELETE_SELF", "IN_MOVE_SELF" }; int inotifyTask(char *argv[]) { int errTimes = 0; int fd = -1; INIT_INOTIFY: fd = inotify_init(); if(fd < 0) { fprintf(stderr, "inotify_init failed\n"); printf("Error no.%d: %s\n", errno, strerror(errno)); goto INOTIFY_FAIL; } int wd1 = -1; int wd2 = -1; struct inotify_event *event; int length; int nread; char buf[BUFSIZ]; int i = 0; buf[sizeof(buf) - 1] = 0; INOTIFY_AGAIN: wd1 = inotify_add_watch(fd, argv[1], IN_ALL_EVENTS); if(wd1 < 0) { fprintf(stderr, "inotify_add_watch %s failed\n", argv[1]); printf("Error no.%d: %s\n", errno, strerror(errno)); if(errTimes < 3) { goto INOTIFY_AGAIN; } else { goto INOTIFY_FAIL; } } wd2 = inotify_add_watch(fd, argv[2], IN_ALL_EVENTS); if(wd2 < 0) { fprintf(stderr, "inotify_add_watch %s failed\n", argv[2]); printf("Error no.%d: %s\n", errno, strerror(errno)); if(errTimes < 3) { goto INOTIFY_AGAIN; } else { goto INOTIFY_FAIL; } } length = read(fd, buf, sizeof(buf) - 1); nread = 0; // inotify 事件发生时 while(length > 0) { printf("\n"); event = (struct inotify_event *)&buf[nread]; // 遍历所有事件 for(i = 0; i< EVENT_NUM; i++) { // 判断事件是否发生 if( (event->mask >> i) & 1 ) { // 该监控项为目录或目录下的文件时 if(event->len > 0) { fprintf(stdout, "%s --- %s\n", event->name, event_str[i]); } // 该监控项为文件时 else if(event->len == 0) { if(event->wd == wd1) { fprintf(stdout, "%s --- %s\n", argv[1], event_str[i]); } if(event->wd == wd2) { fprintf(stdout, "%s --- %s\n", argv[2], event_str[i]); } } } } nread = nread + sizeof(struct inotify_event) + event->len; length = length - sizeof(struct inotify_event) - event->len; } goto INOTIFY_AGAIN; close(fd); return 0; INOTIFY_FAIL: return -1; } int main(int argc, char *argv[]) { if(argc < 3) { fprintf(stderr, "Usage: %s path path\n", argv[0]); return -1; } if(inotifyTask(argv) == -1) { return -1; } return 0; } 6.2 编译

编译命令:

gcc inotify.c -o out

如下图所示:

6.3 运行截图 6.3.1 不加任何参数

此时会提示信息,需要输入两个路径用于监控,如下图所示:

6.3.2 监控两个文件

监控 /etc/a,/etc/b

如下图所示:

6.3.3 监控两个目录

监控 /etc,/tmp

如下图所示:

7 inotify 监控文件时常见问题 7.1 监控文件时,无法根据 event->name 输出对应更改的文件名

原因:

在 linux 手册中关于 inotify 的描述有对应解释。 如果是监控目录,此时目录下的文件触发事件,会输出对应的文件名。但是如果只监控文件,则无法输出对应更改的文件名。如下图所示:

7.2 监控文件时,无法持续监控,第二次更改文件时,它没有响应

原因:

这是由于 vim 的工作机制引起的,vim 会先将源文件复制为另一个文件,然后在另一文件基础上编辑(后缀名为 swp),保存的时候再将这个文件覆盖源文件。此时原来的文件已经被后来的新文件代替,因此监视对象所监视的文件已经不存在了,所以自然不会产生任何事件。

解决方法:

重新使用 inotify_add_watch,将该文件加入监控队列。

8 参考资料

1、linux 手册中关于 inotify 的描述 - https://man7.org/linux/man-pages/man7/inotify.7.html

2、Stack Overflow 中关于 inotify inotify_event event->name is empty Shay 的提问 - Will Chappell 的回答- https://stackoverflow.com/questions/7957132/inotify-inotify-event-event-name-is-empty

3、inotify 检测文件被修改 - https://www.169it.com/tech-qa-linux/article-13284431940448660571.html

4、如何在C中使用inotify - http://www.voidcn.com/article/p-ntlqecbe-bwk.html

5、c使用inotify监控linux路径下文件变化 - meccaendless(一江明澈的水)- https://blog.csdn.net/meccaendless/article/details/80238997

6、如何用c语言实现对目录或是文件进行文件的添加,修改,删除监控(inotify) - jenie - https://blog.csdn.net/jenie/article/details/106195668?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-3&spm=1001.2101.3001.4242



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3