]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/KQNotify.pm
No ext_urls
[public-inbox.git] / lib / PublicInbox / KQNotify.pm
1 # Copyright (C) all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
3
4 # implements the small subset of Linux::Inotify2 functionality we use
5 # using IO::KQueue on *BSD systems.
6 package PublicInbox::KQNotify;
7 use v5.12;
8 use IO::KQueue;
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);
12
13 # NOTE_EXTEND detects rename(2), NOTE_WRITE detects link(2)
14 sub MOVED_TO_OR_CREATE () { NOTE_EXTEND|NOTE_WRITE }
15
16 sub new {
17         my ($class) = @_;
18         bless { dskq => PublicInbox::DSKQXS->new, watch => {} }, $class;
19 }
20
21 sub watch {
22         my ($self, $path, $mask) = @_;
23         my ($fh, $watch);
24         if (-d $path) {
25                 opendir($fh, $path) or return;
26                 my @st = stat($fh);
27                 $watch = bless [ $fh, $path, $st[10] ],
28                         'PublicInbox::KQNotify::Watchdir';
29         } else {
30                 open($fh, '<', $path) or return;
31                 $watch = bless [ $fh, $path ], 'PublicInbox::KQNotify::Watch';
32         }
33         my $ident = fileno($fh);
34         $self->{dskq}->{kq}->EV_SET($ident, # ident (fd)
35                 EVFILT_VNODE, # filter
36                 EV_ADD | EV_CLEAR, # flags
37                 $mask, # fflags
38                 0, 0); # data, udata
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)
43                 }
44         } else {
45                 die "TODO Not implemented: $mask";
46         }
47         $watch;
48 }
49
50 # emulate Linux::Inotify::fileno
51 sub fileno { ${$_[0]->{dskq}->{kq}} }
52
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
56 sub on_overflow {}
57
58 # noop for Linux::Inotify2 compatibility, we use `0' timeout for ->kevent
59 sub blocking {}
60
61 # behave like Linux::Inotify2->read
62 sub read {
63         my ($self) = @_;
64         my @kevents = $self->{dskq}->{kq}->kevent(0);
65         my $events = [];
66         my @gone;
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)) {
73                         push @$events,
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::'.
81                                                 'SelfGoneEvent');
82                                 push @gone, $ident;
83                                 delete $self->{dirlist}->{$path};
84                                 next;
85                         }
86                         if (!@new_st) {
87                                 warn "unhandled stat($path) error: $!\n";
88                                 next;
89                         }
90                         $watch->{$ident}->[3] = $new_st[10]; # ctime
91                         rewinddir($dh);
92                         on_dir_change($events, $dh, $path, $old_ctime,
93                                         $self->{dirlist});
94                 }
95         }
96         delete @$watch{@gone};
97         @$events;
98 }
99
100 package PublicInbox::KQNotify::Watch;
101 use v5.12;
102
103 sub name { $_[0]->[1] }
104
105 sub cancel { close $_[0]->[0] or die "close: $!" }
106
107 package PublicInbox::KQNotify::Watchdir;
108 use v5.12;
109
110 sub name { $_[0]->[1] }
111
112 sub cancel { closedir $_[0]->[0] or die "closedir: $!" }
113
114 1;