-# 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
+my $fdpass = <<'FDPASS';
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/socket.h>
+
+#if defined(CMSG_SPACE) && defined(CMSG_LEN)
+struct my_3fds { int fds[3]; };
+union my_cmsg {
+ struct cmsghdr hdr;
+ char pad[sizeof(struct cmsghdr)+ 8 + sizeof(struct my_3fds) + 8];
+};
+
+int send_3fds(int sockfd, int infd, int outfd, int errfd)
+{
+ struct msghdr msg = { 0 };
+ struct iovec iov;
+ union my_cmsg cmsg = { 0 };
+ int *fdp;
+ size_t i;
+
+ iov.iov_base = &msg.msg_namelen; /* whatever */
+ iov.iov_len = 1;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = &cmsg.hdr;
+ msg.msg_controllen = CMSG_SPACE(sizeof(struct my_3fds));
+
+ cmsg.hdr.cmsg_level = SOL_SOCKET;
+ cmsg.hdr.cmsg_type = SCM_RIGHTS;
+ cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(struct my_3fds));
+ fdp = (int *)CMSG_DATA(&cmsg.hdr);
+ *fdp++ = infd;
+ *fdp++ = outfd;
+ *fdp++ = errfd;
+ return sendmsg(sockfd, &msg, 0) >= 0;
+}
+
+void recv_3fds(int sockfd)
+{
+ union my_cmsg cmsg = { 0 };
+ struct msghdr msg = { 0 };
+ struct iovec iov;
+ size_t i;
+ Inline_Stack_Vars;
+
+ iov.iov_base = &msg.msg_namelen; /* whatever */
+ iov.iov_len = 1;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = &cmsg.hdr;
+ msg.msg_controllen = CMSG_SPACE(sizeof(struct my_3fds));
+
+ if (recvmsg(sockfd, &msg, 0) <= 0)
+ return;
+
+ errno = EDOM;
+ Inline_Stack_Reset;
+ if (cmsg.hdr.cmsg_level == SOL_SOCKET &&
+ cmsg.hdr.cmsg_type == SCM_RIGHTS &&
+ cmsg.hdr.cmsg_len == CMSG_LEN(sizeof(struct my_3fds))) {
+ int *fdp = (int *)CMSG_DATA(&cmsg.hdr);
+ size_t i;
+
+ for (i = 0; i < 3; 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;
}
+unless (__PACKAGE__->can('recv_3fds')) {
+ eval { # try the XS IO::FDPass package
+ require IO::FDPass;
+ no warnings 'once';
+ *recv_3fds = sub { map { IO::FDPass::recv($_[0]) } (0..2) };
+ *send_3fds = sub ($$$$) {
+ my $sockfd = shift;
+ IO::FDPass::send($sockfd, shift) for (0..2);
+ };
+ };
+}
+
undef $set_nodatacow;
undef $vfork_spawn;
+undef $fdpass;
sub which ($) {
my ($file) = @_;
}
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;