Unfortunately, Socket::MsgHdr::recvmsg fails to set the `flags'
field in the Socket::MsgHdr object according to
`struct msghdr.msg_flags', meaning soft error conditions such
as MSG_TRUNC and MSG_CTRUNC are not detectable when they're
set in the .msg_flags field upon recvmsg(2).
While a patch exists[1] for Socket::MsgHdr, it's uncertain if
upstream will act on it and old versions will exist in distros
for a long time.
Instead, we'll fix a formerly innocuous pack template bug for
64-bit Linux and to allow syscall(SYS_recvmsg, ....) to detect
such a problem. Inline::C use is probably common for our users,
anyways, and remains an option for now.
[1] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=
1142663
(liblinux-inotify2-perl and perl-Linux-Inotify2 in .deb and .rpm-based
distros, respectively) are recommended.
-L<Socket::MsgHdr> is optional (libsocket-msghdr-perl in Debian),
-and further improves startup performance. Its effect is most felt
-when using shell completion.
-
=head1 BASH COMPLETION
Preliminary Bash completion for lei is provided in
lib/PublicInbox/CidxComm.pm
lib/PublicInbox/CidxLogP.pm
lib/PublicInbox/CidxXapHelperAux.pm
-lib/PublicInbox/CmdIPC4.pm
lib/PublicInbox/CodeSearch.pm
lib/PublicInbox/CodeSearchIdx.pm
lib/PublicInbox/Compat.pm
+++ /dev/null
-# Copyright (C) all contributors <meta@public-inbox.org>
-# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-
-# callers should use PublicInbox::CmdIPC4->can('send_cmd4') (or recv_cmd4)
-# first choice for script/lei front-end and 2nd choice for lei backend
-# libsocket-msghdr-perl is in Debian but not many other distros as of 2021.
-package PublicInbox::CmdIPC4;
-use v5.12;
-use Socket qw(SOL_SOCKET SCM_RIGHTS);
-
-sub sendmsg_retry ($) {
- return 1 if $!{EINTR};
- return unless ($!{ENOMEM} || $!{ENOBUFS} || $!{ETOOMANYREFS});
- return if $_[0]-- == 0;
- # n.b. `N & (power-of-two - 1)' is a faster `N % power-of-two'
- warn "# sleeping on sendmsg: $! ($_[0] tries left)\n" if !($_[0] & 15);
- select(undef, undef, undef, 0.1);
- 1;
-}
-
-sub fd2io (@) { map { open my $fh, '+<&=', $_; $fh } @_ }
-
-BEGIN { eval {
-require Socket::MsgHdr; # XS
-no warnings 'once';
-
-# any number of FDs per-sendmsg(2) + buffer
-*send_cmd4 = sub ($$$$;$) { # (sock, io, buf, flags) = @_;
- my ($sock, $io, undef, $flags, $tries) = @_;
- $tries //= -1; # infinite
- my $mh = Socket::MsgHdr->new(buf => $_[2]);
- $mh->cmsghdr(SOL_SOCKET, SCM_RIGHTS,
- pack('i' x scalar(@{$io //= []}), map { fileno $_ } @$io));
- my $s;
- do {
- $s = Socket::MsgHdr::sendmsg($sock, $mh, $flags);
- } while (!defined($s) && sendmsg_retry($tries));
- $s;
-};
-
-*recv_cmd4 = sub ($$$) {
- my ($s, undef, $len) = @_; # $_[1] = destination buffer
- my $mh = Socket::MsgHdr->new(buflen => $len, controllen => 256);
- my $r;
- do {
- $r = Socket::MsgHdr::recvmsg($s, $mh, 0);
- } while (!defined($r) && $!{EINTR});
- if (!defined($r)) {
- $_[1] = '';
- return (undef);
- }
- $_[1] = $mh->buf;
- return () if $r == 0;
- my (undef, undef, $data) = $mh->cmsghdr;
- defined($data) ? fd2io(unpack('i' x (length($data) / 4), $data)) : ();
-};
-
-} } # /eval /BEGIN
-
-1;
*ipc_thaw = \&Storable::thaw;
}
-our $recv_cmd = PublicInbox::Spawn->can('recv_cmd4');
-our $send_cmd = PublicInbox::Spawn->can('send_cmd4') // do {
- require PublicInbox::CmdIPC4;
- $recv_cmd //= PublicInbox::CmdIPC4->can('recv_cmd4');
- PublicInbox::CmdIPC4->can('send_cmd4');
+our ($recv_cmd, $send_cmd);
+do {
+ $recv_cmd = PublicInbox::Spawn->can('recv_cmd4');
+ $send_cmd = PublicInbox::Spawn->can('send_cmd4');
} // do {
require PublicInbox::Syscall;
- $recv_cmd //= PublicInbox::Syscall->can('recv_cmd4');
- PublicInbox::Syscall->can('send_cmd4');
+ $recv_cmd = PublicInbox::Syscall->can('recv_cmd4');
+ $send_cmd = PublicInbox::Syscall->can('send_cmd4');
};
sub _get_rec ($) {
local $oldset = PublicInbox::DS::block_signals(POSIX::SIGALRM);
die "incompatible narg=$narg" if $narg != 5;
$PublicInbox::IPC::send_cmd or die <<"";
-(Socket::MsgHdr || Inline::C) missing/unconfigured (narg=$narg);
+Inline::C missing/unconfigured (narg=$narg);
require PublicInbox::Listener;
require PublicInbox::PktOp;
use constant msg_controllen_max =>
CMSG_SPACE(10 * SIZEOF_int) + SIZEOF_cmsghdr; # space for 10 FDs
+sub sendmsg_retry ($) {
+ return 1 if $!{EINTR};
+ return unless ($!{ENOMEM} || $!{ENOBUFS} || $!{ETOOMANYREFS});
+ return if $_[0]-- == 0;
+ # n.b. `N & (power-of-two - 1)' is a faster `N % power-of-two'
+ warn "# sleeping on sendmsg: $! ($_[0] tries left)\n" if !($_[0] & 15);
+ select(undef, undef, undef, 0.1);
+ 1;
+}
+
+sub fd2io (@) { map { open my $fh, '+<&=', $_; $fh } @_ }
+
no warnings 'once';
if (defined($SYS_sendmsg) && defined($SYS_recvmsg)) {
-require PublicInbox::CmdIPC4;
*send_cmd4 = sub ($$$$;$) {
my ($sock, $io, undef, $flags, $tries) = @_;
$tries //= -1;
do {
$s = syscall($SYS_sendmsg, fileno($sock), $mh, $flags);
- } while ($s < 0 && PublicInbox::CmdIPC4::sendmsg_retry($tries));
+ } while ($s < 0 && sendmsg_retry($tries));
$s >= 0 ? $s : undef;
};
$cmsghdr);
if ($lvl == SOL_SOCKET && $type == SCM_RIGHTS) {
$len -= CMSG_ALIGN_SIZEOF_cmsghdr;
- @ret = PublicInbox::CmdIPC4::fd2io(
- @fds[0..(($len / SIZEOF_int) - 1)]);
+ @ret = fd2io(@fds[0..(($len / SIZEOF_int) - 1)]);
}
}
@ret;
state $ok = do {
require PublicInbox::Syscall;
PublicInbox::Syscall->can('send_cmd4'); # Linux+*BSD
- } || eval { require Socket::MsgHdr; 1 } ||
- PublicInbox::Spawn->can('send_cmd4');
+ } || PublicInbox::Spawn->can('send_cmd4');
return () if $ok;
- ('need SCM_RIGHTS support: Socket::MsgHdr OR ' .
+ ('need SCM_RIGHTS support: ' .
'(syscall numbers + msg_hdr pack templates missing) OR ' .
'Inline::C unconfigured/missing '.
'( mkdir -p ~/.cache/public-inbox/inline-c)' );
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
use v5.12;
use Socket qw(AF_UNIX SOCK_SEQPACKET pack_sockaddr_un MSG_EOR);
-use PublicInbox::CmdIPC4;
my $narg = 5;
-my $sock;
-my $recv_cmd = PublicInbox::CmdIPC4->can('recv_cmd4');
-my $send_cmd = PublicInbox::CmdIPC4->can('send_cmd4') // do {
+my ($sock, $recv_cmd, $send_cmd);
+do {
require PublicInbox::Syscall;
$recv_cmd = PublicInbox::Syscall->can('recv_cmd4');
- PublicInbox::Syscall->can('send_cmd4');
+ $send_cmd = PublicInbox::Syscall->can('send_cmd4');
} // do {
my $inline_dir = $ENV{PERL_INLINE_DIRECTORY} //= (
$ENV{XDG_CACHE_HOME} //
}
require PublicInbox::Spawn; # takes ~50ms even if built *sigh*
$recv_cmd = PublicInbox::Spawn->can('recv_cmd4');
- PublicInbox::Spawn->can('send_cmd4');
-} // die 'please install Inline::C or Socket::MsgHdr';
+ $send_cmd = PublicInbox::Spawn->can('send_cmd4');
+} // die 'please install Inline::C';
my %pids;
my $sigchld = sub {
connect($path): $! (after attempted daemon start)
}
-# (Socket::MsgHdr|Inline::C), $sock are all available:
open my $dh, '<', '.' or die "open(.) $!";
my $buf = join("\0", scalar(@ARGV), @ARGV);
while (my ($k, $v) = each %ENV) { $buf .= "\0$k=$v" }
$do_test->(SOCK_SEQPACKET, MSG_EOR, 'Inline::C seqpacket');
}
-SKIP: {
- require_mods('Socket::MsgHdr', 13);
- require_ok 'PublicInbox::CmdIPC4';
- $send = PublicInbox::CmdIPC4->can('send_cmd4');
- $recv = PublicInbox::CmdIPC4->can('recv_cmd4');
- $do_test->(SOCK_STREAM, 0, 'MsgHdr stream');
- $do_test->(SOCK_SEQPACKET, MSG_EOR, 'MsgHdr seqpacket');
- SKIP: {
- ($send_ic && $recv_ic) or
- skip 'Inline::C not installed/enabled', 12;
- $recv = $recv_ic;
- $do_test->(SOCK_STREAM, 0, 'Inline::C -> MsgHdr stream');
- $do_test->(SOCK_SEQPACKET,
- MSG_EOR, 'Inline::C -> MsgHdr seqpacket');
- }
-}
-
SKIP: {
require_ok 'PublicInbox::Syscall';
$send = PublicInbox::Syscall->can('send_cmd4') or
test_lei({ daemon_only => 1 }, sub {
my $send_cmd = PublicInbox::Spawn->can('send_cmd4') // do {
- require PublicInbox::CmdIPC4;
- PublicInbox::CmdIPC4->can('send_cmd4');
- } // do {
require PublicInbox::Syscall;
PublicInbox::Syscall->can('send_cmd4');
};
use IO::Handle; # ->autoflush
use PublicInbox::TestCommon;
use PublicInbox::Spawn;
+use PublicInbox::Syscall;
eval { require PublicInbox::Lg2 }; # placate FindBin
use PublicInbox::DS; # already loaded by Spawn via PublicInbox::IO
use Getopt::Long qw(:config gnu_getopt no_ignore_case auto_abbrev);
# because lei-daemon uses a single inotify FD for all clients.
if ($ENV{TEST_LEI_DAEMON_PERSIST} && !$ENV{TEST_LEI_DAEMON_PERSIST_DIR} &&
(PublicInbox::Spawn->can('recv_cmd4') ||
- eval { require Socket::MsgHdr })) {
+ PublicInbox::Syscall->can('recv_cmd4'))) {
$lei_env = {};
($lei_env->{XDG_RUNTIME_DIR}, $for_destroy) = tmpdir;
$ENV{TEST_LEI_DAEMON_PERSIST_DIR} = $lei_env->{XDG_RUNTIME_DIR};