about summary refs log tree commit homepage
path: root/lib/PublicInbox/FakeInotify.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/PublicInbox/FakeInotify.pm')
-rw-r--r--lib/PublicInbox/FakeInotify.pm63
1 files changed, 55 insertions, 8 deletions
diff --git a/lib/PublicInbox/FakeInotify.pm b/lib/PublicInbox/FakeInotify.pm
index 25818e07..644f5b5b 100644
--- a/lib/PublicInbox/FakeInotify.pm
+++ b/lib/PublicInbox/FakeInotify.pm
@@ -5,16 +5,29 @@
 # enough of Linux::Inotify2
 package PublicInbox::FakeInotify;
 use strict;
+use v5.10.1;
+use parent qw(Exporter);
 use Time::HiRes qw(stat);
 use PublicInbox::DS qw(add_timer);
 sub IN_MODIFY () { 0x02 } # match Linux inotify
 # my $IN_MOVED_TO = 0x80;
 # my $IN_CREATE = 0x100;
 sub MOVED_TO_OR_CREATE () { 0x80 | 0x100 }
+sub IN_DELETE () { 0x00000200 }
+
+our @EXPORT_OK = qw(fill_dirlist on_dir_change);
 
 my $poll_intvl = 2; # same as Filesys::Notify::Simple
 
-sub new { bless { watch => {} }, __PACKAGE__ }
+sub new { bless { watch => {}, dirlist => {} }, __PACKAGE__ }
+
+sub fill_dirlist ($$$) {
+        my ($self, $path, $dh) = @_;
+        my $dirlist = $self->{dirlist}->{$path} = {};
+        while (defined(my $n = readdir($dh))) {
+                $dirlist->{$n} = undef if $n !~ /\A\.\.?\z/;
+        }
+}
 
 # behaves like Linux::Inotify2->watch
 sub watch {
@@ -22,11 +35,19 @@ sub watch {
         my @st = stat($path) or return;
         my $k = "$path\0$mask";
         $self->{watch}->{$k} = $st[10]; # 10 - ctime
+        if ($mask & IN_DELETE) {
+                opendir(my $dh, $path) or return;
+                fill_dirlist($self, $path, $dh);
+        }
         bless [ $self->{watch}, $k ], 'PublicInbox::FakeInotify::Watch';
 }
 
-sub on_new_files ($$$$) {
-        my ($events, $dh, $path, $old_ctime) = @_;
+# also used by KQNotify since it kevent requires readdir on st_nlink
+# count changes.
+sub on_dir_change ($$$$;$) {
+        my ($events, $dh, $path, $old_ctime, $dirlist) = @_;
+        my $oldlist = $dirlist->{$path};
+        my $newlist = $oldlist ? {} : undef;
         while (defined(my $base = readdir($dh))) {
                 next if $base =~ /\A\.\.?\z/;
                 my $full = "$path/$base";
@@ -35,7 +56,19 @@ sub on_new_files ($$$$) {
                         push @$events,
                                 bless(\$full, 'PublicInbox::FakeInotify::Event')
                 }
+                if (!@st) {
+                        # ignore ENOENT due to race
+                        warn "unhandled stat($full) error: $!\n" if !$!{ENOENT};
+                } elsif ($newlist) {
+                        $newlist->{$base} = undef;
+                }
         }
+        return if !$newlist;
+        delete @$oldlist{keys %$newlist};
+        $dirlist->{$path} = $newlist;
+        push(@$events, map {
+                bless \"$path/$_", 'PublicInbox::FakeInotify::GoneEvent'
+        } keys %$oldlist);
 }
 
 # behaves like non-blocking Linux::Inotify2->read
@@ -43,6 +76,7 @@ sub read {
         my ($self) = @_;
         my $watch = $self->{watch} or return ();
         my $events = [];
+        my @watch_gone;
         for my $x (keys %$watch) {
                 my ($path, $mask) = split(/\0/, $x, 2);
                 my @now = stat($path) or next;
@@ -52,14 +86,18 @@ sub read {
                 if ($mask & IN_MODIFY) {
                         push @$events,
                                 bless(\$path, 'PublicInbox::FakeInotify::Event')
-                } elsif ($mask & MOVED_TO_OR_CREATE) {
-                        opendir(my $dh, $path) or do {
+                } elsif ($mask & (MOVED_TO_OR_CREATE | IN_DELETE)) {
+                        if (opendir(my $dh, $path)) {
+                                on_dir_change($events, $dh, $path, $old_ctime,
+                                                $self->{dirlist});
+                        } elsif ($!{ENOENT}) {
+                                push @watch_gone, $x;
+                        } else {
                                 warn "W: opendir $path: $!\n";
-                                next;
-                        };
-                        on_new_files($events, $dh, $path, $old_ctime);
+                        }
                 }
         }
+        delete @$watch{@watch_gone};
         @$events;
 }
 
@@ -86,4 +124,13 @@ package PublicInbox::FakeInotify::Event;
 use strict;
 
 sub fullname { ${$_[0]} }
+
+sub IN_DELETE { 0 }
+
+package PublicInbox::FakeInotify::GoneEvent;
+use strict;
+our @ISA = qw(PublicInbox::FakeInotify::Event);
+
+sub IN_DELETE { 1 }
+
 1;