use PublicInbox::Sigfd;
use PublicInbox::DS qw(now dwaitpid);
use PublicInbox::Spawn qw(spawn popen_rd);
-use PublicInbox::OnDestroy;
+use PublicInbox::Lock;
use Time::HiRes qw(stat); # ctime comparisons for config cache
use File::Path qw(mkpath);
use File::Spec;
save-as=s output|mfolder|o=s format|f=s dedupe|d=s threads|t augment|a
sort|s=s reverse|r offset=i remote! local! external! pretty
include|I=s@ exclude=s@ only=s@ jobs|j=s globoff|g stdin|
- mua=s no-torsocks torsocks=s verbose|v+ quiet|q),
+ alert=s@ mua=s no-torsocks torsocks=s verbose|v+ quiet|q),
PublicInbox::LeiQuery::curl_opt(), opt_dash('limit|n=i', '[0-9]+') ],
'show' => [ 'MID|OID', 'show a given object (Message-ID or object ID)',
'show threads|t' => 'display entire thread a message belongs to',
'q threads|t' =>
'return all messages in the same threads as the actual match(es)',
+'alert=s@' => ['CMD,-WINCH,-bell,<any command>',
+ 'run command(s) or perform ops when done writing to output ' .
+ '(default: "-WINCH,-bell" with --mua and Maildir/IMAP output, ' .
+ 'nothing otherwise)' ],
+
'augment|a' => 'augment --output destination instead of clobbering',
'output|mfolder|o=s' => [ 'MFOLDER',
if (my $sock = $self->{sock}) { # lei(1) client process runs it
send($sock, exec_buf(\@cmd, {}), MSG_EOR);
} elsif ($self->{oneshot}) {
- $self->{"mua.pid.$self.$$"} = spawn(\@cmd);
+ $self->{"pid.$self.$$"}->{spawn(\@cmd)} = \@cmd;
+ }
+ if ($self->{lxs} && $self->{au_done}) { # kick wait_startq
+ syswrite($self->{au_done}, 'q' x ($self->{lxs}->{jobs} // 0));
}
+ $self->{opt}->{quiet} = 1;
delete $self->{-progress};
+ delete $self->{opt}->{verbose};
+}
+
+sub poke_mua { # forces terminal MUAs to wake up and hopefully notice new mail
+ my ($self) = @_;
+ my $alerts = $self->{opt}->{alert} // return;
+ while (my $op = shift(@$alerts)) {
+ if ($op eq '-WINCH') {
+ # hit the process group that started the MUA
+ if ($self->{sock}) {
+ send($self->{sock}, '-WINCH', MSG_EOR);
+ } elsif ($self->{oneshot}) {
+ kill('-WINCH', $$);
+ }
+ } elsif ($op eq '-bell') {
+ out($self, "\a");
+ } elsif ($op =~ /(?<!\\),/) { # bare ',' (not ',,')
+ push @$alerts, split(/(?<!\\),/, $op);
+ } elsif ($op =~ m!\A([/a-z0-9A-Z].+)!) {
+ my $cmd = $1; # run an arbitrary command
+ require Text::ParseWords;
+ $cmd = [ Text::ParseWords::shellwords($cmd) ];
+ if (my $s = $self->{sock}) {
+ send($s, exec_buf($cmd, {}), MSG_EOR);
+ } elsif ($self->{oneshot}) {
+ $self->{"pid.$self.$$"}->{spawn($cmd)} = $cmd;
+ }
+ } else {
+ err($self, "W: unsupported --alert=$op"); # non-fatal
+ }
+ }
}
# caller needs to "-t $self->{1}" to check if tty
chomp(my $pager = <$fh> // '');
close($fh) or warn "`git var PAGER' error: \$?=$?";
return if $pager eq 'cat' || $pager eq '';
- # TODO TIOCGWINSZ
- my $new_env = { LESS => 'FRX', LV => '-c', COLUMNS => 80 };
+ my $new_env = { LESS => 'FRX', LV => '-c' };
$new_env->{MORE} = 'FRX' if $^O eq 'freebsd';
pipe(my ($r, $wpager)) or return warn "pipe: $!";
my $rdr = { 0 => $r, 1 => $self->{1}, 2 => $self->{2} };
my $fds = [ map { fileno($_) } @$rdr{0..2} ];
$send_cmd->($sock, $fds, exec_buf([$pager], $new_env), MSG_EOR);
} elsif ($self->{oneshot}) {
- $pgr->[0] = spawn([$pager], $new_env, $rdr);
- $pgr->[3] = $$; # ew'll reap it
+ my $cmd = [$pager];
+ $self->{"pid.$self.$$"}->{spawn($cmd, $new_env, $rdr)} = $cmd;
} else {
die 'BUG: start_pager w/o socket';
}
$self->{2} = $pgr->[2];
# do not restore original stdout, just close it so we error out
close(delete($self->{1})) if $self->{1};
- my $pid = $pgr->[0];
- dwaitpid($pid) if $pid && ($pgr->[3] // 0) == $$;
}
sub accept_dispatch { # Listener {post_accept} callback
vec(my $rvec = '', fileno($sock), 1) = 1;
select($rvec, undef, undef, 60) or
return send($sock, 'timed out waiting to recv FDs', MSG_EOR);
- my @fds = $recv_cmd->($sock, my $buf, 4096 * 33); # >MAX_ARG_STRLEN
+ # (4096 * 33) >MAX_ARG_STRLEN
+ my @fds = $recv_cmd->($sock, my $buf, 4096 * 33) or return; # EOF
if (scalar(@fds) == 4) {
for my $i (0..3) {
my $fd = shift(@fds);
open($self->{$i}, '+<&=', $fd) and next;
send($sock, "open(+<&=$fd) (FD=$i): $!", MSG_EOR);
}
- } else {
- my $msg = "recv_cmd failed: $!";
- warn $msg;
+ } elsif (!defined($fds[0])) {
+ warn(my $msg = "recv_cmd failed: $!");
return send($sock, $msg, MSG_EOR);
+ } else {
+ return;
}
$self->{2}->autoflush(1); # keep stdout buffered until x_it|DESTROY
# $ENV_STR = join('', map { "\0$_=$ENV{$_}" } keys %ENV);
# lei(1) calls this when it can't connect
sub lazy_start {
my ($path, $errno, $narg) = @_;
- if ($errno == ECONNREFUSED) {
- unlink($path) or die "unlink($path): $!";
- } elsif ($errno != ENOENT) {
+ local ($errors_log, $listener);
+ ($errors_log) = ($path =~ m!\A(.+?/)[^/]+\z!);
+ $errors_log .= 'errors.log';
+ my $addr = pack_sockaddr_un($path);
+ my $lk = bless { lock_path => $errors_log }, 'PublicInbox::Lock';
+ $lk->lock_acquire;
+ socket($listener, AF_UNIX, SOCK_SEQPACKET, 0) or die "socket: $!";
+ if ($errno == ECONNREFUSED || $errno == ENOENT) {
+ return if connect($listener, $addr); # another process won
+ if ($errno == ECONNREFUSED && -S $path) {
+ unlink($path) or die "unlink($path): $!";
+ }
+ } else {
$! = $errno; # allow interpolation to stringify in die
die "connect($path): $!";
}
- if (eval { require BSD::Resource }) {
- my $NOFILE = BSD::Resource::RLIMIT_NOFILE();
- my ($s, $h) = BSD::Resource::getrlimit($NOFILE);
- BSD::Resource::setrlimit($NOFILE, $h, $h) if $s < $h;
- }
umask(077) // die("umask(077): $!");
- local $listener;
- socket($listener, AF_UNIX, SOCK_SEQPACKET, 0) or die "socket: $!";
- bind($listener, pack_sockaddr_un($path)) or die "bind($path): $!";
+ bind($listener, $addr) or die "bind($path): $!";
listen($listener, 1024) or die "listen: $!";
+ $lk->lock_release;
+ undef $lk;
my @st = stat($path) or die "stat($path): $!";
my $dev_ino_expect = pack('dd', $st[0], $st[1]); # dev+ino
local $oldset = PublicInbox::DS::block_signals();
require PublicInbox::Listener;
require PublicInbox::EOFpipe;
(-p STDOUT) or die "E: stdout must be a pipe\n";
- local $errors_log;
- ($errors_log) = ($path =~ m!\A(.+?/)[^/]+\z!);
- $errors_log .= 'errors.log';
open(STDIN, '+>>', $errors_log) or die "open($errors_log): $!";
STDIN->autoflush(1);
dump_and_clear_log("from previous daemon process:\n");
my ($self) = @_;
$self->{1}->autoflush(1) if $self->{1};
stop_pager($self);
- if (my $mua_pid = delete $self->{"mua.pid.$self.$$"}) {
- waitpid($mua_pid, 0);
- }
+ my $oneshot_pids = delete $self->{"pid.$self.$$"} or return;
+ waitpid($_, 0) for keys %$oneshot_pids;
}
1;