use Getopt::Long ();
use Socket qw(AF_UNIX SOCK_STREAM pack_sockaddr_un);
use Errno qw(EAGAIN ECONNREFUSED ENOENT);
-use POSIX qw(setsid);
+use POSIX ();
use IO::Handle ();
use Sys::Syslog qw(syslog openlog);
use PublicInbox::Config;
# lei(1) calls this when it can't connect
sub lazy_start {
- my ($path, $err) = @_;
- require IO::FDPass; # require this early so caller sees it
- if ($err == ECONNREFUSED) {
+ my ($path, $errno) = @_;
+ if ($errno == ECONNREFUSED) {
unlink($path) or die "unlink($path): $!";
- } elsif ($err != ENOENT) {
- $! = $err; # allow interpolation to stringify in die
+ } elsif ($errno != ENOENT) {
+ $! = $errno; # allow interpolation to stringify in die
die "connect($path): $!";
}
umask(077) // die("umask(077): $!");
socket(my $l, AF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
bind($l, pack_sockaddr_un($path)) or die "bind($path): $!";
- listen($l, 1024) or die "listen $!";
+ listen($l, 1024) or die "listen: $!";
my @st = stat($path) or die "stat($path): $!";
my $dev_ino_expect = pack('dd', $st[0], $st[1]); # dev+ino
pipe(my ($eof_r, $eof_w)) or die "pipe: $!";
my $oldset = PublicInbox::Sigfd::block_signals();
- my $pid = fork // die "fork: $!";
- return if $pid;
+ require IO::FDPass;
require PublicInbox::Listener;
require PublicInbox::EOFpipe;
- openlog($path, 'pid', 'user');
- local $SIG{__DIE__} = sub {
- syslog('crit', "@_");
- die; # calls the default __DIE__ handler
- };
- local $SIG{__WARN__} = sub { syslog('warning', "@_") };
- open(STDIN, '+<', '/dev/null') or die "redirect stdin failed: $!\n";
- open STDOUT, '>&STDIN' or die "redirect stdout failed: $!\n";
- open STDERR, '>&STDIN' or die "redirect stderr failed: $!\n";
- setsid();
- $pid = fork // die "fork: $!";
+ (-p STDOUT && -p STDERR) or die "E: stdout+stderr must be pipes\n";
+ open(STDIN, '+<', '/dev/null') or die "redirect stdin failed: $!";
+ POSIX::setsid() > 0 or die "setsid: $!";
+ my $pid = fork // die "fork: $!";
return if $pid;
- $SIG{__DIE__} = 'DEFAULT';
- my $on_destroy = PublicInbox::OnDestroy->new(sub {
- my ($owner_pid) = @_;
- syslog('crit', "$@") if $@ && $$ == $owner_pid;
- }, $$);
$0 = "lei-daemon $path";
local %PATH2CFG;
- $l->blocking(0);
- $eof_w->blocking(0);
- $eof_r->blocking(0);
- my $listener = PublicInbox::Listener->new($l, \&accept_dispatch, $l);
+ $_->blocking(0) for ($l, $eof_r, $eof_w);
+ $l = PublicInbox::Listener->new($l, \&accept_dispatch, $l);
my $exit_code;
local $quit = sub {
$exit_code //= shift;
- my $tmp = $listener or exit($exit_code);
+ my $listener = $l or exit($exit_code);
unlink($path) if defined($path);
- syswrite($eof_w, '.');
- $l = $listener = $path = undef;
- $tmp->close if $tmp; # DS::close
+ # closing eof_w triggers \&noop wakeup
+ $eof_w = $l = $path = undef;
+ $listener->close; # DS::close
PublicInbox::DS->SetLoopTimeout(1000);
};
- PublicInbox::EOFpipe->new($eof_r, sub {}, undef);
+ PublicInbox::EOFpipe->new($eof_r, \&noop, undef);
my $sig = {
CHLD => \&PublicInbox::DS::enqueue_reap,
QUIT => $quit,
}
$n; # true: continue, false: stop
});
+
+ # STDIN was redirected to /dev/null above, closing STDOUT and
+ # STDERR will cause the calling `lei' client process to finish
+ # reading <$daemon> pipe.
+ open STDOUT, '>&STDIN' or die "redirect stdout failed: $!";
+ openlog($path, 'pid', 'user');
+ local $SIG{__WARN__} = sub { syslog('warning', "@_") };
+ my $owner_pid = $$;
+ my $on_destroy = PublicInbox::OnDestroy->new(sub {
+ syslog('crit', "$@") if $@ && $$ == $owner_pid;
+ });
+ open STDERR, '>&STDIN' or die "redirect stderr failed: $!";
+ # $daemon pipe to `lei' closed, main loop begins:
PublicInbox::DS->EventLoop;
- $@ = undef if $on_destroy; # quiet OnDestroy if we got here
+ @$on_destroy = (); # cancel on_destroy if we get here
exit($exit_code // 0);
}
use strict;
use v5.10.1;
use Socket qw(AF_UNIX SOCK_STREAM pack_sockaddr_un);
-
-if (eval { require IO::FDPass; 1 }) { # use daemon to reduce load time
+if (my ($sock, $pwd) = eval {
+ require IO::FDPass; # will try to use a daemon to reduce load time
my $path = do {
my $runtime_dir = ($ENV{XDG_RUNTIME_DIR} // '') . '/lei';
if ($runtime_dir eq '/lei') {
my $addr = pack_sockaddr_un($path);
socket(my $sock, AF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
unless (connect($sock, $addr)) { # start the daemon if not started
- my $err = $! + 0;
- my $env = { PERL5LIB => join(':', @INC) };
my $cmd = [ $^X, qw[-MPublicInbox::LEI
-E PublicInbox::LEI::lazy_start(@ARGV)],
- $path, $err ];
+ $path, $! + 0 ];
+ my $env = { PERL5LIB => join(':', @INC) };
+ pipe(my ($daemon, $w)) or die "pipe: $!";
+ my $opt = { 1 => $w, 2 => $w };
require PublicInbox::Spawn;
- waitpid(PublicInbox::Spawn::spawn($cmd, $env), 0);
- warn "lei-daemon exited with \$?=$?\n" if $?;
+ my $pid = PublicInbox::Spawn::spawn($cmd, $env, $opt);
+ $opt = $w = undef;
+ while (<$daemon>) { warn $_ } # EOF when STDERR is redirected
+ waitpid($pid, 0) or warn <<"";
+lei-daemon could not start, PID:$pid exited with \$?=$?
# try connecting again anyways, unlink+bind may be racy
- connect($sock, $addr) or die
- "connect($path): $! (after attempted daemon start)";
+ unless (connect($sock, $addr)) {
+ die <<"";
+connect($path): $! (after attempted daemon start)
+Falling back to (slow) one-shot mode
+
+ }
}
require Cwd;
- my $cwd = Cwd::fastcwd() // die "fastcwd: $!";
+ my $cwd = Cwd::fastcwd() // die "fastcwd(PWD=".($ENV{PWD}//'').": $!";
my $pwd = $ENV{PWD} // '';
- if ($pwd eq $cwd) { # likely, all good
- } elsif ($pwd) { # prefer ENV{PWD} if it's a symlink to real cwd
- my @st_cwd = stat($cwd) or die "stat(cwd=$cwd): $!\n";
- my @st_pwd = stat($pwd);
+ if ($pwd ne $cwd) { # prefer ENV{PWD} if it's a symlink to real cwd
+ my @st_cwd = stat($cwd) or die "stat(cwd=$cwd): $!";
+ my @st_pwd = stat($pwd); # PWD invalid, use cwd
# make sure st_dev/st_ino match for {PWD} to be valid
$pwd = $cwd if (!@st_pwd || $st_pwd[1] != $st_cwd[1] ||
$st_pwd[0] != $st_cwd[0]);
} else {
$pwd = $cwd;
}
+ ($sock, $pwd);
+}) { # IO::FDPass, $sock, $pwd are all available:
local $ENV{PWD} = $pwd;
my $buf = "$$\0\0>" . join("]\0[", @ARGV) . "\0\0>";
while (my ($k, $v) = each %ENV) { $buf .= "$k=$v\0" }
die $buf;
}
} else { # for systems lacking IO::FDPass
+ # don't warn about IO::FDPass since it's not commonly installed
+ warn $@ if $@ && index($@, 'IO::FDPass') < 0;
require PublicInbox::LEI;
PublicInbox::LEI::oneshot(__PACKAGE__);
}
my $test_lei_oneshot = $ENV{TEST_LEI_ONESHOT};
SKIP: {
last SKIP if $test_lei_oneshot;
- require_mods('IO::FDPass', 16);
+ require_mods(qw(IO::FDPass Cwd), 41);
my $sock = "$ENV{XDG_RUNTIME_DIR}/lei/sock";
ok(run_script([qw(lei daemon-pid)], undef, $opt), 'daemon-pid');
chomp(my $new_pid = $out);
ok(kill(0, $new_pid), 'new pid is running');
ok(-S $sock, 'sock exists again');
- unlink $sock or BAIL_OUT "unlink $!";
+
+ if ('socket inaccessible') {
+ chmod 0000, $sock or BAIL_OUT "chmod 0000: $!";
+ $out = $err = '';
+ ok(run_script([qw(lei help)], undef, $opt),
+ 'connect fail, one-shot fallback works');
+ like($err, qr/\bconnect\(/, 'connect error noted');
+ like($out, qr/^usage: /, 'help output works');
+ chmod 0700, $sock or BAIL_OUT "chmod 0700: $!";
+ }
+ if ('oneshot on cwd gone') {
+ my $cwd = Cwd::fastcwd() or BAIL_OUT "fastcwd: $!";
+ my $d = "$home/to-be-removed";
+ mkdir $d or BAIL_OUT "mkdir($d) $!";
+ chdir $d or BAIL_OUT "chdir($d) $!";
+ if (rmdir($d)) {
+ $out = $err = '';
+ ok(run_script([qw(lei help)], undef, $opt),
+ 'cwd fail, one-shot fallback works');
+ } else {
+ $err = "rmdir=$!";
+ }
+ chdir $cwd or BAIL_OUT "chdir($cwd) $!";
+ like($err, qr/cwd\(/, 'cwd error noted');
+ like($out, qr/^usage: /, 'help output still works');
+ }
+
+ unlink $sock or BAIL_OUT "unlink($sock) $!";
for (0..100) {
kill('CHLD', $new_pid) or last;
tick();