X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FKQNotify.pm;h=381711fae8f400bf11b3f0bb3316c04d66207336;hb=HEAD;hp=1b5c578ef3df8aef15a9adefab55c00c11ded432;hpb=b5217ee1eee64ff22e085c9eb942b5ea65da9bd9;p=public-inbox.git
diff --git a/lib/PublicInbox/KQNotify.pm b/lib/PublicInbox/KQNotify.pm
index 1b5c578e..381711fa 100644
--- a/lib/PublicInbox/KQNotify.pm
+++ b/lib/PublicInbox/KQNotify.pm
@@ -1,16 +1,17 @@
-# Copyright (C) 2020 all contributors
+# Copyright (C) all contributors
# License: AGPL-3.0+
# implements the small subset of Linux::Inotify2 functionality we use
# using IO::KQueue on *BSD systems.
package PublicInbox::KQNotify;
-use strict;
+use v5.12;
use IO::KQueue;
use PublicInbox::DSKQXS; # wraps IO::KQueue for fork-safe DESTROY
+use PublicInbox::FakeInotify qw(fill_dirlist on_dir_change);
+use Time::HiRes qw(stat);
-# only true as far as public-inbox is concerned with .lock files:
-sub IN_CLOSE () { NOTE_WRITE }
-#sub IN_CLOSE () { 0x200 } # NOTE_CLOSE_WRITE (FreeBSD 11+ only)
+# NOTE_EXTEND detects rename(2), NOTE_WRITE detects link(2)
+sub MOVED_TO_OR_CREATE () { NOTE_EXTEND|NOTE_WRITE }
sub new {
my ($class) = @_;
@@ -18,20 +19,32 @@ sub new {
}
sub watch {
- my ($self, $path, $mask, $cb) = @_;
- open(my $fh, '<', $path) or return;
+ my ($self, $path, $mask) = @_;
+ my ($fh, $watch);
+ if (-d $path) {
+ opendir($fh, $path) or return;
+ my @st = stat($fh);
+ $watch = bless [ $fh, $path, $st[10] ],
+ 'PublicInbox::KQNotify::Watchdir';
+ } else {
+ open($fh, '<', $path) or return;
+ $watch = bless [ $fh, $path ], 'PublicInbox::KQNotify::Watch';
+ }
my $ident = fileno($fh);
- $self->{dskq}->{kq}->EV_SET($ident, # ident
+ $self->{dskq}->{kq}->EV_SET($ident, # ident (fd)
EVFILT_VNODE, # filter
EV_ADD | EV_CLEAR, # flags
$mask, # fflags
0, 0); # data, udata
- if ($mask == IN_CLOSE) {
- $self->{watch}->{$ident} = [ $fh, $cb ];
+ if ($mask & (MOVED_TO_OR_CREATE|NOTE_DELETE|NOTE_LINK|NOTE_REVOKE)) {
+ $self->{watch}->{$ident} = $watch;
+ if ($mask & (NOTE_DELETE|NOTE_LINK|NOTE_REVOKE)) {
+ fill_dirlist($self, $path, $fh)
+ }
} else {
die "TODO Not implemented: $mask";
}
- bless \$fh, 'PublicInbox::KQNotify::Watch';
+ $watch;
}
# emulate Linux::Inotify::fileno
@@ -45,22 +58,57 @@ sub on_overflow {}
# noop for Linux::Inotify2 compatibility, we use `0' timeout for ->kevent
sub blocking {}
-# behave like Linux::Inotify2::poll
-sub poll {
+# behave like Linux::Inotify2->read
+sub read {
my ($self) = @_;
my @kevents = $self->{dskq}->{kq}->kevent(0);
+ my $events = [];
+ my @gone;
+ my $watch = $self->{watch};
for my $kev (@kevents) {
my $ident = $kev->[KQ_IDENT];
my $mask = $kev->[KQ_FFLAGS];
- if (($mask & IN_CLOSE) == IN_CLOSE) {
- eval { $self->{watch}->{$ident}->[1]->() };
+ my ($dh, $path, $old_ctime) = @{$watch->{$ident}};
+ if (!defined($old_ctime)) {
+ push @$events,
+ bless(\$path, 'PublicInbox::FakeInotify::Event')
+ } elsif ($mask & (MOVED_TO_OR_CREATE|NOTE_DELETE|NOTE_LINK|
+ NOTE_REVOKE|NOTE_RENAME)) {
+ my @new_st = stat($path);
+ if (!@new_st && $!{ENOENT}) {
+ push @$events, bless(\$path,
+ 'PublicInbox::FakeInotify::'.
+ 'SelfGoneEvent');
+ push @gone, $ident;
+ delete $self->{dirlist}->{$path};
+ next;
+ }
+ if (!@new_st) {
+ warn "unhandled stat($path) error: $!\n";
+ next;
+ }
+ $watch->{$ident}->[3] = $new_st[10]; # ctime
+ rewinddir($dh);
+ on_dir_change($events, $dh, $path, $old_ctime,
+ $self->{dirlist});
}
}
+ delete @$watch{@gone};
+ @$events;
}
package PublicInbox::KQNotify::Watch;
-use strict;
+use v5.12;
+
+sub name { $_[0]->[1] }
+
+sub cancel { close $_[0]->[0] or die "close: $!" }
+
+package PublicInbox::KQNotify::Watchdir;
+use v5.12;
+
+sub name { $_[0]->[1] }
-sub cancel { close ${$_[0]} or die "close: $!" }
+sub cancel { closedir $_[0]->[0] or die "closedir: $!" }
1;