zoukankan      html  css  js  c++  java
  • Linux C 使用 inotify 监控文件或目录变化

    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<sys/inotify.h> 
     
    int inotify_init(void);  
    

    返回值

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

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

    使用及解释

    int fd = inotify_init();
    

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

    3.2 inotify_add_watch

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

    函数原型

    #include<sys/inotify.h> 
    
    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 <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/inotify.h>
    
    #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
    ");
    
    		printf("Error no.%d: %s
    ", 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
    ", argv[1]);
    
    		printf("Error no.%d: %s
    ", 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
    ", argv[2]);
    
    		printf("Error no.%d: %s
    ", 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("
    ");
    		
    		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
    ", event->name, event_str[i]);
    				}
    				// 该监控项为文件时
    				else if(event->len == 0)
    				{
    					if(event->wd == wd1)
    					{
    						fprintf(stdout, "%s --- %s
    ", argv[1], event_str[i]);
    					}
    					if(event->wd == wd2)
    					{
    						fprintf(stdout, "%s --- %s
    ", 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
    ", 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

  • 相关阅读:
    flask框架中SQLAlchemy相关
    flask使用外部存储模块之数据库的使用
    flask的基础知识
    docker的下载和使用
    rbac权限管理系统的学习
    redis数据库之五种数据类型的简单操作
    使用django框架进行web项目开发需要了解的知识
    django项目常用外部模块下载和使用
    pwn学习之dl_resolve学习篇
    验证docker的Redis镜像也存在未授权访问漏洞
  • 原文地址:https://www.cnblogs.com/PikapBai/p/14480881.html
Copyright © 2011-2022 走看看