It is not uncommon to write a daemon program and let the daemon process to watch its configuration file, if the file changed, then daemon will do a restart or reload.
To watch if file has changed, an easy solution is setting a timer to poll the config file info, like its atime, ctime and mtime, and file content's hash value, if there is a change b/t this time and next time polling, then we know there is a change happened.
Another solution is using Linux's inotify API (3 APIs and 1 structure are related to this):
3 APIs:
- inotify_init
- inotify_add_watch
- inotify_rm_watch
1 structure:
- struct inotify_event
So basically we need to init the inotify
system (inotify_init), then add the file we want to watch to this system (inotify_add_watch). When adding this file we can specify what kind event to watch, if we want to watch all events, use IN_ALL_EVENTS
, then we call read
on the "inotify" fd, then function will block until the specified events happened.
Example code:
#include <sys/inotify.h>
#include <limits.h>
#include <unistd.h>
#include <stdio.h>
#define BUF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1))
int main() {
char buf[BUF_LEN];
int inotify_fd = 0;
struct inotify_event *event = NULL;
char* pathname = "config.ini";
inotify_fd = inotify_init();
inotify_add_watch(inotify_fd, pathname, IN_ALL_EVENTS);
while (1) {
int n = read(inotify_fd, buf, BUF_LEN);
char* p = buf;
while (p < buf + n) {
event = (struct inotify_event*)p;
uint32_t mask = event->mask;
if (mask & IN_ACCESS) {
printf("File has been accessed\n");
}
if (mask & IN_ATTRIB) {
printf("File meta data changed\n");
}
if (mask & IN_CLOSE_WRITE) {
printf("File closed after write\n");
}
if (mask & IN_CLOSE_NOWRITE) {
printf("File closed after read\n");
}
if (mask & IN_DELETE_SELF) {
printf("File is deleted\n");
}
if (mask & IN_MODIFY) {
printf("File has been modified\n");
}
if (mask & IN_MOVE_SELF) {
printf("File has been moved\n");
}
if (mask & IN_OPEN) {
printf("File has been opened\n");
}
if (mask & IN_IGNORED) {
printf("File monitor has been removed\n");
}
p += sizeof(struct inotify_event) + event->len;
}
}
return 0;
}
Compile and run it:
$ gcc main.c -o iwatch
$ ./iwatch
Now open another terminal, and change the file to see if our "iwatch" works:
When touch config.ini
, "iwatch" printed:
File has been opened
File meta data changed
File closed after write
When cat config.ini
, "iwatch" printed:
File has been opened
File has been accessed
File closed after read
When use nano
to edit config.ini
and close it, "iwatch" printed:
File has been opened
File has been accessed
File closed after read
File has been modified
File has been opened
File has been modified
File closed after write
When cp config2.ini config.ini
, "iwatch" printed:
File has been modified
File has been opened
File closed after write
So far so good, but when vim config.ini
, "iwatch" printed:
File has been opened
File closed after read
File has been opened
File has been accessed
File closed after read
File has been moved
Weird there is no "File closed after write", and even worse, after we vim config.ini
, if we touch config.ini
, no further events got printed.
The reason is, when vim
edits a file, it will open the file and create a temporary file, and write the content to that temporary file, after editing done, it will move the temporary file to the actual file. That's why we can see the message "File has been moved" in the end.
Because vim created a new temporary file to do the editing, the temporary file has its own inode number, and because inotify is inode-based API, after move the temporary file to the actual file, our config.ini
file's inode has been changed, it is not the one we are watching anymore, that's why after vim
, if we do touch config.ini
, there are no more message printed out.
But remember we used nano
to edit the file before, and it works as expected, that's because nano
does the editing in a different way, it will open the actual file and overwrite its content, so our config.ini
file's inode number stays unchange:
# get the inode number
$ ls -i config.ini
1574489 config.ini
$ nano config.ini
$ ls -i config.ini
1574489 config.ini
# we can see after nano, inode number stays unchange
$
# now let's use vim
$ vim config.ini
$ ls -i config.ini
1574501 config.ini
# the file's inode number changed.
Conclusion: if your program just need to monitor file change on "touch", "nano", "cp", you can use the inotify
API to monitor that file, but "vim" won't trigger a file content change, and you will lose the future events since the inode number has been changed. An alternative way is to monitor file's folder instead of the file itself, then all editing will fire events. But as a fallback and probably easier solution, also because inotify API is only available in Linux, maybe just use the polling method is enough for your program.
Top comments (0)