From 5808636263d72b635a46100a7e7037074dad8f75 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 27 Jun 2020 10:03:35 +0000 Subject: kqnotify|fake_inotify: detect Maildir write ops We need to detect link(2) and rename(2) in other apps writing to the Maildir. We'll be removing the Filesys::Notify::Simple from -watch in favor of using IO::KQueue or Linux::Inotify2 directly. Ensure non-inotify emulations can support everything we expect for Maildir writers. --- lib/PublicInbox/FakeInotify.pm | 46 +++++++++++++++++++++++++++++++++++------- lib/PublicInbox/KQNotify.pm | 38 ++++++++++++++++++++++++++++------ 2 files changed, 71 insertions(+), 13 deletions(-) (limited to 'lib') diff --git a/lib/PublicInbox/FakeInotify.pm b/lib/PublicInbox/FakeInotify.pm index b077d63a..df63173f 100644 --- a/lib/PublicInbox/FakeInotify.pm +++ b/lib/PublicInbox/FakeInotify.pm @@ -6,10 +6,13 @@ package PublicInbox::FakeInotify; use strict; use Time::HiRes qw(stat); +use PublicInbox::DS; my $IN_CLOSE = 0x08 | 0x10; # match Linux inotify +# my $IN_MOVED_TO = 0x80; +# my $IN_CREATE = 0x100; +sub MOVED_TO_OR_CREATE () { 0x80 | 0x100 } my $poll_intvl = 2; # same as Filesys::Notify::Simple -my $for_cancel = bless \(my $x), 'PublicInbox::FakeInotify::Watch'; sub poll_once { my ($self) = @_; @@ -30,8 +33,22 @@ sub new { sub watch { my ($self, $path, $mask, $cb) = @_; my @st = stat($path) or return; - $self->{watch}->{"$path\0$mask"} = [ @st, $cb ]; - $for_cancel; + my $k = "$path\0$mask"; + $self->{watch}->{$k} = [ $st[10], $cb ]; # 10 - ctime + bless [ $self->{watch}, $k ], 'PublicInbox::FakeInotify::Watch'; +} + +sub on_new_files ($$$$) { + my ($dh, $cb, $path, $old_ctime) = @_; + while (defined(my $base = readdir($dh))) { + next if $base =~ /\A\.\.?\z/; + my $full = "$path/$base"; + my @st = stat($full); + if (@st && $st[10] > $old_ctime) { + bless \$full, 'PublicInbox::FakeInotify::Event'; + eval { $cb->(\$full) }; + } + } } # behaves like non-blocking Linux::Inotify2->poll @@ -43,17 +60,32 @@ sub poll { my @now = stat($path) or next; my $prv = $watch->{$x}; my $cb = $prv->[-1]; - # 10: ctime, 7: size - if ($prv->[10] != $now[10]) { + my $old_ctime = $prv->[0]; + if ($old_ctime != $now[10]) { if (($mask & $IN_CLOSE) == $IN_CLOSE) { eval { $cb->() }; + } elsif ($mask & MOVED_TO_OR_CREATE) { + opendir(my $dh, $path) or do { + warn "W: opendir $path: $!\n"; + next; + }; + on_new_files($dh, $cb, $path, $old_ctime); } } - @$prv = (@now, $cb); + @$prv = ($now[10], $cb); } } package PublicInbox::FakeInotify::Watch; -sub cancel {} # noop +use strict; + +sub cancel { + my ($self) = @_; + delete $self->[0]->{$self->[1]}; +} + +package PublicInbox::FakeInotify::Event; +use strict; +sub fullname { ${$_[0]} } 1; diff --git a/lib/PublicInbox/KQNotify.pm b/lib/PublicInbox/KQNotify.pm index 110594cc..9673b442 100644 --- a/lib/PublicInbox/KQNotify.pm +++ b/lib/PublicInbox/KQNotify.pm @@ -7,6 +7,11 @@ package PublicInbox::KQNotify; use strict; use IO::KQueue; use PublicInbox::DSKQXS; # wraps IO::KQueue for fork-safe DESTROY +use PublicInbox::FakeInotify; +use Time::HiRes qw(stat); + +# NOTE_EXTEND detects rename(2), NOTE_WRITE detects link(2) +sub MOVED_TO_OR_CREATE () { NOTE_EXTEND|NOTE_WRITE } sub new { my ($class) = @_; @@ -15,19 +20,28 @@ sub new { sub watch { my ($self, $path, $mask, $cb) = @_; - open(my $fh, '<', $path) or return; + my ($fh, $cls, @extra); + if (-d $path) { + opendir($fh, $path) or return; + my @st = stat($fh); + @extra = ($path, $st[10]); # 10: ctime + $cls = 'PublicInbox::KQNotify::Watchdir'; + } else { + open($fh, '<', $path) or return; + $cls = 'PublicInbox::KQNotify::Watch'; + } my $ident = fileno($fh); $self->{dskq}->{kq}->EV_SET($ident, # ident EVFILT_VNODE, # filter EV_ADD | EV_CLEAR, # flags $mask, # fflags 0, 0); # data, udata - if ($mask == NOTE_WRITE) { - $self->{watch}->{$ident} = [ $fh, $cb ]; + if ($mask == NOTE_WRITE || $mask == MOVED_TO_OR_CREATE) { + $self->{watch}->{$ident} = [ $fh, $cb, @extra ]; } else { die "TODO Not implemented: $mask"; } - bless \$fh, 'PublicInbox::KQNotify::Watch'; + bless \$fh, $cls; } # emulate Linux::Inotify::fileno @@ -48,8 +62,15 @@ sub poll { for my $kev (@kevents) { my $ident = $kev->[KQ_IDENT]; my $mask = $kev->[KQ_FFLAGS]; - if (($mask & NOTE_WRITE) == NOTE_WRITE) { - eval { $self->{watch}->{$ident}->[1]->() }; + my ($dh, $cb, $path, $old_ctime) = @{$self->{watch}->{$ident}}; + if (!defined($path) && ($mask & NOTE_WRITE) == NOTE_WRITE) { + eval { $cb->() }; + } elsif ($mask & MOVED_TO_OR_CREATE) { + my @new_st = stat($path) or next; + $self->{watch}->{$ident}->[3] = $new_st[10]; # ctime + rewinddir($dh); + PublicInbox::FakeInotify::on_new_files($dh, $cb, + $path, $old_ctime); } } } @@ -59,4 +80,9 @@ use strict; sub cancel { close ${$_[0]} or die "close: $!" } +package PublicInbox::KQNotify::Watchdir; +use strict; + +sub cancel { closedir ${$_[0]} or die "closedir: $!" } + 1; -- cgit v1.2.3-24-ge0c7