about summary refs log tree commit homepage
path: root/lib
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2023-12-28 04:23:00 +0000
committerEric Wong <e@80x24.org>2023-12-29 16:56:08 +0000
commitcf977e706b07e80f394570a393eb2169b9b9a1a7 (patch)
treecaa1a8a4cad1ec89e59658fc088a3b5054dbf58a /lib
parent84874a852c80e3d4eb96af14c017b37424cdf840 (diff)
downloadpublic-inbox-cf977e706b07e80f394570a393eb2169b9b9a1a7.tar.gz
This is a step towards improving the out-of-the-box experience
in achieving notifications without XS, extra downloads, and .so
loading + runtime mmap overhead.

This also fixes loongarch support of all Linux syscalls due to
a bad regexp :x

All the reachable Linux architectures listed at
<https://portal.cfarm.net/machines/list/> should be supported.
At the moment, there appears to be no reachable sparc* Linux
machines available to cfarm users.

Fixes: b0e5093aa3572a86 (syscall: add support for riscv64, 2022-08-11)
Diffstat (limited to 'lib')
-rw-r--r--lib/PublicInbox/DirIdle.pm16
-rw-r--r--lib/PublicInbox/In3Event.pm24
-rw-r--r--lib/PublicInbox/In3Watch.pm20
-rw-r--r--lib/PublicInbox/InboxIdle.pm6
-rw-r--r--lib/PublicInbox/Inotify.pm27
-rw-r--r--lib/PublicInbox/Inotify3.pm115
-rw-r--r--lib/PublicInbox/Syscall.pm37
-rw-r--r--lib/PublicInbox/TailNotify.pm6
8 files changed, 231 insertions, 20 deletions
diff --git a/lib/PublicInbox/DirIdle.pm b/lib/PublicInbox/DirIdle.pm
index e6a326ab..230df166 100644
--- a/lib/PublicInbox/DirIdle.pm
+++ b/lib/PublicInbox/DirIdle.pm
@@ -10,12 +10,12 @@ use PublicInbox::In2Tie;
 
 my ($MAIL_IN, $MAIL_GONE, $ino_cls);
 if ($^O eq 'linux' && eval { require PublicInbox::Inotify; 1 }) {
-        $MAIL_IN = Linux::Inotify2::IN_MOVED_TO() |
-                Linux::Inotify2::IN_CREATE();
-        $MAIL_GONE = Linux::Inotify2::IN_DELETE() |
-                        Linux::Inotify2::IN_DELETE_SELF() |
-                        Linux::Inotify2::IN_MOVE_SELF() |
-                        Linux::Inotify2::IN_MOVED_FROM();
+        $MAIL_IN = PublicInbox::Inotify::IN_MOVED_TO() |
+                PublicInbox::Inotify::IN_CREATE();
+        $MAIL_GONE = PublicInbox::Inotify::IN_DELETE() |
+                        PublicInbox::Inotify::IN_DELETE_SELF() |
+                        PublicInbox::Inotify::IN_MOVE_SELF() |
+                        PublicInbox::Inotify::IN_MOVED_FROM();
         $ino_cls = 'PublicInbox::Inotify';
 # Perl 5.22+ is needed for fileno(DIRHANDLE) support:
 } elsif ($^V ge v5.22 && eval { require PublicInbox::KQNotify }) {
@@ -79,7 +79,7 @@ sub event_step {
         my $cb = $self->{cb} or return;
         local $PublicInbox::DS::in_loop = 0; # waitpid() synchronously (FIXME)
         eval {
-                my @events = $self->{inot}->read; # Linux::Inotify2->read
+                my @events = $self->{inot}->read; # Inotify3->read
                 $cb->($_) for @events;
         };
         warn "$self->{inot}->read err: $@\n" if $@;
@@ -88,7 +88,7 @@ sub event_step {
 sub force_close {
         my ($self) = @_;
         my $inot = delete $self->{inot} // return;
-        if ($inot->can('fh')) { # Linux::Inotify2 2.3+
+        if ($inot->can('fh')) { # Inotify3 or Linux::Inotify2 2.3+
                 $inot->fh->close or warn "CLOSE ERROR: $!";
         } elsif ($inot->isa('Linux::Inotify2')) {
                 require PublicInbox::LI2Wrap;
diff --git a/lib/PublicInbox/In3Event.pm b/lib/PublicInbox/In3Event.pm
new file mode 100644
index 00000000..f93dc0da
--- /dev/null
+++ b/lib/PublicInbox/In3Event.pm
@@ -0,0 +1,24 @@
+# Copyright (C) all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+
+# duck-type compatible with Linux::Inotify2::Event for pure Perl
+# PublicInbox::Inotify3 w/o callback support
+package PublicInbox::In3Event;
+use v5.12;
+
+sub w { $_[0]->[2] } # PublicInbox::In3Watch
+sub mask { $_[0]->[0] }
+sub name { $_[0]->[1] }
+
+sub fullname {
+        my ($name, $wname) = ($_[0]->[1], $_[0]->[2]->name);
+        length($name) ? "$wname/$name" : $wname;
+}
+
+my $buf = '';
+while (my ($sym, $mask) = each %PublicInbox::Inotify3::events) {
+        $buf .= "sub $sym { \$_[0]->[0] & $mask }\n";
+}
+eval $buf;
+
+1;
diff --git a/lib/PublicInbox/In3Watch.pm b/lib/PublicInbox/In3Watch.pm
new file mode 100644
index 00000000..bdb91869
--- /dev/null
+++ b/lib/PublicInbox/In3Watch.pm
@@ -0,0 +1,20 @@
+# Copyright (C) all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+
+# duck-type compatible with Linux::Inotify2::Watch for pure Perl
+# PublicInbox::Inotify3 for our needs, only
+package PublicInbox::In3Watch;
+use v5.12;
+
+sub mask { $_[0]->[1] }
+sub name { $_[0]->[2] }
+
+sub cancel {
+        my ($self) = @_;
+        my ($wd, $in3) = @$self[0, 3];
+        $in3 or return 1; # already canceled
+        pop @$self;
+        $in3->rm_watch($wd);
+}
+
+1;
diff --git a/lib/PublicInbox/InboxIdle.pm b/lib/PublicInbox/InboxIdle.pm
index 4231c0a0..3c4d4a68 100644
--- a/lib/PublicInbox/InboxIdle.pm
+++ b/lib/PublicInbox/InboxIdle.pm
@@ -11,7 +11,7 @@ use PublicInbox::Syscall qw(EPOLLIN);
 my $IN_MODIFY = 0x02; # match Linux inotify
 my $ino_cls;
 if ($^O eq 'linux' && eval { require PublicInbox::Inotify }) {
-        $IN_MODIFY = Linux::Inotify2::IN_MODIFY();
+        $IN_MODIFY = PublicInbox::Inotify::IN_MODIFY();
         $ino_cls = 'PublicInbox::Inotify';
 } elsif (eval { require PublicInbox::KQNotify }) {
         $IN_MODIFY = PublicInbox::KQNotify::NOTE_WRITE();
@@ -34,7 +34,7 @@ sub in2_arm ($$) { # PublicInbox::Config::each_inbox callback
                 $ibx->{unlock_subs} = $old_ibx->{unlock_subs};
                 %{$ibx->{unlock_subs}} = (%$u, %{$ibx->{unlock_subs}}) if $u;
 
-                # Linux::Inotify2::Watch::name matches if watches are the
+                # *::Inotify*::Watch::name matches if watches are the
                 # same, no point in replacing a watch of the same name
                 if ($cur->[1]->name eq $lock) {
                         $self->{on_unlock}->{$lock} = $ibx;
@@ -87,7 +87,7 @@ sub new {
 sub event_step {
         my ($self) = @_;
         eval {
-                my @events = $self->{inot}->read; # Linux::Inotify2::read
+                my @events = $self->{inot}->read; # PublicInbox::Inotify3::read
                 my $on_unlock = $self->{on_unlock};
                 for my $ev (@events) {
                         my $fn = $ev->fullname // next; # cancelled
diff --git a/lib/PublicInbox/Inotify.pm b/lib/PublicInbox/Inotify.pm
index 3ef271c8..c4f1ae84 100644
--- a/lib/PublicInbox/Inotify.pm
+++ b/lib/PublicInbox/Inotify.pm
@@ -5,12 +5,29 @@
 package PublicInbox::Inotify;
 use v5.12;
 our @ISA;
-BEGIN {
-        eval { require Linux::Inotify2 };
-        if ($@) { # TODO: get rid of XS dependency
-                die "W: Linux::Inotify2 missing: $@\n";
+BEGIN { # prefer pure Perl since it works out-of-the-box
+        my $isa;
+        for my $m (qw(PublicInbox::Inotify3 Linux::Inotify2)) {
+                eval "require $m";
+                next if $@;
+                $isa = $m;
+        }
+        if ($isa) {
+                push @ISA, $isa;
+                my $buf = '';
+                for (qw(IN_MOVED_TO IN_CREATE IN_DELETE IN_DELETE_SELF
+                                IN_MOVE_SELF IN_MOVED_FROM IN_MODIFY)) {
+                        $buf .= "*$_ = \\&PublicInbox::Inotify3::$_;\n";
+                }
+                eval $buf;
+                die $@ if $@;
         } else {
-                push @ISA, 'Linux::Inotify2';
+                die <<EOM;
+W: inotify syscall numbers unknown on your platform and
+W: Linux::Inotify2 missing: $@
+W: public-inbox hackers welcome the plain-text output of ./devel/sysdefs-list
+W: at meta\@public-inbox.org
+EOM
         }
 };
 
diff --git a/lib/PublicInbox/Inotify3.pm b/lib/PublicInbox/Inotify3.pm
new file mode 100644
index 00000000..4f337a7a
--- /dev/null
+++ b/lib/PublicInbox/Inotify3.pm
@@ -0,0 +1,115 @@
+# Copyright (C) all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+
+# Implements most Linux::Inotify2 functionality we need in pure Perl
+# Anonymous sub support isn't supported since it's expensive in the
+# best case and likely leaky in older Perls (e.g. 5.16.3)
+package PublicInbox::Inotify3;
+use v5.12;
+use autodie qw(open);
+use PublicInbox::Syscall ();
+use Carp;
+use Scalar::Util ();
+
+# this fails if undefined no unsupported platforms
+use constant $PublicInbox::Syscall::INOTIFY;
+our %events;
+
+# extracted from devel/sysdefs-list output, these should be arch-independent
+BEGIN {
+%events = (
+        IN_ACCESS => 0x1,
+        IN_ALL_EVENTS => 0xfff,
+        IN_ATTRIB => 0x4,
+        IN_CLOSE => 0x18,
+        IN_CLOSE_NOWRITE => 0x10,
+        IN_CLOSE_WRITE => 0x8,
+        IN_CREATE => 0x100,
+        IN_DELETE => 0x200,
+        IN_DELETE_SELF => 0x400,
+        IN_DONT_FOLLOW => 0x2000000,
+        IN_EXCL_UNLINK => 0x4000000,
+        IN_IGNORED => 0x8000,
+        IN_ISDIR => 0x40000000,
+        IN_MASK_ADD => 0x20000000,
+        IN_MODIFY => 0x2,
+        IN_MOVE => 0xc0,
+        IN_MOVED_FROM => 0x40,
+        IN_MOVED_TO => 0x80,
+        IN_MOVE_SELF => 0x800,
+        IN_ONESHOT => 0x80000000,
+        IN_ONLYDIR => 0x1000000,
+        IN_OPEN => 0x20,
+        IN_Q_OVERFLOW => 0x4000,
+        IN_UNMOUNT => 0x2000,
+);
+} # /BEGIN
+use constant \%events;
+require PublicInbox::In3Event; # uses %events
+require PublicInbox::In3Watch; # uses SYS_inotify_rm_watch
+
+use constant autocancel =>
+        (IN_IGNORED|IN_UNMOUNT|IN_ONESHOT|IN_DELETE_SELF);
+
+sub new {
+        open my $fh, '+<&=', syscall(SYS_inotify_init1, IN_CLOEXEC);
+        bless { fh => $fh }, __PACKAGE__;
+}
+
+sub read {
+        my ($self) = @_;
+        my (@ret, $wd, $mask, $len, $name, $size, $buf);
+        my $r = sysread($self->{fh}, my $rbuf, 8192);
+        if ($r) {
+                while ($r) {
+                        ($wd, $mask, undef, $len) = unpack('lLLL', $rbuf);
+                        $size = 16 + $len; # 16: sizeof(struct inotify_event)
+                        substr($rbuf, 0, 16, '');
+                        $name = $len ? unpack('Z*', substr($rbuf, 0, $len, ''))
+                                        : undef;
+                        $r -= $size;
+                        next if $self->{ignore}->{$wd};
+                        my $ev = bless [$mask, $name], 'PublicInbox::In3Event';
+                        push @ret, $ev;
+                        if (my $w = $self->{w}->{$wd}) {
+                                $ev->[2] = $w;
+                                $w->cancel if $ev->mask & autocancel;
+                        } elsif ($mask & IN_Q_OVERFLOW) {
+                                carp 'E: IN_Q_OVERFLOW, too busy? (non-fatal)'
+                        } else {
+                                carp "BUG? wd:$wd unknown (non-fatal)";
+                        }
+                }
+        } elsif (defined($r) || ($!{EAGAIN} || $!{EINTR})) {
+        } else {
+                croak "inotify read: $!";
+        }
+        delete $self->{ignore};
+        @ret;
+}
+
+sub fileno { CORE::fileno($_[0]->{fh}) }
+
+sub fh { $_[0]->{fh} }
+
+sub blocking { shift->{fh}->blocking(@_) }
+
+sub watch {
+        my ($self, $name, $mask, $cb) = @_;
+        croak "E: $cb not supported" if $cb; # too much memory
+        my $wd = syscall(SYS_inotify_add_watch, $self->fileno, $name, $mask);
+        return if $wd < 0;
+        my $w = bless [ $wd, $mask, $name, $self ], 'PublicInbox::In3Watch';
+        $self->{w}->{$wd} = $w;
+        Scalar::Util::weaken($w->[3]); # ugh
+        $w;
+}
+
+sub rm_watch {
+        my ($self, $wd) = @_;
+        delete $self->{w}->{$wd};
+        $self->{ignore}->{$wd} = 1; # is this needed?
+        syscall(SYS_inotify_rm_watch, $self->fileno, $wd) < 0 ? undef : 1;
+}
+
+1;
diff --git a/lib/PublicInbox/Syscall.pm b/lib/PublicInbox/Syscall.pm
index 78181bb6..96af2b22 100644
--- a/lib/PublicInbox/Syscall.pm
+++ b/lib/PublicInbox/Syscall.pm
@@ -22,6 +22,7 @@ use POSIX qw(ENOENT ENOSYS EINVAL O_NONBLOCK);
 use Socket qw(SOL_SOCKET SCM_RIGHTS);
 use Config;
 our %SIGNUM = (WINCH => 28); # most Linux, {Free,Net,Open}BSD, *Darwin
+our $INOTIFY;
 
 # $VERSION = '0.25'; # Sys::Syscall version
 our @EXPORT_OK = qw(epoll_ctl epoll_create epoll_wait
@@ -98,6 +99,11 @@ if ($^O eq "linux") {
         $SYS_fstatfs = 100;
         $SYS_sendmsg = 370;
         $SYS_recvmsg = 372;
+        $INOTIFY = { # usage: `use constant $PublicInbox::Syscall::INOTIFY'
+                SYS_inotify_init1 => 332,
+                SYS_inotify_add_watch => 292,
+                SYS_inotify_rm_watch => 293,
+        };
         $FS_IOC_GETFLAGS = 0x80046601;
         $FS_IOC_SETFLAGS = 0x40046602;
     } elsif ($machine eq "x86_64") {
@@ -109,6 +115,11 @@ if ($^O eq "linux") {
         $SYS_fstatfs = 138;
         $SYS_sendmsg = 46;
         $SYS_recvmsg = 47;
+        $INOTIFY = {
+                SYS_inotify_init1 => 294,
+                SYS_inotify_add_watch => 254,
+                SYS_inotify_rm_watch => 255,
+        };
         $FS_IOC_GETFLAGS = 0x80086601;
         $FS_IOC_SETFLAGS = 0x40086602;
     } elsif ($machine eq 'x32') {
@@ -122,6 +133,11 @@ if ($^O eq "linux") {
         $SYS_recvmsg = 0x40000207;
         $FS_IOC_GETFLAGS = 0x80046601;
         $FS_IOC_SETFLAGS = 0x40046602;
+        $INOTIFY = {
+                SYS_inotify_init1 => 1073742118,
+                SYS_inotify_add_watch => 1073742078,
+                SYS_inotify_rm_watch => 1073742079,
+        };
     } elsif ($machine eq 'sparc64') {
         $SYS_epoll_create = 193;
         $SYS_epoll_ctl = 194;
@@ -154,6 +170,11 @@ if ($^O eq "linux") {
         $SYS_recvmsg = 342;
         $FS_IOC_GETFLAGS = 0x40086601;
         $FS_IOC_SETFLAGS = 0x80086602;
+        $INOTIFY = {
+                SYS_inotify_init1 => 318,
+                SYS_inotify_add_watch => 276,
+                SYS_inotify_rm_watch => 277,
+        };
     } elsif ($machine eq "ppc") {
         $SYS_epoll_create = 236;
         $SYS_epoll_ctl    = 237;
@@ -188,7 +209,7 @@ if ($^O eq "linux") {
         $u64_mod_8        = 1;
         $SYS_signalfd4 = 484;
         $SFD_CLOEXEC = 010000000;
-    } elsif ($machine =~ /\A(?:loong)?aarch64\z/ || $machine eq 'riscv64') {
+    } elsif ($machine =~ /\A(?:loong|a)arch64\z/ || $machine eq 'riscv64') {
         $SYS_epoll_create = 20;  # (sys_epoll_create1)
         $SYS_epoll_ctl    = 21;
         $SYS_epoll_wait   = 22;  # (sys_epoll_pwait)
@@ -199,6 +220,11 @@ if ($^O eq "linux") {
         $SYS_fstatfs = 44;
         $SYS_sendmsg = 211;
         $SYS_recvmsg = 212;
+        $INOTIFY = {
+                SYS_inotify_init1 => 26,
+                SYS_inotify_add_watch => 27,
+                SYS_inotify_rm_watch => 28,
+        };
         $FS_IOC_GETFLAGS = 0x80086601;
         $FS_IOC_SETFLAGS = 0x40086602;
     } elsif ($machine =~ m/arm(v\d+)?.*l/) { # ARM OABI (untested on cfarm)
@@ -236,6 +262,11 @@ if ($^O eq "linux") {
         $FS_IOC_GETFLAGS = 0x40046601;
         $FS_IOC_SETFLAGS = 0x80046602;
         $SIGNUM{WINCH} = 20;
+        $INOTIFY = {
+                SYS_inotify_init1 => 4329,
+                SYS_inotify_add_watch => 4285,
+                SYS_inotify_rm_watch => 4286,
+        };
     } else {
         warn <<EOM;
 machine=$machine ptrsize=$Config{ptrsize} has no syscall definitions
@@ -251,6 +282,10 @@ EOM
         *epoll_ctl = \&epoll_ctl_mod4;
     }
 }
+
+# SFD_CLOEXEC is arch-dependent, so IN_CLOEXEC may be, too
+$INOTIFY->{IN_CLOEXEC} //= 0x80000 if $INOTIFY;
+
 # use Inline::C for *BSD-only or general POSIX stuff.
 # Linux guarantees stable syscall numbering, BSDs only offer a stable libc
 # use devel/sysdefs-list on Linux to detect new syscall numbers and
diff --git a/lib/PublicInbox/TailNotify.pm b/lib/PublicInbox/TailNotify.pm
index bdb92d54..84340a35 100644
--- a/lib/PublicInbox/TailNotify.pm
+++ b/lib/PublicInbox/TailNotify.pm
@@ -9,9 +9,9 @@ use PublicInbox::DS qw(now);
 
 my ($TAIL_MOD, $ino_cls);
 if ($^O eq 'linux' && eval { require PublicInbox::Inotify; 1 }) {
-        $TAIL_MOD = Linux::Inotify2::IN_MOVED_TO() |
-                Linux::Inotify2::IN_CREATE() |
-                Linux::Inotify2::IN_MODIFY();
+        $TAIL_MOD = PublicInbox::Inotify::IN_MOVED_TO() |
+                PublicInbox::Inotify::IN_CREATE() |
+                PublicInbox::Inotify::IN_MODIFY();
         $ino_cls = 'PublicInbox::Inotify';
 } elsif (eval { require PublicInbox::KQNotify }) {
         $TAIL_MOD = PublicInbox::KQNotify::MOVED_TO_OR_CREATE() |