public-inbox.git  about / heads / tags
an "archives first" approach to mailing lists
blob 087afc33014dd062e9f3472a56a63714d7a8c5db 4387 bytes (raw)
$ git show HEAD:script/lei	# shows this blob on the CLI

  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
 
#!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 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, $^W ? ('-w') : (),
		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, 0) or die "sendmsg: $!";
$SIG{TSTP} = sub { send($sock, 'STOP', 0); kill 'STOP', $$ };
$SIG{CONT} = sub { send($sock, 'CONT', 0) };

my $x_it_code = 0;
while (1) {
	my (@fds) = $recv_cmd->($sock, my $buf, 4096 * 33);
	die "recvmsg: $!" if scalar(@fds) == 1 && !defined($fds[0]);
	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), 0) 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);

git clone https://public-inbox.org/public-inbox.git
git clone http://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/public-inbox.git