]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/KQNotify.pm
imap+nntp: share COMPRESS implementation
[public-inbox.git] / lib / PublicInbox / KQNotify.pm
1 # Copyright (C) 2020-2021 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 strict;
8 use v5.10.1;
9 use IO::KQueue;
10 use PublicInbox::DSKQXS; # wraps IO::KQueue for fork-safe DESTROY
11 use PublicInbox::FakeInotify qw(fill_dirlist on_dir_change);
12 use Time::HiRes qw(stat);
13
14 # NOTE_EXTEND detects rename(2), NOTE_WRITE detects link(2)
15 sub MOVED_TO_OR_CREATE () { NOTE_EXTEND|NOTE_WRITE }
16
17 sub new {
18         my ($class) = @_;
19         bless { dskq => PublicInbox::DSKQXS->new, watch => {} }, $class;
20 }
21
22 sub watch {
23         my ($self, $path, $mask) = @_;
24         my ($fh, $watch);
25         if (-d $path) {
26                 opendir($fh, $path) or return;
27                 my @st = stat($fh);
28                 $watch = bless [ $fh, $path, $st[10] ],
29                         'PublicInbox::KQNotify::Watchdir';
30         } else {
31                 open($fh, '<', $path) or return;
32                 $watch = bless [ $fh, $path ],
33                         'PublicInbox::KQNotify::Watch';
34         }
35         my $ident = fileno($fh);
36         $self->{dskq}->{kq}->EV_SET($ident, # ident (fd)
37                 EVFILT_VNODE, # filter
38                 EV_ADD | EV_CLEAR, # flags
39                 $mask, # fflags
40                 0, 0); # data, udata
41         if ($mask & (MOVED_TO_OR_CREATE|NOTE_DELETE|NOTE_LINK|NOTE_REVOKE)) {
42                 $self->{watch}->{$ident} = $watch;
43                 if ($mask & (NOTE_DELETE|NOTE_LINK|NOTE_REVOKE)) {
44                         fill_dirlist($self, $path, $fh)
45                 }
46         } else {
47                 die "TODO Not implemented: $mask";
48         }
49         $watch;
50 }
51
52 # emulate Linux::Inotify::fileno
53 sub fileno { ${$_[0]->{dskq}->{kq}} }
54
55 # noop for Linux::Inotify2 compatibility.  Unlike inotify,
56 # kqueue doesn't seem to overflow since it's limited by the number of
57 # open FDs the process has
58 sub on_overflow {}
59
60 # noop for Linux::Inotify2 compatibility, we use `0' timeout for ->kevent
61 sub blocking {}
62
63 # behave like Linux::Inotify2->read
64 sub read {
65         my ($self) = @_;
66         my @kevents = $self->{dskq}->{kq}->kevent(0);
67         my $events = [];
68         my @gone;
69         my $watch = $self->{watch};
70         for my $kev (@kevents) {
71                 my $ident = $kev->[KQ_IDENT];
72                 my $mask = $kev->[KQ_FFLAGS];
73                 my ($dh, $path, $old_ctime) = @{$watch->{$ident}};
74                 if (!defined($old_ctime)) {
75                         push @$events,
76                                 bless(\$path, 'PublicInbox::FakeInotify::Event')
77                 } elsif ($mask & (MOVED_TO_OR_CREATE|NOTE_DELETE|NOTE_LINK|
78                                 NOTE_REVOKE|NOTE_RENAME)) {
79                         my @new_st = stat($path);
80                         if (!@new_st && $!{ENOENT}) {
81                                 push @$events, bless(\$path,
82                                                 'PublicInbox::FakeInotify::'.
83                                                 'SelfGoneEvent');
84                                 push @gone, $ident;
85                                 delete $self->{dirlist}->{$path};
86                                 next;
87                         }
88                         if (!@new_st) {
89                                 warn "unhandled stat($path) error: $!\n";
90                                 next;
91                         }
92                         $watch->{$ident}->[3] = $new_st[10]; # ctime
93                         rewinddir($dh);
94                         on_dir_change($events, $dh, $path, $old_ctime,
95                                         $self->{dirlist});
96                 }
97         }
98         delete @$watch{@gone};
99         @$events;
100 }
101
102 package PublicInbox::KQNotify::Watch;
103 use strict;
104
105 sub name { $_[0]->[1] }
106
107 sub cancel { close $_[0]->[0] or die "close: $!" }
108
109 package PublicInbox::KQNotify::Watchdir;
110 use strict;
111
112 sub name { $_[0]->[1] }
113
114 sub cancel { closedir $_[0]->[0] or die "closedir: $!" }
115
116 1;