]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/KQNotify.pm
update copyrights for 2021
[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 IO::KQueue;
9 use PublicInbox::DSKQXS; # wraps IO::KQueue for fork-safe DESTROY
10 use PublicInbox::FakeInotify;
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 ],
32                         'PublicInbox::KQNotify::Watch';
33         }
34         my $ident = fileno($fh);
35         $self->{dskq}->{kq}->EV_SET($ident, # ident
36                 EVFILT_VNODE, # filter
37                 EV_ADD | EV_CLEAR, # flags
38                 $mask, # fflags
39                 0, 0); # data, udata
40         if ($mask == NOTE_WRITE || $mask == MOVED_TO_OR_CREATE) {
41                 $self->{watch}->{$ident} = $watch;
42         } else {
43                 die "TODO Not implemented: $mask";
44         }
45         $watch;
46 }
47
48 # emulate Linux::Inotify::fileno
49 sub fileno { ${$_[0]->{dskq}->{kq}} }
50
51 # noop for Linux::Inotify2 compatibility.  Unlike inotify,
52 # kqueue doesn't seem to overflow since it's limited by the number of
53 # open FDs the process has
54 sub on_overflow {}
55
56 # noop for Linux::Inotify2 compatibility, we use `0' timeout for ->kevent
57 sub blocking {}
58
59 # behave like Linux::Inotify2->read
60 sub read {
61         my ($self) = @_;
62         my @kevents = $self->{dskq}->{kq}->kevent(0);
63         my $events = [];
64         for my $kev (@kevents) {
65                 my $ident = $kev->[KQ_IDENT];
66                 my $mask = $kev->[KQ_FFLAGS];
67                 my ($dh, $path, $old_ctime) = @{$self->{watch}->{$ident}};
68                 if (!defined($old_ctime)) {
69                         push @$events,
70                                 bless(\$path, 'PublicInbox::FakeInotify::Event')
71                 } elsif ($mask & MOVED_TO_OR_CREATE) {
72                         my @new_st = stat($path) or next;
73                         $self->{watch}->{$ident}->[3] = $new_st[10]; # ctime
74                         rewinddir($dh);
75                         PublicInbox::FakeInotify::on_new_files($events, $dh,
76                                                         $path, $old_ctime);
77                 }
78         }
79         @$events;
80 }
81
82 package PublicInbox::KQNotify::Watch;
83 use strict;
84
85 sub name { $_[0]->[1] }
86
87 sub cancel { close $_[0]->[0] or die "close: $!" }
88
89 package PublicInbox::KQNotify::Watchdir;
90 use strict;
91
92 sub name { $_[0]->[1] }
93
94 sub cancel { closedir $_[0]->[0] or die "closedir: $!" }
95
96 1;