]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/Daemon.pm
replace ParentPipe with EOFpipe
[public-inbox.git] / lib / PublicInbox / Daemon.pm
index 9db472a19479b5fdfd271c6c60c6597116cadbe9..000ba1695d666bb7eed68677b5418e7bb8bbe23a 100644 (file)
@@ -1,12 +1,12 @@
-# Copyright (C) 2015-2019 all contributors <meta@public-inbox.org>
+# Copyright (C) 2015-2020 all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-# contains common daemon code for the nntpd and httpd servers.
+# contains common daemon code for the httpd, imapd, and nntpd servers.
 # This may be used for read-only IMAP server if we decide to implement it.
 package PublicInbox::Daemon;
 use strict;
 use warnings;
 use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;
-use IO::Handle;
+use IO::Handle; # ->autoflush
 use IO::Socket;
 use POSIX qw(WNOHANG :signal_h);
 use Socket qw(IPPROTO_TCP SOL_SOCKET);
@@ -15,12 +15,12 @@ use Cwd qw/abs_path/;
 STDOUT->autoflush(1);
 STDERR->autoflush(1);
 use PublicInbox::DS qw(now);
-use PublicInbox::Syscall qw(SFD_NONBLOCK);
+use PublicInbox::Syscall qw($SFD_NONBLOCK);
 require PublicInbox::Listener;
-require PublicInbox::ParentPipe;
-require PublicInbox::Sigfd;
+use PublicInbox::EOFpipe;
+use PublicInbox::Sigfd;
 my @CMD;
-my ($set_user, $oldset, $newset);
+my ($set_user, $oldset);
 my (@cfg_listen, $stdout, $stderr, $group, $user, $pid_file, $daemonize);
 my $worker_processes = 1;
 my @listeners;
@@ -29,8 +29,8 @@ my %tls_opt; # scheme://sockname => args for IO::Socket::SSL->start_SSL
 my $reexec_pid;
 my ($uid, $gid);
 my ($default_cert, $default_key);
-my %KNOWN_TLS = ( 443 => 'https', 563 => 'nntps' );
-my %KNOWN_STARTTLS = ( 119 => 'nntp' );
+my %KNOWN_TLS = ( 443 => 'https', 563 => 'nntps', 993 => 'imaps' );
+my %KNOWN_STARTTLS = ( 119 => 'nntp', 143 => 'imap' );
 
 sub accept_tls_opt ($) {
        my ($opt_str) = @_;
@@ -72,15 +72,10 @@ sub accept_tls_opt ($) {
        { SSL_server => 1, SSL_startHandshake => 0, SSL_reuse_ctx => $ctx };
 }
 
-sub sig_setmask { sigprocmask(SIG_SETMASK, @_) or die "sigprocmask: $!" }
-
 sub daemon_prepare ($) {
        my ($default_listen) = @_;
        my $listener_names = {}; # sockname => IO::Handle
-       $oldset = POSIX::SigSet->new();
-       $newset = POSIX::SigSet->new();
-       $newset->fillset or die "fillset: $!";
-       sig_setmask($newset, $oldset);
+       my $oldset = PublicInbox::Sigfd::block_signals();
        @CMD = ($0, @ARGV);
        my %opts = (
                'l|listen=s' => \@cfg_listen,
@@ -123,7 +118,7 @@ sub daemon_prepare ($) {
                        $tls_opt{"$scheme://$l"} = accept_tls_opt($1);
                } elsif (defined($default_cert)) {
                        $tls_opt{"$scheme://$l"} = accept_tls_opt('');
-               } elsif ($scheme =~ /\A(?:nntps|https)\z/) {
+               } elsif ($scheme =~ /\A(?:https|imaps|imaps)\z/) {
                        die "$orig specified w/o cert=\n";
                }
                # TODO: use scheme to load either NNTP.pm or HTTP.pm
@@ -247,7 +242,7 @@ sub daemonize () {
 
        write_pid($pid_file);
        # for ->DESTROY:
-       bless { pid => $$, pid_file => $pid_file }, __PACKAGE__;
+       bless { pid => $$, pid_file => \$pid_file }, __PACKAGE__;
 }
 
 sub worker_quit { # $_[0] = signal name or number (unused)
@@ -403,6 +398,9 @@ sub upgrade { # $_[0] = signal name or number (unused)
                $ENV{LISTEN_FDS} = scalar @listeners;
                $ENV{LISTEN_PID} = $$;
                foreach my $s (@listeners) {
+                       # @listeners are globs with workers, PI::L w/o workers
+                       $s = $s->{sock} if ref($s) eq 'PublicInbox::Listener';
+
                        my $fl = fcntl($s, F_GETFD, 0);
                        fcntl($s, F_SETFD, $fl &= ~FD_CLOEXEC);
                }
@@ -470,8 +468,6 @@ sub master_quit ($) {
 
 sub master_loop {
        pipe(my ($p0, $p1)) or die "failed to create parent-pipe: $!";
-       # 1031: F_SETPIPE_SZ, 4096: page size
-       fcntl($p1, 1031, 4096) if $^O eq 'linux';
        my $set_workers = $worker_processes;
        reopen_logs();
        my $ignore_winch;
@@ -512,7 +508,7 @@ EOF
        };
        my $sigfd = PublicInbox::Sigfd->new($sig, 0);
        local %SIG = (%SIG, %$sig) if !$sigfd;
-       sig_setmask($oldset) if !$sigfd;
+       PublicInbox::restore_signals($oldset) if !$sigfd;
        while (1) { # main loop
                my $n = scalar keys %pids;
                unless (@listeners) {
@@ -528,7 +524,7 @@ EOF
                }
                my $want = $worker_processes - 1;
                if ($n <= $want) {
-                       sig_setmask($newset) if !$sigfd;
+                       PublicInbox::Sigfd::block_signals() if !$sigfd;
                        for my $i ($n..$want) {
                                my $pid = fork;
                                if (!defined $pid) {
@@ -541,7 +537,7 @@ EOF
                                        $pids{$pid} = $i;
                                }
                        }
-                       sig_setmask($oldset) if !$sigfd;
+                       PubliInbox::Sigfd::set_sigmask($oldset) if !$sigfd;
                }
 
                if ($sigfd) { # Linux and IO::KQueue users:
@@ -566,11 +562,12 @@ sub defer_accept ($$) {
        my ($s, $af_name) = @_;
        return unless defined $af_name;
        if ($^O eq 'linux') {
-               my $x = getsockopt($s, IPPROTO_TCP, Socket::TCP_DEFER_ACCEPT());
+               my $TCP_DEFER_ACCEPT = 9; # Socket::TCP_DEFER_ACCEPT is in 5.14+
+               my $x = getsockopt($s, IPPROTO_TCP, $TCP_DEFER_ACCEPT);
                return unless defined $x; # may be Unix socket
                my $sec = unpack('i', $x);
                return if $sec > 0; # systemd users may set a higher value
-               setsockopt($s, IPPROTO_TCP, Socket::TCP_DEFER_ACCEPT(), 1);
+               setsockopt($s, IPPROTO_TCP, $TCP_DEFER_ACCEPT, 1);
        } elsif ($^O eq 'freebsd') {
                my $x = getsockopt($s, SOL_SOCKET, SO_ACCEPTFILTER);
                return if defined $x; # don't change if set
@@ -580,13 +577,13 @@ sub defer_accept ($$) {
 }
 
 sub daemon_loop ($$$$) {
-       my ($refresh, $post_accept, $nntpd, $af_default) = @_;
+       my ($refresh, $post_accept, $tlsd, $af_default) = @_;
        my %post_accept;
        while (my ($k, $v) = each %tls_opt) {
-               if ($k =~ s!\A(?:nntps|https)://!!) {
+               if ($k =~ s!\A(?:https|imaps|nntps)://!!) {
                        $post_accept{$k} = tls_start_cb($v, $post_accept);
-               } elsif ($nntpd) { # STARTTLS, $k eq '' is OK
-                       $nntpd->{accept_tls} = $v;
+               } elsif ($tlsd) { # STARTTLS, $k eq '' is OK
+                       $tlsd->{accept_tls} = $v;
                }
        }
        my $sig = {
@@ -601,11 +598,10 @@ sub daemon_loop ($$$$) {
                WINCH => 'IGNORE',
                CHLD => \&PublicInbox::DS::enqueue_reap,
        };
-       my $parent_pipe;
        if ($worker_processes > 0) {
                $refresh->(); # preload by default
                my $fh = master_loop(); # returns if in child process
-               $parent_pipe = PublicInbox::ParentPipe->new($fh, *worker_quit);
+               PublicInbox::EOFpipe->new($fh, \&worker_quit, undef);
        } else {
                reopen_logs();
                $set_user->() if $set_user;
@@ -617,30 +613,31 @@ sub daemon_loop ($$$$) {
        @listeners = map {;
                my $tls_cb = $post_accept{sockname($_)};
 
-               # NNTPS, HTTPS, HTTP, and POP3S are client-first traffic
-               # NNTP and POP3 are server-first
+               # NNTPS, HTTPS, HTTP, IMAPS and POP3S are client-first traffic
+               # IMAP, NNTP and POP3 are server-first
                defer_accept($_, $tls_cb ? 'dataready' : $af_default);
 
                # this calls epoll_create:
                PublicInbox::Listener->new($_, $tls_cb || $post_accept)
        } @listeners;
-       my $sigfd = PublicInbox::Sigfd->new($sig, SFD_NONBLOCK);
+       my $sigfd = PublicInbox::Sigfd->new($sig, $SFD_NONBLOCK);
        local %SIG = (%SIG, %$sig) if !$sigfd;
        if (!$sigfd) {
                # wake up every second to accept signals if we don't
                # have signalfd or IO::KQueue:
-               sig_setmask($oldset);
+               PublicInbox::Sigfd::set_sigmask($oldset);
                PublicInbox::DS->SetLoopTimeout(1000);
        }
        PublicInbox::DS->EventLoop;
 }
 
 sub run ($$$;$) {
-       my ($default, $refresh, $post_accept, $nntpd) = @_;
+       my ($default, $refresh, $post_accept, $tlsd) = @_;
+       local $SIG{PIPE} = 'IGNORE';
        daemon_prepare($default);
        my $af_default = $default =~ /:8080\z/ ? 'httpready' : undef;
        my $for_destroy = daemonize();
-       daemon_loop($refresh, $post_accept, $nntpd, $af_default);
+       daemon_loop($refresh, $post_accept, $tlsd, $af_default);
        PublicInbox::DS->Reset;
        # ->DESTROY runs when $for_destroy goes out-of-scope
 }
@@ -659,7 +656,7 @@ sub write_pid ($) {
 }
 
 sub DESTROY {
-       unlink_pid_file_safe_ish($_[0]->{pid}, $_[0]->{pid_file});
+       unlink_pid_file_safe_ish($_[0]->{pid}, ${$_[0]->{pid_file}});
 }
 
 1;