1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
#!perl -w
# Copyright (C) all contributors <meta@public-inbox.org>
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
use v5.12;
use Socket qw(AF_UNIX SOCK_SEQPACKET MSG_EOR pack_sockaddr_un);
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 {
require PublicInbox::Syscall;
$recv_cmd = PublicInbox::Syscall->can('recv_cmd4');
PublicInbox::Syscall->can('send_cmd4');
} // do {
my $inline_dir = $ENV{PERL_INLINE_DIRECTORY} //= (
$ENV{XDG_CACHE_HOME} //
( ($ENV{HOME} // '/nonexistent').'/.cache' )
).'/public-inbox/inline-c';
if (!-d $inline_dir) {
require File::Path;
File::Path::make_path($inline_dir);
}
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';
my %pids;
my $sigchld = sub {
my $flags = scalar(@_) ? POSIX::WNOHANG() : 0;
for my $pid (keys %pids) {
delete($pids{$pid}) if waitpid($pid, $flags) == $pid;
}
};
my @parent;
my $exec_cmd = sub {
my ($fds, $argc, @argv) = @_;
my $parent = $$;
require POSIX;
my @old = (*STDIN{IO}, *STDOUT{IO}, *STDERR{IO});
my @rdr;
for my $fd (@$fds) {
open(my $newfh, '+<&=', $fd) or die "open +<&=$fd: $!";
push @rdr, shift(@old), $newfh;
}
my $do_exec = sub {
my @non_std; # ex. $op_p from lei_edit_search
while (my ($io, $newfh) = splice(@rdr, 0, 2)) {
my $old_io = !!$io;
open $io, '+<&', $newfh or die "open +<&=: $!";
push @non_std, $io unless $old_io;
}
if (@non_std) {
require Fcntl;
fcntl($_, Fcntl::F_SETFD(), 0) for @non_std;
}
my %env = map { split(/=/, $_, 2) } splice(@argv, $argc);
@ENV{keys %env} = values %env;
umask 077;
exec(@argv);
warn "exec: @argv: $!\n";
POSIX::_exit(1);
};
$SIG{CHLD} = $sigchld;
my $pid = fork // die "fork: $!";
if ($pid == 0) {
$do_exec->() if $fds->[1]; # git-credential, pager
# parent backgrounds on MUA
POSIX::setsid() > 0 or die "setsid: $!";
@parent = ($parent);
return; # continue $recv_cmd in background
}
if ($fds->[1]) {
$pids{$pid} = undef;
} else {
$do_exec->(); # MUA reuses stdout
}
};
my $runtime_dir = ($ENV{XDG_RUNTIME_DIR} // '') . '/lei';
if ($runtime_dir eq '/lei') {
require File::Spec;
$runtime_dir = File::Spec->tmpdir."/lei-$<";
}
unless (-d $runtime_dir) {
require File::Path;
File::Path::make_path($runtime_dir, { mode => 0700 });
}
my $path = "$runtime_dir/$narg.seq.sock";
my $addr = pack_sockaddr_un($path);
socket($sock, AF_UNIX, SOCK_SEQPACKET, 0) or die "socket: $!";
unless (connect($sock, $addr)) { # start the daemon if not started
local $ENV{PERL5LIB} = join(':', @INC);
open(my $daemon, '-|', $^X, qw[-MPublicInbox::LEI
-E PublicInbox::LEI::lazy_start(@ARGV)],
$path, $! + 0, $narg) or die "popen: $!";
while (<$daemon>) { warn $_ } # EOF when STDERR is redirected
close($daemon) or warn <<"";
lei-daemon could not start, exited with \$?=$?
# try connecting again anyways, unlink+bind may be racy
connect($sock, $addr) or die <<"";
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" }
$buf .= "\0\0";
$send_cmd->($sock, [0, 1, 2, fileno($dh)], $buf, MSG_EOR) or die "sendmsg: $!";
$SIG{TSTP} = sub { send($sock, 'STOP', MSG_EOR); kill 'STOP', $$ };
$SIG{CONT} = sub { send($sock, 'CONT', MSG_EOR) };
my $x_it_code = 0;
while (1) {
my (@fds) = $recv_cmd->($sock, my $buf, 4096 * 33);
if (scalar(@fds) == 1 && !defined($fds[0])) {
next if $!{EINTR};
die "recvmsg: $!";
}
last if $buf eq '';
if ($buf =~ /\Aexec (.+)\z/) {
$exec_cmd->(\@fds, split(/\0/, $1));
} elsif ($buf eq '-WINCH') {
kill($buf, @parent); # for MUA
} elsif ($buf eq 'umask') {
send($sock, 'u'.pack('V', umask), MSG_EOR) or die "send: $!"
} elsif ($buf =~ /\Ax_it ([0-9]+)\z/) {
$x_it_code ||= $1 + 0;
last;
} elsif ($buf =~ /\Achild_error ([0-9]+)\z/) {
$x_it_code ||= $1 + 0;
} elsif ($buf eq 'wait') {
$sigchld->();
} else {
$sigchld->();
die $buf;
}
}
$sigchld->();
if (my $sig = ($x_it_code & 127)) {
kill $sig, $$;
sleep(1) while 1; # no self-pipe/signalfd, here, so we loop
}
exit($x_it_code >> 8);
|