Watching multiple files with inotify in perl

Drake picture Drake · Mar 15, 2011 · Viewed 7.8k times · Source

I need to watch multiple files in Perl, and I am using Linux::Inotify2. However I am encountering an issue in that the first file being watched needs to be modified and hit, then the second, then the first etc etc

For example if the second file is changed before the first, it will not trigger out, or if the first is triggered twice in a row without the second being triggered in between.

This is the section of code I am using which is having this issue.

my $inotify = new Linux::Inotify2;
my $inotify2 = new Linux::Inotify2;
$inotify->watch ("/tmp/rules.txt", IN_MODIFY);
$inotify2->watch ("/tmp/csvrules.out", IN_MODIFY);

while () {
  my @events = $inotify->read;
  unless (@events > 0){
    print "read error: $!";
    last ;
  }

  foreach $mask (@events) {
    printf "mask\t%d\n", $mask;

    open (WWWRULES, "/tmp/rules.txt");

    my @lines = <WWWRULES>;
    foreach $line (@lines) {
      @things = split(/,/, $line);
      addrule(@things[0], @things[1], @things[2], @things[3], trim(@things[4]));
      print "PRINTING: @things[0], @things[1], @things[2], @things[3], @things[4]";
      close (WWWRULES);
      open (WWWRULES, ">/tmp/rules.txt");
      close (WWWRULES);
    }
  }

  my @events2 = $inotify2->read;
  unless (@events2 > 0){
    print "read error: $!";
    last ;
  }
  foreach $mask (@events) {
    printf "mask\t%d\n", $mask;
    open (SNORTRULES, "/tmp/csvrules.out");

    my @lines2 = <SNORTRULES>;
    foreach $line2 (@lines2) {
      @things2 = split(/,/, $line2);
      addrule("INPUT", @things2[0], @things2[1], @things2[2], trim(@things2[3]));
      print "PRINTING: INPUT, @things2[0], @things2[1], @things2[2], @things2[3]";

      close (SNORTRULES);
      open (SNORTRULES, ">/tmp/csvrules.out");
      close (SNORTRULES);
    }
  }
}

Ideally I would like to be watching 3 files but as I cannot get 2 working it seems a little pointless at this stage.

Thanks for any help!

Answer

cjm picture cjm · Mar 15, 2011

A single inotify object can handle any number of watches. That's one of the advantages of inotify over the older and now obsolete dnotify. So you should be saying:

my $inotify = Linux::Inotify2->new;
$inotify->watch("/tmp/rules.txt", IN_MODIFY);
$inotify->watch("/tmp/csvrules.out", IN_MODIFY);

Then you can see which watch was triggered by checking the fullname property of the event object:

while () {
  my @events = $inotify->read;
  unless (@events > 0){
    print "read error: $!";
    last ;
  }

  foreach my $event (@events) {
    print $event->fullname . " was modified\n" if $event->IN_MODIFY;
  }
}

The big problem is that your code is modifying the same files that you're watching for modifications. When /tmp/rules.txt is modified, you open it, read it, and then truncate it, which triggers another modification notice, starting the whole process over again. In general, this is hard to solve without race conditions, but in your case, you should be able to just check for an empty file (next if -z $event->fullname).