As a listener in the Linux system, inotify can monitor changes in files or directories.
inotify interface
There are three main interfaces of inotify, namely inotify_init, inotify_add_watch and inotify_rm_watch. Each is introduced in detail below.
The inotify_init function is used to create an inotify handle. The function prototype int inotify_init(void);
inotify_add_watch is responsible for adding the monitored files or directories. The function prototype int inotify_add_watch(int fd, const char *pathname, uint32_t mask);
fd: inotify_init creates the inotify handle.
pathname: The file or directory path to be monitored.
mask: The event type to listen for.
inotify_rm_watch is used to delete the monitored files or directories. The function prototype int inotify_rm_watch(int fd, int wd);
fd: inotify handle created by inotify_init.
wd: handle created by inotify_add_watch
inotify use
The structure inotify_event is mainly used to save the information of the monitored files or directories. The specific definition is as follows.
To implement a simple example based on inotify, the code is as follows.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/inotify.h>
#include <pthread.h>
#define EVENTS_BUF_SIZE 1024
static void PrintEvent(const char *base, struct inotify_event *event)
{
char *operate;
int mask = event->mask;
if (mask & IN_ACCESS) operate = "ACCESS";
if (mask & IN_ATTRIB) operate = "ATTRIB";
if (mask & IN_CLOSE_WRITE) operate = "CLOSE_WRITE";
if (mask & IN_CLOSE_NOWRITE) operate = "CLOSE_NOWRITE";
if (mask & IN_CREATE) operate = "CREATE";
if (mask & IN_DELETE_SELF) operate = "DELETE_SELF";
if (mask & IN_MODIFY) operate = "MODIFY";
if (mask & IN_MOVE_SELF) operate = "MOVE_SELF";
if (mask & IN_MOVED_FROM) operate = "MOVED_FROM";
if (mask & IN_MOVED_TO) operate = "MOVED_TO";
if (mask & IN_OPEN) operate = "OPEN";
if (mask & IN_IGNORED) operate = "IGNORED";
if (mask & IN_DELETE) operate = "DELETE";
if (mask & IN_UNMOUNT) operate = "UNMOUNT";
printf("%s: %s\n", base, operate);
}
static void Thread_Inotify(void *arg)
{
char *path = (char *)arg;
char events[EVENTS_BUF_SIZE];
int fd = inotify_init(); // 创建 inotify 句柄
if (fd < 0) {
printf("Failed to initalize inotify\n");
return;
}
int ret = inotify_add_watch(fd, path, IN_ALL_EVENTS); // 添加监听事件类型
if (ret == -1) {
printf("Failed to add file or directory watch\n");
return;
}
while (1) {
memset(events, 0, sizeof(events));
int nbytes = read(fd, events, sizeof(events));
if (nbytes <= 0) {
printf("Failed to read events\n");
continue;
}
int offset = 0;
struct inotify_event event;
do {
memset(&event, 0x00, sizeof(event));
memcpy(&event, &events[offset], sizeof(event));
PrintEvent(path, &event);
offset += sizeof(struct inotify_event) + event.len;
} while (offset < nbytes);
}
}
static void Thread_HandleFile(void *arg)
{
char *path = (char *)arg;
while (1)
{
FILE *fp = fopen(path, "wb+");
char wbuf[] = "123";
fwrite(wbuf, sizeof(wbuf), sizeof(char), fp);
fclose(fp);
sleep(2);
fp = fopen(path, "r");
char rbuf[16];
fread(rbuf, sizeof(rbuf), sizeof(char), fp);
fclose(fp);
sleep(2);
break;
}
}
int main(int argc, char *argv[])
{
char *path = argv[1];
pthread_t thid_inotify, thid_handle;
pthread_create(&thid_inotify, NULL, (void *)Thread_Inotify, path);
sleep(2);
pthread_create(&thid_handle, NULL, (void *)Thread_HandleFile, path);
while (1) {
sleep(2);
}
return 0;
}
inotify principle
Taking read as an example, when the user calls read, the kernel will save the event in the queue events (struct list_head events;) in inotify_device, and then wake up the process waiting for the events (wait_queue_head_t wq;). The specific calling relationship is as shown below:
When the user calls system calls such as reading and writing, the kernel will create a corresponding trigger function to generate an event, add the event to the event queue events of inotify_device, and wake up the process waiting for the event. When the process is awakened, the events in the inotify event queue can be read through read.