]> git.ipfire.org Git - thirdparty/public-inbox.git/commitdiff
support inotify on FreeBSD 15+
authorEric Wong <e@80x24.org>
Tue, 13 Jan 2026 10:51:25 +0000 (10:51 +0000)
committerEric Wong <e@80x24.org>
Wed, 14 Jan 2026 22:00:46 +0000 (22:00 +0000)
FreeBSD 15+ introduces a Linux-compatible inotify API.  The
inotify API is superior to EVFILT_VNODE with kqueue(2) since
kqueue requires file descriptors remain open.  Inotify watch
descriptors should use less kernel memory than file descriptors
and is governed by platform-specific limits rather than
RLIMIT_NOFILE.

devel/sysdefs-list
lib/PublicInbox/Inotify3.pm
lib/PublicInbox/Syscall.pm
t/inotify3.t

index f84befe3ed7e18a4803c7faa3bdd820c7d32cb99..23cdaed5095841e5833e60751931708907918653 100755 (executable)
@@ -62,7 +62,13 @@ __DATA__
 #ifdef HAVE_SYS_FILIO_H
 #      include <sys/filio.h>
 #endif
+#if defined(__FreeBSD__) && __FreeBSD__ >= 15
+#      define HAZ_INOTIFY
+#      include <sys/inotify.h>
+#      include <sys/specialfd.h>
+#endif
 #ifdef __linux__
+#      define HAZ_INOTIFY
 #      include <linux/fs.h>
 #      include <sys/epoll.h>
 #      include <sys/inotify.h>
@@ -107,11 +113,9 @@ int main(void)
        printf("'sizeof(size_t)' => %zu,\n", sizeof(size_t));
        printf("'sizeof(off_t)' => %zu,\n", sizeof(off_t));
 
-#ifdef __linux__
-       D(SYS_epoll_create1);
-       D(SYS_epoll_ctl);
-       MAYBE D(SYS_epoll_pwait);
-
+#ifdef HAZ_INOTIFY
+       D(SYS_inotify_rm_watch);
+       X(IN_NONBLOCK);
        X(IN_CLOEXEC);
        X(IN_ACCESS);
        X(IN_ALL_EVENTS);
@@ -137,10 +141,22 @@ int main(void)
        X(IN_OPEN);
        X(IN_Q_OVERFLOW);
        X(IN_UNMOUNT);
+       STRUCT_BEGIN(struct inotify_event);
+               PR_NUM(wd);
+               PR_NUM(mask);
+               PR_NUM(cookie);
+               PR_NUM(len);
+               PR_OFF(name);
+       STRUCT_END;
+#endif /* HAZ_INOTIFY */
+
+#ifdef __linux__
+       D(SYS_epoll_create1);
+       D(SYS_epoll_ctl);
+       MAYBE D(SYS_epoll_pwait);
 
        D(SYS_inotify_init1);
        D(SYS_inotify_add_watch);
-       D(SYS_inotify_rm_watch);
 
        D(SYS_prctl);
        D(SYS_fstatfs);
@@ -156,19 +172,22 @@ int main(void)
                PR_NUM(data.u64);
        STRUCT_END;
 
-       STRUCT_BEGIN(struct inotify_event);
-               PR_NUM(wd);
-               PR_NUM(mask);
-               PR_NUM(cookie);
-               PR_NUM(len);
-               PR_OFF(name);
-       STRUCT_END;
-
        STRUCT_BEGIN(struct statfs);
                PR_NUM(f_type);
        STRUCT_END;
 #endif /* Linux, any other OSes with stable syscalls? */
 
+/* FreeBSD 15+ has inotify, maybe others will follow */
+#if defined(__FreeBSD__) && __FreeBSD__ >= 15
+       D(SYS___specialfd);
+       D(SPECIALFD_INOTIFY);
+       STRUCT_BEGIN(struct specialfd_inotify);
+               PR_NUM(flags);
+       STRUCT_END;
+       D(SYS_inotify_add_watch_at);
+       D(AT_FDCWD);
+#endif /* FreeBSD >= 15 */
+
        D(SIGWINCH);
        MAYBE X(FIONREAD);
        MAYBE D(SO_ACCEPTFILTER);
index 4f337a7a608626a25bd5cbcfb317a6c0d06854df..245de6862ee7bce5859a5233de925948425c0b78 100644 (file)
@@ -3,7 +3,7 @@
 
 # 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)
+# best case and likely leaky in some Perls (e.g. 5.16.3, 5.40.x)
 package PublicInbox::Inotify3;
 use v5.12;
 use autodie qw(open);
@@ -15,8 +15,9 @@ use Scalar::Util ();
 use constant $PublicInbox::Syscall::INOTIFY;
 our %events;
 
-# extracted from devel/sysdefs-list output, these should be arch-independent
 BEGIN {
+# extracted from devel/sysdefs-list output, these should be arch-independent
+# for Linux and also FreeBSD 15+
 %events = (
        IN_ACCESS => 0x1,
        IN_ALL_EVENTS => 0xfff,
@@ -51,11 +52,31 @@ require PublicInbox::In3Watch; # uses SYS_inotify_rm_watch
 use constant autocancel =>
        (IN_IGNORED|IN_UNMOUNT|IN_ONESHOT|IN_DELETE_SELF);
 
+if (defined $PublicInbox::Syscall::INOTIFY->{SYS_inotify_init1}) {
+       eval <<'EOS' or die $@;
+
 sub new {
-       open my $fh, '+<&=', syscall(SYS_inotify_init1, IN_CLOEXEC);
+       open my $fh, "+<&=", syscall SYS_inotify_init1, IN_CLOEXEC;
        bless { fh => $fh }, __PACKAGE__;
 }
 
+sub inotify_add_watch ($$$) { syscall SYS_inotify_add_watch, @_ }
+1;
+EOS
+} elsif (defined $PublicInbox::Syscall::INOTIFY->{SYS___specialfd}) {
+       eval <<'EOS' or die $@;
+sub new {
+       my $args = pack "L", IN_CLOEXEC; # struct specialfd_inotify
+       open my $fh, "+<&=", syscall SYS___specialfd, SPECIALFD_INOTIFY,
+                                       $args, length $args;
+       bless { fh => $fh }, __PACKAGE__;
+}
+sub inotify_add_watch ($$$) {
+       syscall SYS_inotify_add_watch_at, $_[0], AT_FDCWD, $_[1], $_[2];
+}
+1;
+EOS
+}
 sub read {
        my ($self) = @_;
        my (@ret, $wd, $mask, $len, $name, $size, $buf);
@@ -97,7 +118,7 @@ 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);
+       my $wd = inotify_add_watch($self->fileno, $name, $mask);
        return if $wd < 0;
        my $w = bless [ $wd, $mask, $name, $self ], 'PublicInbox::In3Watch';
        $self->{w}->{$wd} = $w;
index acd0f7eb5b86d7ef0a5aa838faef3e83b3de5be1..fefb6b323d7d9993e5e5ec78fb845abe3b0c94f3 100644 (file)
@@ -66,14 +66,17 @@ our ($SYS_epoll_create,
 my $SYS_fstatfs; # don't need fstatfs64, just statfs.f_type
 my ($FS_IOC_GETFLAGS, $FS_IOC_SETFLAGS, $SYS_writev,
        $BTRFS_IOC_DEFRAG);
+our ($machine, $kver);
 my $SFD_CLOEXEC = 02000000; # Perl does not expose O_CLOEXEC
 our $no_deprecated = 0;
+BEGIN {
+       (undef, undef, my $release, undef, $machine) = POSIX::uname();
+       ($kver) = ($release =~ /([0-9]+(?:\.(?:[0-9]+))+)/);
+       $kver = eval("v$kver") // die "bad release=$release from uname";
+}
 
 if ($^O eq "linux") {
        $F_SETPIPE_SZ = 1031;
-       my (undef, undef, $release, undef, $machine) = POSIX::uname();
-       my ($kver) = ($release =~ /([0-9]+(?:\.(?:[0-9]+))+)/);
-       $kver = eval("v$kver") // v2.6;
        $SYS_renameat2 = 0 if $kver lt v3.15;
        $SYS_epoll_pwait = 0 if $kver lt v2.6.19;
        # whether the machine requires 64-bit numbers to be on 8-byte
@@ -333,6 +336,16 @@ BEGIN {
                # so leave it unset on dfly
                $CONST{TCP_ESTABLISHED} = 4 if $^O ne 'dragonfly';
        }
+       if ($^O eq 'freebsd' && $kver ge v15.0) {
+               $INOTIFY = {
+                       IN_CLOEXEC => 0x100000, # different from Linux :P
+                       SYS___specialfd => 577,
+                       AT_FDCWD => -100, # XXX we may use elsewhere
+                       SPECIALFD_INOTIFY => 2,
+                       SYS_inotify_add_watch_at => 593,
+                       SYS_inotify_rm_watch => 594,
+               }
+       }
        $CONST{CMSG_ALIGN_size} = SIZEOF_size_t;
        $CONST{SIZEOF_cmsghdr} //= 0;
        $CONST{TMPL_cmsg_len} //= undef;
index 278755998a160e15a18b29702d7874ffa1c4fdc5..634f87bf12d0356a99de9b71725744df8f8a9746 100644 (file)
@@ -4,7 +4,9 @@
 use v5.12; use PublicInbox::TestCommon;
 use Config;
 use POSIX qw(uname);
-plan skip_all => 'inotify is Linux-only' if $^O ne 'linux';
+use PublicInbox::Syscall;
+plan skip_all => 'inotify is Linux and FreeBSD 15+ only'
+       unless $PublicInbox::Syscall::INOTIFY;
 unless (eval { require PublicInbox::Inotify3 }) {
        my (undef, undef, undef, undef, $machine) = uname();
        diag '<cppsymbols>';