Some applications need to monitor files or directories to sense that specific events have occurred in these files or directories. The inotify mechanism is provided in Linux, which allows applications to listen for file (directory) events.
This paper mainly introduces inotify from the following aspects:
- inotify usage scenario
- Relevant system calls associated with inotify mechanism
- inotify supported event types
- inotify usage example
The ultimate purpose of listening for changes in files or directories must be to take corresponding measures based on different change events. Common usage scenarios are as follows:
- The configuration file is hot loaded. When the configuration file changes, the process can automatically perceive and reload the configuration file, such as the star project of golang-- viper
- Configure the hold function. When we need to keep some files on the server unchanged, we can listen to the files to be held. When the document is changed, it shall be restored accordingly
- When files are moved out or added to a directory, the graphical file manager needs to make corresponding adjustments according to the corresponding events
There are three main system calls related to inotify: inotify_init,inotify_add_watch,inotify_rm_watch. The specific system calls are as follows:
inotify_init
#include <sys inotify.h=""> int inotify_init(void);
inotify_init creates an inotify instance. This function returns a file descriptor to refer to the inotify instance. At the same time, it needs to read the file descriptor to obtain the file change event
inotify_add_watch
#include <sys inotify.h=""> int inotify_add_watch(int fd, const char *pathname, uint32_t mask);
- fd means inotify_ notify instance returned by init system call
- pathname refers to the file or directory path of the event to be monitored
- Mask event mask, indicating the type of event to listen to. The specific event types are described below
The system call return value (wd) is a monitoring descriptor, which refers to a monitoring item.
The figure above shows a notify instance and a set of monitoring items maintained by the instance
- The monitor descriptor is inotify_ add_ The return of the watch system call uniquely refers to a monitoring description item
- A mask is a mask used to define specific listening events
- pathname is the legal path of the complete file or directory to be monitored
inotify_rm_watch
#include <sys inotify.h=""> int inotify_rm_watch(int fd, uint32_t wd);
- fd means inotify_ notify instance returned by init system call
- wd refers to the monitor descriptor
General event type
mask flag describe IN_ACCESS File accessed (read operation performed) IN_ATTRIB File metadata changed IN_CLOSE_WRITE Close files opened for writing IN_CLOSE_NOWRITE Close a file that is opened read-only IN_CREATE A file or directory is created in a controlled directory IN_DELETE The file or directory is deleted in the controlled directory IN_DELETE_SELF Delete the controlled file or directory itself IN_MOVED_FROM Move files out of controlled directory IN_MOVED_TO Move files into controlled directory IN_OPEN File opened IN_MOVE IN_MOVED_FROM|IN_MOVED_TO events IN_CLOSE IN_CLOSE_WRITE|IN_CLOSE_NOWRITE collectively- IN_ Metadata changes monitored by attrib include permissions, ownership, number of links, user ID, etc
- An in is issued when you rename a controlled object_ DELETE_ Self event. If the controlled target is a directory, two events will be triggered when the file under the controlled target is renamed_ MOVED_ From and IN_MOVED_TO
In our daily development work, the above events have basically covered all situations of document changes. We can make corresponding processing processes for the above different event types according to their respective scenarios.
Other events
In addition to the regular events in the above files, inotify also provides the following mask s to control the process of event listening
mask flag describe IN_DONT_FOLLOW No symbolic link references IN_MASK_ADD Append the event to the current monitoring mask of pathname IN_ONESHOT Only one event of pathname is monitored IN_ONLYDIR It fails when pathname is not a directoryAdd the above mask flag to inotify_ add_ The monitoring process can be controlled in watch, which is a little general. For example.
inotify_add_watch(fd, pathname, IN_OPEN | IN_CLOSE | IN_ONESHOT);
The above code, in addition to listening to the in of the file_ Open and in_ In addition to the close event, an in is added_ Oneshot mask, which means that inotify will not listen to the events sent by the file referred to by pathname after listening to an event of the file referred to by pathname.
The above mask is used as inotify when adding a file monitoring item_ add_ The parameters of the watch system call are passed in. In addition, there are several events that do not require the user to display and call inotify_add_watch is added and is issued only when some other exceptions occur.
mask flag describe IN_IGNORED The monitor item is removed for the kernel or application IN_ISDIR What is monitored is the path of a directory IN_Q_OVERFLOW Event queue overflow IN_UNMOUNT The file system containing the object was unmounted- IN_ The isdir event indicates that the listened pathname refers to a directory. For example, the system command mkdir /tmp/xxx will generate IN_CREATE|IS_DIR event.
Event structure
The above describes the event types supported by inotify. It can be seen that the supported event types are very rich, which basically meets our various demands for file monitoring. In addition to the above event types, we will briefly describe the event structure of inotify in this section. Through the event data structure, we can see what information we can get from the event. The specific data structure of the event is as follows:
struct inotify_event { int wd; //Monitoring description symbol, which uniquely refers to a monitoring item uint32_t mask; //Type of event monitored uint32_t cookie; //This field is used only after renaming uint32_t len; //The following is the size of the name array char name[]; //When the file in the controlled directory is changed, this string will record the file name of the changed file };Use example
inotify demo The example comments are very detailed, and the above three system calls are used at the same time. The specific code is as follows:
#include <errno.h> #include <poll.h> #include <stdio.h> #include <stdlib.h> #include <sys inotify.h=""> #include <unistd.h> #include <string.h> /* Read all available inotify events from the file descriptor 'fd'. wd is the table of watch descriptors for the directories in argv. argc is the length of wd and argv. argv is the list of watched directories. Entry 0 of wd and argv is unused. */ static void handle_events(int fd, int *wd, int argc, char* argv[]) { /* Some systems cannot read integer variables if they are not properly aligned. On other systems, incorrect alignment may decrease performance. Hence, the buffer used for reading from the inotify file descriptor should have the same alignment as struct inotify_event. */ char buf[4096] __attribute__ ((aligned(__alignof__(struct inotify_event)))); const struct inotify_event *event; ssize_t len; /* Loop while events can be read from inotify file descriptor. */ for (;;) { /* Read some events. */ // fd is the inotfy instance file descriptor len = read(fd, buf, sizeof(buf)); //Read event if (len == -1 && errno != EAGAIN) { perror("read"); exit(EXIT_FAILURE); } /* If the nonblocking read() found no events to read, then it returns -1 with errno set to EAGAIN. In that case, we exit the loop. */ if (len <= 0) break; /* Loop over all events in the buffer. */ for (char *ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + event->len) { event = (const struct inotify_event *) ptr; /* Print event type. */ //Get what type of event the current event is through the event mask if (event->mask & IN_OPEN) printf("IN_OPEN: "); if (event->mask & IN_CLOSE_NOWRITE) printf("IN_CLOSE_NOWRITE: "); if (event->mask & IN_CLOSE_WRITE) printf("IN_CLOSE_WRITE: "); /* Print the name of the watched directory. */ for (int i = 1; i < argc; ++i) { if (wd[i] == event->wd) { printf("%s/", argv[i]); break; } } /* Print the name of the file. */ if (event->len) printf("%s", event->name); /* Print type of filesystem object. */ if (event->mask & IN_ISDIR) printf(" [directory]\n"); else printf(" [file]\n"); } } } int main(int argc, char* argv[]) { char buf; int fd, i, poll_num; int *wd; nfds_t nfds; struct pollfd fds[2]; if (argc < 2) { printf("Usage: %s PATH [PATH ...]\n", argv[0]); exit(EXIT_FAILURE); } printf("Press ENTER key to terminate.\n"); /* Create the file descriptor for accessing the inotify API. */ fd = inotify_init1(IN_NONBLOCK); //Create an inotify instance, and the fd file handle refers to the inotify instance if (fd == -1) { perror("inotify_init1"); exit(EXIT_FAILURE); } /* Allocate memory for watch descriptors. */ wd = calloc(argc, sizeof(int)); if (wd == NULL) { perror("calloc"); exit(EXIT_FAILURE); } /* Mark directories for events - file was opened - file was closed */ //Add a file monitoring item. Multiple listening directories can be registered here, and only in can be monitored at the same time_ Open and IN_CLOSE event type for (i = 1; i < argc; i++) { wd[i] = inotify_add_watch(fd, argv[i], IN_OPEN | IN_CLOSE); if (wd[i] == -1) { fprintf(stderr, "Cannot watch '%s': %s\n", argv[i], strerror(errno)); exit(EXIT_FAILURE); } } /* Prepare for polling. */ //At the same time, monitor the inotify message of the terminal. When the terminal enters, the monitor program exits. When the monitor hears the file change event, handle the event nfds = 2; fds[0].fd = STDIN_FILENO; /* Console input */ fds[0].events = POLLIN; fds[1].fd = fd; /* Inotify input */ fds[1].events = POLLIN; /* Wait for events and/or terminal input. */ printf("Listening for events.\n"); while (1) { poll_num = poll(fds, nfds, -1); if (poll_num == -1) { if (errno == EINTR) continue; perror("poll"); exit(EXIT_FAILURE); } if (poll_num > 0) { if (fds[0].revents & POLLIN) { /* Console input is available. Empty stdin and quit. */ while (read(STDIN_FILENO, &buf, 1) > 0 && buf != '\n') continue; break; } if (fds[1].revents & POLLIN) { /* Inotify events are available. */ handle_events(fd, wd, argc, argv); } } } printf("Listening for events stopped.\n"); /* Close inotify file descriptor. */ close(fd); free(wd); exit(EXIT_SUCCESS); }Other details
Inotify's event queue is not infinite, because the queue also consumes kernel memory, so some upper limits will be set to limit it. The specific configuration can control file event listening by modifying the three files under / proc/sys/fs/inotify.
- max_queued_events: Specifies the maximum number of inotify event queues. Once this limit is exceeded, the system will generate IN_Q_OVERFLOW event, which is described in detail above and will not be repeated here.
- max_user_instances: limit on the number of inotify instances created by each real user ID.
- max_user_watchers: limit on the number of monitoring items created by each real user ID.
Recently, I was studying the book < < Linux / unix system programming manual > >. At the same time, there are scenarios in the project that need to use the file listening mechanism. Therefore, we have a detailed understanding of the concept and specific usage of file event listening. I hope that by writing this article, I can help myself clarify my ideas, deepen my understanding of this knowledge point, and help the students who are understanding this piece.