-# Copyright (C) 2016-2020 all contributors <meta@public-inbox.org>
+# Copyright (C) 2016-2021 all contributors <meta@public-inbox.org>
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
#
# This allows vfork to be used for spawning subprocesses if
use parent qw(Exporter);
use Symbol qw(gensym);
use PublicInbox::ProcessPipe;
-our @EXPORT_OK = qw/which spawn popen_rd/;
+our @EXPORT_OK = qw(which spawn popen_rd run_die nodatacow_dir);
our @RLIMITS = qw(RLIMIT_CPU RLIMIT_CORE RLIMIT_DATA);
my $vfork_spawn = <<'VFORK_SPAWN';
#include <sys/vfs.h>
#include <linux/magic.h>
#include <linux/fs.h>
+#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
-void set_nodatacow(int fd)
+void nodatacow_fd(int fd)
{
struct statfs buf;
int val = 0;
if (ioctl(fd, FS_IOC_SETFLAGS, &val) < 0)
fprintf(stderr, "FS_IOC_SET_FLAGS: %s\\n", strerror(errno));
}
+
+void nodatacow_dir(const char *dir)
+{
+ DIR *dh = opendir(dir);
+ int fd;
+
+ if (!dh) croak("opendir(%s): %s", dir, strerror(errno));
+ fd = dirfd(dh);
+ if (fd >= 0)
+ nodatacow_fd(fd);
+ /* ENOTSUP probably won't happen under Linux... */
+ closedir(dh);
+}
SET_NODATACOW
+# last choice for script/lei, 1st choice for lei internals
+# compatible with PublicInbox::CmdIPC4
+my $fdpass = <<'FDPASS';
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/socket.h>
+
+#if defined(CMSG_SPACE) && defined(CMSG_LEN)
+#define SEND_FD_CAPA 6
+#define SEND_FD_SPACE (SEND_FD_CAPA * sizeof(int))
+union my_cmsg {
+ struct cmsghdr hdr;
+ char pad[sizeof(struct cmsghdr) + 16 + SEND_FD_SPACE];
+};
+
+SV *send_cmd4(PerlIO *s, SV *svfds, SV *data, int flags)
+{
+ struct msghdr msg = { 0 };
+ union my_cmsg cmsg = { 0 };
+ STRLEN dlen = 0;
+ struct iovec iov;
+ ssize_t sent;
+ AV *fds = (AV *)SvRV(svfds);
+ I32 i, nfds = av_len(fds) + 1;
+ int *fdp;
+
+ if (SvOK(data)) {
+ iov.iov_base = SvPV(data, dlen);
+ iov.iov_len = dlen;
+ }
+ if (!dlen) { /* must be non-zero */
+ iov.iov_base = &msg.msg_namelen; /* whatever */
+ iov.iov_len = 1;
+ }
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ if (nfds) {
+ if (nfds > SEND_FD_CAPA) {
+ fprintf(stderr, "FIXME: bump SEND_FD_CAPA=%d\n", nfds);
+ nfds = SEND_FD_CAPA;
+ }
+ msg.msg_control = &cmsg.hdr;
+ msg.msg_controllen = CMSG_SPACE(nfds * sizeof(int));
+ cmsg.hdr.cmsg_level = SOL_SOCKET;
+ cmsg.hdr.cmsg_type = SCM_RIGHTS;
+ cmsg.hdr.cmsg_len = CMSG_LEN(nfds * sizeof(int));
+ fdp = (int *)CMSG_DATA(&cmsg.hdr);
+ for (i = 0; i < nfds; i++) {
+ SV **fd = av_fetch(fds, i, 0);
+ *fdp++ = SvIV(*fd);
+ }
+ }
+ sent = sendmsg(PerlIO_fileno(s), &msg, flags);
+ return sent >= 0 ? newSViv(sent) : &PL_sv_undef;
+}
+
+void recv_cmd4(PerlIO *s, SV *buf, STRLEN n)
+{
+ union my_cmsg cmsg = { 0 };
+ struct msghdr msg = { 0 };
+ struct iovec iov;
+ ssize_t i;
+ Inline_Stack_Vars;
+ Inline_Stack_Reset;
+
+ if (!SvOK(buf))
+ sv_setpvn(buf, "", 0);
+ iov.iov_base = SvGROW(buf, n + 1);
+ iov.iov_len = n;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = &cmsg.hdr;
+ msg.msg_controllen = CMSG_SPACE(SEND_FD_SPACE);
+
+ i = recvmsg(PerlIO_fileno(s), &msg, 0);
+ if (i < 0)
+ Inline_Stack_Push(&PL_sv_undef);
+ else
+ SvCUR_set(buf, i);
+ if (i > 0 && cmsg.hdr.cmsg_level == SOL_SOCKET &&
+ cmsg.hdr.cmsg_type == SCM_RIGHTS) {
+ size_t len = cmsg.hdr.cmsg_len;
+ int *fdp = (int *)CMSG_DATA(&cmsg.hdr);
+ for (i = 0; CMSG_LEN((i + 1) * sizeof(int)) <= len; i++)
+ Inline_Stack_Push(sv_2mortal(newSViv(*fdp++)));
+ }
+ Inline_Stack_Done;
+}
+#endif /* defined(CMSG_SPACE) && defined(CMSG_LEN) */
+FDPASS
+
my $inline_dir = $ENV{PERL_INLINE_DIRECTORY} //= (
$ENV{XDG_CACHE_HOME} //
( ($ENV{HOME} // '/nonexistent').'/.cache' )
).'/public-inbox/inline-c';
-$set_nodatacow = $vfork_spawn = undef unless -d $inline_dir && -w _;
+$set_nodatacow = $vfork_spawn = $fdpass = undef unless -d $inline_dir && -w _;
if (defined $vfork_spawn) {
# Inline 0.64 or later has locking in multi-process env,
# but we support 0.5 on Debian wheezy
my $f = "$inline_dir/.public-inbox.lock";
open my $fh, '>', $f or die "failed to open $f: $!\n";
flock($fh, LOCK_EX) or die "LOCK_EX failed on $f: $!\n";
- eval 'use Inline C => $vfork_spawn . $set_nodatacow';
+ eval 'use Inline C => $vfork_spawn.$fdpass.$set_nodatacow';
+ # . ', BUILD_NOISY => 1';
my $err = $@;
my $ndc_err;
if ($err && $set_nodatacow) { # missing Linux kernel headers
$ndc_err = $err;
undef $set_nodatacow;
- eval 'use Inline C => $vfork_spawn';
+ eval 'use Inline C => $vfork_spawn . $fdpass';
}
flock($fh, LOCK_UN) or die "LOCK_UN failed on $f: $!\n";
die $err if $err;
};
if ($@) {
warn "Inline::C failed for vfork: $@\n";
- $set_nodatacow = $vfork_spawn = undef;
+ $set_nodatacow = $vfork_spawn = $fdpass = undef;
}
}
unless ($set_nodatacow) {
require PublicInbox::NDC_PP;
no warnings 'once';
- *set_nodatacow = \&PublicInbox::NDC_PP::set_nodatacow;
+ *nodatacow_fd = \&PublicInbox::NDC_PP::nodatacow_fd;
+ *nodatacow_dir = \&PublicInbox::NDC_PP::nodatacow_dir;
}
+
undef $set_nodatacow;
undef $vfork_spawn;
+undef $fdpass;
sub which ($) {
my ($file) = @_;
return $file if index($file, '/') >= 0;
- foreach my $p (split(':', $ENV{PATH})) {
+ for my $p (split(/:/, $ENV{PATH})) {
$p .= "/$file";
return $p if -x $p;
}
}
my $cd = $opts->{'-C'} // ''; # undef => NULL mapping doesn't work?
my $pid = pi_fork_exec($redir, $f, $cmd, \@env, $rlim, $cd);
- die "fork_exec failed: $!\n" unless $pid > 0;
+ die "fork_exec @$cmd failed: $!\n" unless $pid > 0;
$pid;
}
sub popen_rd {
- my ($cmd, $env, $opts) = @_;
+ my ($cmd, $env, $opt) = @_;
pipe(my ($r, $w)) or die "pipe: $!\n";
- $opts ||= {};
- $opts->{1} = fileno($w);
- my $pid = spawn($cmd, $env, $opts);
+ $opt ||= {};
+ $opt->{1} = fileno($w);
+ my $pid = spawn($cmd, $env, $opt);
return ($r, $pid) if wantarray;
my $ret = gensym;
- tie *$ret, 'PublicInbox::ProcessPipe', $pid, $r;
+ tie *$ret, 'PublicInbox::ProcessPipe', $pid, $r, @$opt{qw(cb arg)};
$ret;
}
+sub run_die ($;$$) {
+ my ($cmd, $env, $rdr) = @_;
+ my $pid = spawn($cmd, $env, $rdr);
+ waitpid($pid, 0) == $pid or die "@$cmd did not finish";
+ $? == 0 or die "@$cmd failed: \$?=$?\n";
+}
+
1;