1

I'd like to monitor a file with inotify, and trigger some code when someone changes the content (IN_MODIFY or IN_CLOSE_WRITE), but I'm running into problems where inotify stops returning events when users edit the file with their favorite tool. The file is meant to be simple (single line, no spaces, max 20 characters). I'd rather not restrict their usage, but I'm not sure how to handle different situations.

I'm using inotify and these are the events that I receive when various applications edit the file:

Action inotify Events
touch file IN_OPEN
echo "data" > file IN_MODIFY, IN_OPEN, IN_ACCESS, then IN_CLOSE_NOWRITE
nano file (on open) IN_OPEN
nano file (on ^O) IN_MODIFY, IN_CLOSE_WRITE, IN_OPEN, IN_ACCESS
vim file (on open) IN_OPEN, IN_CLOSE_NOWRITE
vim file (on :w) IN_MOVE_SELF, IN_ATTRIB, then events stop coming from this file
gedit file (on open) IN_OPEN, IN_CLOSE_NOWRITE, IN_ACCESS
gedit file (on save) IN_OPEN, IN_CLOSE_WRITE, IN_ATTRIB, then events stop coming from this file
mv newfile file IN_ATTRIB, then events stop coming from this file

At one point I thought I saw gedit trigger also trigger IN_DELETE_SELF before going silent.

In the case where a user uses vim and gedit, I stop getting inotify events after the user has finished the edits. How should I deal with this?

The only thing I see in common is the IN_ATTRIB event. I suspect that when I receive the IN_ATTRIB event, I should inotify_rm_watch() that wd, and then re-create a new inotify_add_watch() based on the same path. But is that the correct approach?

Another option could be to watch the parent directory. The affected file name is included in the inotify_event::name, and so I could filter on the file of interest, and trigger off of any IN_MODIFY or IN_CLOSE_WRITE where the name matches my file of interest.

2
  • 2
    Some editors rename the original file to another name, and then create a new file with the original name (esp. when creating a backup file, e.g. from foo.txt to foo.txt~). That would mean that if you only follow the original file, you should see just a rename event on the original, and nothing on the new one. Though there looks to be only one IN_MOVE* event in your list, but plausibly some programs might do something similar without renames (and without backup files). If you want to follow files of a particular name, you might need to follow the whole directory to catch them.
    – ilkkachu
    Commented Apr 19 at 12:29
  • Ditto. Commented Apr 19 at 12:53

2 Answers 2

1

As ikkachu mentions, some editors create a new file, then replace the original, changing the inode. That means any watches on the original watch descriptor will expire.

The answer is to look at the parent directory, and check for changes on any file with the target name. Something like this:

namespace fs = std::filesystem;
fs::path path = "./file1";
assert( !path.is_directory() );

int fd = inotify_init();

int wd = inotify_add_watch(
    fd, 
    path.parent_path().c_str(),
    IN_MODIFY | IN_CREATE | IN_CLOSE_WRITE
);  

...

inotify_event event;
read(fd, &event, BUF_SIZE);

if (wd == event->wd && path.filename() == event->name) {
    emit_file_changed();
}

These events (IN_MODIFY|IN_CREATE|IN_CLOSE_WRITE) capture the techniques I tried above (touch, echo "" >, vim, nano, gedit). I bet I could also capture changed symbolic links with these.

0

Or you could use fatrace which is a ton faster and has no such issues, e.g. something along these lines:

fatrace --timestamp --filter='WD<>+'

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .