1 # Copyright (C) all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
4 # implements the small subset of Linux::Inotify2 functionality we use
5 # using IO::KQueue on *BSD systems.
6 package PublicInbox::KQNotify;
9 use PublicInbox::DSKQXS; # wraps IO::KQueue for fork-safe DESTROY
10 use PublicInbox::FakeInotify qw(fill_dirlist on_dir_change);
11 use Time::HiRes qw(stat);
13 # NOTE_EXTEND detects rename(2), NOTE_WRITE detects link(2)
14 sub MOVED_TO_OR_CREATE () { NOTE_EXTEND|NOTE_WRITE }
18 bless { dskq => PublicInbox::DSKQXS->new, watch => {} }, $class;
22 my ($self, $path, $mask) = @_;
25 opendir($fh, $path) or return;
27 $watch = bless [ $fh, $path, $st[10] ],
28 'PublicInbox::KQNotify::Watchdir';
30 open($fh, '<', $path) or return;
31 $watch = bless [ $fh, $path ], 'PublicInbox::KQNotify::Watch';
33 my $ident = fileno($fh);
34 $self->{dskq}->{kq}->EV_SET($ident, # ident (fd)
35 EVFILT_VNODE, # filter
36 EV_ADD | EV_CLEAR, # flags
39 if ($mask & (MOVED_TO_OR_CREATE|NOTE_DELETE|NOTE_LINK|NOTE_REVOKE)) {
40 $self->{watch}->{$ident} = $watch;
41 if ($mask & (NOTE_DELETE|NOTE_LINK|NOTE_REVOKE)) {
42 fill_dirlist($self, $path, $fh)
45 die "TODO Not implemented: $mask";
50 # emulate Linux::Inotify::fileno
51 sub fileno { ${$_[0]->{dskq}->{kq}} }
53 # noop for Linux::Inotify2 compatibility. Unlike inotify,
54 # kqueue doesn't seem to overflow since it's limited by the number of
55 # open FDs the process has
58 # noop for Linux::Inotify2 compatibility, we use `0' timeout for ->kevent
61 # behave like Linux::Inotify2->read
64 my @kevents = $self->{dskq}->{kq}->kevent(0);
67 my $watch = $self->{watch};
68 for my $kev (@kevents) {
69 my $ident = $kev->[KQ_IDENT];
70 my $mask = $kev->[KQ_FFLAGS];
71 my ($dh, $path, $old_ctime) = @{$watch->{$ident}};
72 if (!defined($old_ctime)) {
74 bless(\$path, 'PublicInbox::FakeInotify::Event')
75 } elsif ($mask & (MOVED_TO_OR_CREATE|NOTE_DELETE|NOTE_LINK|
76 NOTE_REVOKE|NOTE_RENAME)) {
77 my @new_st = stat($path);
78 if (!@new_st && $!{ENOENT}) {
79 push @$events, bless(\$path,
80 'PublicInbox::FakeInotify::'.
83 delete $self->{dirlist}->{$path};
87 warn "unhandled stat($path) error: $!\n";
90 $watch->{$ident}->[3] = $new_st[10]; # ctime
92 on_dir_change($events, $dh, $path, $old_ctime,
96 delete @$watch{@gone};
100 package PublicInbox::KQNotify::Watch;
103 sub name { $_[0]->[1] }
105 sub cancel { close $_[0]->[0] or die "close: $!" }
107 package PublicInbox::KQNotify::Watchdir;
110 sub name { $_[0]->[1] }
112 sub cancel { closedir $_[0]->[0] or die "closedir: $!" }