INOTIFY 机制
inotif可以对文件系统进行监控,监控文件系统中发生的事情。为了截取文件系统的变化,inotif机制在文件系统的各个操作中加入了hook函数,当文件系统调用了这些操作函数并改变了文件系统中的文件或目录的时候,就会调用hook函数发出对应的时事件。并将这个事件放到内核的一个队列中,应用层可以取走这些事件,同时如果事件过多而没用及时取走事件的话就有可能丢失事件。
说人话就是这东西可以监控文件或目录被读了,被写了,创建了,删除了,被移动了,被访问了等等。这个在linux内核 二点几之后才支持的。 具体哪个版本等我想起来了我补充上来~。
具体那些事情被监控可以查看头文件 #include <sys/inotify.h>
#ifndef _SYS_INOTIFY_H
#define _SYS_INOTIFY_H 1
#include <stdint.h>
/* Get the platform-dependent flags. */
#include <bits/inotify.h>
/* Structure describing an inotify event. */
struct inotify_event
{
int wd; /* Watch descriptor. */
uint32_t mask; /* Watch mask. */
uint32_t cookie; /* Cookie to synchronize two events. */
uint32_t len; /* Length (including NULs) of name. */
char name __flexarr; /* Name. */
};
/* Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH. */
#define IN_ACCESS 0x00000001 /* File was accessed. 文件被访问 */
#define IN_MODIFY 0x00000002 /* File was modified. 文件被修改 */
#define IN_ATTRIB 0x00000004 /* Metadata changed. 元数据修改*/
#define IN_CLOSE_WRITE 0x00000008 /* Writtable file was closed. 可写文件被关闭 */
#define IN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed. */
#define IN_CLOSE (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) /* Close. 文件被关闭 */
#define IN_OPEN 0x00000020 /* File was opened. 文件被打开 */
#define IN_MOVED_FROM 0x00000040 /* File was moved from X. 文件被移动过来 */
#define IN_MOVED_TO 0x00000080 /* File was moved to Y. 文件被移动到 */
#define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO) /* Moves. */
#define IN_CREATE 0x00000100 /* Subfile was created. 子文件被创建 */
#define IN_DELETE 0x00000200 /* Subfile was deleted. */
#define IN_DELETE_SELF 0x00000400 /* Self was deleted. 文件被删除 */
#define IN_MOVE_SELF 0x00000800 /* Self was moved. 文件被移动 */
/* Events sent by the kernel. */
#define IN_UNMOUNT 0x00002000 /* Backing fs was unmounted. 目录被卸载 */
#define IN_Q_OVERFLOW 0x00004000 /* Event queued overflowed. 内核的队列满了 */
#define IN_IGNORED 0x00008000 /* File was ignored. 不知道是啥, 知道了给我说一下 */
/* Helper events. */
#define IN_CLOSE (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) /* Close. */
#define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO) /* Moves. */
/* Special flags. */
#define IN_ONLYDIR 0x01000000 /* Only watch the path if it is a
directory. */
#define IN_DONT_FOLLOW 0x02000000 /* Do not follow a sym link. */
#define IN_EXCL_UNLINK 0x04000000 /* Exclude events on unlinked
objects. */
#define IN_MASK_CREATE 0x10000000 /* Only create watches. */
#define IN_MASK_ADD 0x20000000 /* Add to the mask of an already
existing watch. */
#define IN_ISDIR 0x40000000 /* Event occurred against dir. */
#define IN_ONESHOT 0x80000000 /* Only send event once. */
/* All events which a program can wait on. */
#define IN_ALL_EVENTS (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)
__BEGIN_DECLS
/* Create and initialize inotify instance. */
extern int inotify_init (void) __THROW;
/* Create and initialize inotify instance. */
extern int inotify_init1 (int __flags) __THROW;
/* Add watch of object NAME to inotify instance FD. Notify about
events specified by MASK. */
extern int inotify_add_watch (int __fd, const char *__name, uint32_t __mask)
__THROW;
/* Remove the watch specified by WD from the inotify instance FD. */
extern int inotify_rm_watch (int __fd, int __wd) __THROW;
__END_DECLS
#endif /* sys/inotify.h */
我直接把这个头文件拷贝过来了,对上面的事件进行了部分备注。 可以看到整个头文件还是十分简洁的。上面包含了所有可以监控的事件。
流程如下:
- int fd = inotify_init(); //创建一个inotify文件描述符
- int wd = inotify_add_watch(fd, path, mask); //添加一个watch,path是被监控的文件路径,mask是事件掩码,wd是watch描述符
- size_t len = read (fd, buf, BUF_LEN); //读取多个事件, 这个fd也可以使用select进行监控
- int ret = inotify_rm_watch(fd, wd); //删除一个监控
- close(fd);
上述就是程序的一个流程了。详细内容见下面的代码
#include <sys/inotify.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
char *monitored_file[] = {
"/home/lq/桌面/111.txt",
"/home/lq/1.txt",
"/home/lq/桌面/"
};
struct wd_name {
int wd;
char *name;
};
#define WD_NUM 3 //定义要监控的数量
struct wd_name wd_array[WD_NUM];
const char *event_array[] = { //事件要发生打印的内容。
"File was accessed",
"File was modified",
"FIle attributes were changed",
"writtable file closed",
"Unwrittable file closed",
"File was opened",
"File was moved from x",
"File was moved to Y",
"Subfile was created",
"Subfile was deleted",
"Self was deleted",
"Self was moved",
"",
"Backing fs was unmounted",
"Event queued overflowed",
"File was ignored"
};
#define EVEBT_NUM 16
#define MAX_BUF_SIZE 1024
int main()
{
int fd;
int wd;
char buffer[1024];
char *offset = NULL;
struct inotify_event *event;
int len, tmp_len;
char strbuf[16];
int i = 0;
fd = inotify_init(); // 1 初始化文件描述符
if (fd < 0) {
printf("初始化inotify err = %d ", fd);
_exit(-1);
}
for (i = 0; i < WD_NUM; i++) {
wd_array[i].name = monitored_file[i];
wd = inotify_add_watch(fd, wd_array[i].name, IN_ALL_EVENTS); //添加文件
if (wd < 0) {
printf("Can not add watch for %s \n", wd_array[i].name);
_exit(-2);
}
wd_array[i].wd = wd;
}
while (len = read(fd, buffer, MAX_BUF_SIZE)) { //读取文件, 可以使用select监控该文件描述符
offset = buffer;
printf("Some event happens , len = %d. \n", len);
event = (struct inotify_event *)buffer;
while (((char *)event - buffer) < len) { //遍历所有事件
if (event->mask& IN_ISDIR) { //判断事件是目录还是文件
memcpy(strbuf, "Direcotory", 11);
} else {
memcpy(strbuf, "File", 5);
}
printf("Object type: %s\n", strbuf);
for (i = 0; i < WD_NUM; i++) { //检测是那个文件发生的事件
if (event->wd != wd_array[i].wd)
continue;
printf("Object name: %s \n", wd_array[i].name);
break;
}
printf("Event mask: %08X\n", event->mask); //打印事件掩码, 事件通过掩码的形式放在mask中
for (i = 0; i < EVEBT_NUM; i++) {
if (event_array[i][0] == '\0')
continue;
if (event->mask &(i << i)) {
printf("Event: %s\n", event_array[i]); // 打印事件对应的说明字符串
}
}
tmp_len = sizeof(struct inotify_event) + event->len; //获取下一个事件
event = (struct inotify_event *)(offset + tmp_len);
offset += tmp_len;
}
}
return 0;
}
gcc inotfy.cpp -o inotfy
然后执行结果如下:
我们可以看到文件打开和关闭的都被监控到了。
在这里需要主要的是 如果文件被删除了或者被移动。 在创建一个1.txt那么这个时候1.txt是不会被监控到的。原因是内核中的文件节点已经发生了变化。所以当监控到被删除或被移动了之后应当重新添加一次。
但是存在以下问题:
- 需要手动封装select这样会让编码变得复杂一些。
- 当前对目录的监控只能是对当前目录的监控, 例如上面的例子中监控了/home/lq/桌面 这个目录,但是桌面上的子目录却无法被监控,如果在桌面上再在创建一个目录也无法进行监控。如果需要监控只能说遍历目录将每一个文件和目录都进行监控,这样以来又增加了代码量。虽然不是很难,但是很麻烦。
现在可以介绍一个程序 inotifywait, 查看帮助信息如下:
执行命令: inotifywait -rm /home/lq/桌面 可以对桌面的所有文件进行监控,并且是递归的。这就比较厉害了呀。 具体的操作截图就不放了。
通过对inotifywait的源码进行查看发现,这个程序封装了一个库叫做libinotifytools.so
这个库中直接对inotif进行了封装, 反正好用就对了。
sudo apt install libinotifytools0-dev
之后看代码:
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <inotifytools/inotifytools.h>
#include <inotifytools/inotify.h>
// 需要安装libinotifytools0-dev
void test()
{
int ret = inotifytools_initialize(); //初始化时间
ret = inotifytools_watch_recursively("/home/lq/桌面/", IN_CLOSE); //写入要监控的目录
ret = inotifytools_watch_file("/home/lq/桌面/", IN_CLOSE);
while (1) {
struct inotify_event *s = inotifytools_next_event(0); //获取事件
static char *filename = inotifytools_filename_from_wd(s->wd); //获取发送事件的文件名称
inotifytools_printf(s, "%w %,e %f\n");
static char *str = inotifytools_event_to_str(s->mask);
printf("str = %s\n", str);
}
}
int main()
{
test(); // inotif 进行监控磁盘
return 0;
}
g++ inotify1.cpp -linotifytools -o inotify1
运行结果如下:
从图中可以看到111为桌面的子目录,但是仍然被监控到了。 并且如果该目录新建立文件,那么这个文件也会被自动加入到监控的队列中,这样子就很方便了。