使用 Linux inotify
进行文件监控。
参考:
- Linux Inotify详解和使用
- inotify(7) — Linux manual page
- linux 系统调用 inotify & epoll
Linux 提供的 inotify
是一种异步文件监控机制,可以用来监控文件系统的事件,包括访问、读写、权限、删除、移动等事件。
inotify
既可以监控单个文件,也可以监控整个目录,当监控的对象是一个目录的时候,目录本身和目录下的文件/文件夹都是被监控的对象。但是不能递归监控子目录,如果想要监控子目录下的文件,需要自己通过递归的方法将所有子目录都添加到监控中。
此种机制出现的目的是当内核空间发生某种事件之后,可以立即通知到用户空间,方便用户对此做出相应的操作。
参考:监听风云 | inotify 实现原理
inotify
是一个内核用于通知用户空间程序文件系统变化的机制。
inotify
的实现过程总结为以下两点:
inotify
的事件队列中。read
函数来读取 inotify
事件队列中的事件,进而获知文件的变化。此处只是简单记录了下 inotify 的原理,想了解详细过程的朋友还请参考如上的参考文献。
inotify 三个常用的 API :
inotify_init()
inotify_add_watch()
inotify_rm_watch()
原型:
int fd = inotify_init(void);
用于创建一个inotify的实例,然后返回inotify事件队列的文件描述符。
原型:
int wd = inotify_add_watch(int fd, const char* pathname, uint32_t mask);
该函数用于添加“watch list”,也就是监控列表。 可以是一个新的watch,也可以是一个已经存在的watch。其中 fd
就是 inotify_init() 的返回值,pathname
是要监控的目录或者文件的路径,mask
就是要监控的事件类型。见下文 inotify_event
中的 mask
字段。
该函数执行成功则返回一个unique的watch描述符。返回值是一个inotify标识,注意不要和 inotify_init() 的返回值搞混淆了。inotify_init() 的返回值是在读取事件、注册监听文件时使用,而 inotify_add_watch() 的返回值用来判断返回的事件属于哪个监听的文件(后面介绍inotify_event结构时会看到),以及移除监听文件时使用。
原型:
int inotify_rm_watch(int fd, int wd);
用于从 watch list 中移除监控的对象。其中 fd
是 inotify_init() 的返回值,wd
是 inotify_add_watch() 的返回值。
inotify API 常用使用流程:
inotify_init()
inotify_add_watch()
read() / epoll()
inotify_rm_watch()
内核使用 inotify_event
表示一个文件事件。当有事件发生时(即监控的文件对象发生了变化),inotify 文件描述符会可读。此时使用 select
、poll
、epoll
进行系统调用就会返回一个或者多个 inotify_event
文件事件对象。
inotify_event
数据结构:
struct inotify_event {
int wd; /* 监控对象的watch描述符 */
uint32_t mask; /* 事件掩码 */
uint32_t cookie; /* 和rename事件相关 */
uint32_t len; /* name字段的长度 */
char name[]; /* 监控对象的文件或目录名 */
};
mask 所表示的事件监控类型如下:
事件 | 描述 |
---|---|
IN_ACCESS | 文件被访问 |
IN_ATTRIB | 元数据被改变,例如权限、时间戳、扩展属性、链接数、UID、GID等 |
IN_CLOSE_WRITE | 打开用于写的文件被关闭 |
IN_CLOSE_NOWRITE | 不是打开用于写的文件被关闭 |
IN_CREATE | 在监控的目录中创建了文件或目录 |
IN_DELETE | 在监控的目录中删除了文件或目录 |
IN_DELETE_SELF | 监控的文件或目录本身被删除 |
IN_MODIFY | 文件被修改,这种事件会用到 inotify_event 中的 cookie |
IN_MOVE_SELF | 监控的文件或目录本身被移动 |
IN_MOVED_FROM | 从监控的目录中移出文件 |
IN_MOVED_TO | 向监控的目录中移入文件 |
IN_OPEN | 文件或目录被打开 |
IN_ALL_EVENTS | 包含了上面提到的所有事件 |
示例:监听指定文件的创建和删除操作。
#include
#include
#include
#include
/*
struct inotify_event {
int wd; // Watch descriptor
uint32_t mask; // Mask of events
uint32_t cookie; // Unique cookie associating related events (for rename(2))
uint32_t len; // Size of name field
char name[]; // Optional null-terminated name
};
*/
int process_inotify_events(int fd) {
char buf[512];
struct inotify_event *event;
int event_size = sizeof(struct inotify_event);
// 检测事件是否发生,没有发生就会阻塞
int read_len = read(fd, buf, sizeof(buf));
// 如果read的返回值,小于inotify_event大小出现错误
if (read_len < event_size) {
printf("could not get event!\n");
return -1;
}
// 因为read的返回值存在一个或者多个inotify_event对象,需要一个一个取出来处理
int pos = 0;
while (read_len >= event_size) {
event = (struct inotify_event *) (buf + pos);
if (event->len) {
if (event->mask & IN_CREATE) {
printf("create file: %s\n", event->name);
} else {
printf("delete file: %s\n", event->name);
}
}
// 一个事件的真正大小:inotify_event 结构体所占用的空间 + inotify_event->name 所占用的空间
int temp_size = event_size + event->len;
read_len -= temp_size;
pos += temp_size;
}
return 0;
}
int main(int argc, char **argv) {
// inotify初始化
int InotifyFd = inotify_init();
if (InotifyFd == -1) {
printf("inotify_init error!\n");
return -1;
}
// 添加watch对象
std::string filePath = "/home/test.log";
int wd = inotify_add_watch(InotifyFd, filePath.c_str(), IN_CREATE | IN_DELETE);
if (wd < 0) {
printf("inotify_add_watch failed: %s\n", filePath.c_str());
return -1;
}
// 处理事件
int ret = process_inotify_events(InotifyFd);
if (ret != 0) {
return -1;
}
// 删除inotify的watch对象
if (inotify_rm_watch(InotifyFd, wd) == -1) {
printf("notify_rm_watch error!\n");
return -1;
}
// 关闭inotify描述符
close(InotifyFd);
return 0;
}
示例:监听指定目录下的文件操作。
完整的代码见:inotify test 。
//
// Created by lixiaoqing on 2022/7/12.
//
#include
#include
#include
#include
#include
#include
#include
#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
bool isQuited = false;
void quit() {
isQuited = true;
}
int main(int argc, char *argv[]) {
// inotify 初始化
int fd = inotify_init();
int wd = inotify_add_watch(fd, "/home/cc/tmp/", IN_MODIFY | IN_CREATE | IN_DELETE); // 监听指定目录下的修改、创建、删除事件
// 创建一个 epoll 句柄
int epfd = epoll_create(256);
struct epoll_event ev;
ev.data.fd = fd; // 设置要处理的事件相关的文件描述符
ev.events = EPOLLIN | EPOLLET; // 设置要处理的事件类型
// 注册 epoll 事件
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
// 循环监听事件
char buffer[BUF_LEN];
struct epoll_event events[20]; // 存储从内核得到的事件集合
while (isQuited) {
// 等待事件发生。返回需要处理的事件数目
int nfds = epoll_wait(epfd, events, 20, 500);
for (int i = 0; i < nfds; ++i) {
/**
* epoll_wait 会一直阻塞直到下面2种情况:
* 1. 一个文件描述符触发了事件。
* 2. 被一个信号处理函数打断,或者 timeout 超时。
* 所以下面需要对 fd 进行过滤,判断是否是我们需要的 fd 产生了事件
*/
if (events[i].data.fd != fd) {
continue;
}
int length = read(fd, buffer, BUF_LEN);
if (length < 0) {
perror("read");
}
int pos = 0;
while (pos < length) {
struct inotify_event *event = (struct inotify_event *) &buffer[pos];
if (event->len) {
if (event->mask & IN_CREATE) {
if (event->mask & IN_ISDIR) {
printf("The directory %s was created.\n", event->name);
} else {
printf("The file %s was created.\n", event->name);
}
} else if (event->mask & IN_DELETE) {
if (event->mask & IN_ISDIR) {
printf("The directory %s was deleted.\n", event->name);
} else {
printf("The file %s was deleted.\n", event->name);
}
} else if (event->mask & IN_MODIFY) {
if (event->mask & IN_ISDIR) {
printf("The directory %s was modified.\n", event->name);
} else {
printf("The file %s was modified.\n", event->name);
}
}
}
pos += EVENT_SIZE + event->len;
}
}
}
inotify_rm_watch(fd, wd);
close(epfd);
close(fd);
return 0;
}