# Abstract class to implement Daemons
#
# Features:
-# * lock and write PID file /var/run/$name.pid to make sure onyl
+# * lock and write PID file /var/run/$name.pid to make sure only
# one instance is running.
# * keep lock open during restart
# * correctly daemonize (redirect STDIN/STDOUT)
# * allow to restart while workers are still runningl
# (option 'leave_children_open_on_reload')
# * run as different user using setuid/setgid
-
+
use strict;
use warnings;
use English;
$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
my $daemon_initialized = 0; # we only allow one instance
+my $daemon_sockets = [];
my $close_daemon_lock = sub {
my ($self) = @_;
for my $sig (qw(CHLD HUP INT TERM QUIT)) {
$SIG{$sig} = 'DEFAULT'; # restore default handler
- # AnyEvent signals only works if $SIG{XX} is
+ # AnyEvent signals only works if $SIG{XX} is
# undefined (perl event loop)
delete $SIG{$sig}; # so that we can handle events with AnyEvent
}
if (my $fd = $self->{env_pve_lock_fd}) {
$self->{daemon_lock_fh} = IO::Handle->new_from_fd($fd, "a");
-
+
} else {
$waittime = 5;
my ($running, $pid) = $self->running();
if ($running) {
- die "can't aquire lock '$lkfn' - daemon already started (pid = $pid)\n";
+ die "can't acquire lock '$lkfn' - daemon already started (pid = $pid)\n";
} else {
- die "can't aquire lock '$lkfn' - $err\n";
+ die "can't acquire lock '$lkfn' - $err\n";
}
}
};
my $pidfile = $self->{pidfile};
- die "can't open pid file '$pidfile' - $!\n" if !open (PIDFH, ">$pidfile");
+ open (my $PID_FH, '>', "$pidfile") or die "can't open pid file '$pidfile' - $!\n";
- print PIDFH "$$\n";
- close (PIDFH);
+ print $PID_FH "$$\n";
+ close ($PID_FH);
};
my $server_cleanup = sub {
return if $self->{terminate};
- my $count = 0;
- foreach my $cpid (keys %{$self->{workers}}) {
- $count++;
- }
-
+ my $count = scalar keys %{$self->{workers}};
my $need = $self->{max_workers} - $count;
return if $need <= 0;
}
};
+my $terminate_old_workers = sub {
+ my ($self) = @_;
+
+ # if list is empty kill sends no signal, so no checks needed
+ kill 15, keys %{$self->{old_workers}};
+};
+
my $terminate_server = sub {
my ($self, $allow_open_children) = @_;
$self->{terminate} = 1; # set flag to avoid worker restart
- if (!$self->{max_workers}) {
- eval { $self->shutdown(); };
- warn $@ if $@;
- return;
- }
-
eval { $self->shutdown(); };
warn $@ if $@;
- # we have workers - send TERM signal
-
- foreach my $cpid (keys %{$self->{workers}}) {
- kill(15, $cpid); # TERM childs
- }
+ return if !$self->{max_workers}; # if we have no workers we're done here
# if configured, leave children running on HUP
- return if $allow_open_children &&
- $self->{leave_children_open_on_reload};
+ return if $allow_open_children && $self->{leave_children_open_on_reload};
- # else, send TERM to old workers
- foreach my $cpid (keys %{$self->{old_workers}}) {
- kill(15, $cpid); # TERM childs
- }
+ # else send TERM to all (old and current) child workers
+ kill 15, (keys %{$self->{workers}}, keys %{$self->{old_workers}});
# nicely shutdown childs (give them max 10 seconds to shut down)
my $previous_alarm = alarm(10);
}
};
+sub setup {
+ my ($self) = @_;
+
+ initlog($self->{name});
+
+ my $restart = delete $ENV{RESTART_PVE_DAEMON};
+ $self->{env_restart_pve_daemon} = $restart;
+
+ my $lockfd = $ENV{PVE_DAEMON_LOCK_FD};
+ delete $ENV{PVE_DAEMON_LOCK_FD};
+ if (defined($lockfd)) {
+ die "unable to parse lock fd '$lockfd'\n"
+ if $lockfd !~ m/^(\d+)$/;
+ $lockfd = $1; # untaint
+ }
+ $self->{env_pve_lock_fd} = $lockfd;
+
+ die "please run as root\n" if !$restart && ($> != 0);
+
+ die "can't create more that one PVE::Daemon" if $daemon_initialized;
+ $daemon_initialized = 1;
+
+ PVE::INotify::inotify_init();
+
+ if (my $gidstr = $self->{setgid}) {
+ my $gid = getgrnam($gidstr) || die "getgrnam failed - $!\n";
+ POSIX::setgid($gid) || die "setgid $gid failed - $!\n";
+ $EGID = "$gid $gid"; # this calls setgroups
+ # just to be sure
+ die "detected strange gid\n" if !($GID eq "$gid $gid" && $EGID eq "$gid $gid");
+ }
+
+ if (my $uidstr = $self->{setuid}) {
+ my $uid = getpwnam($uidstr) || die "getpwnam failed - $!\n";
+ POSIX::setuid($uid) || die "setuid $uid failed - $!\n";
+ # just to be sure
+ die "detected strange uid\n" if !($UID == $uid && $EUID == $uid);
+ }
+
+ if ($restart && $self->{max_workers}) {
+ if (my $wpids = $ENV{PVE_DAEMON_WORKER_PIDS}) {
+ foreach my $pid (split(':', $wpids)) {
+ # check & untaint
+ if ($pid =~ m/^(\d+)$/) {
+ $self->{old_workers}->{$1} = 1;
+ }
+ }
+ }
+ }
+
+ $self->{nodename} = PVE::INotify::nodename();
+}
+
my $server_run = sub {
my ($self, $debug) = @_;
$self->init();
if (!$debug) {
- open STDIN, '</dev/null' || die "can't read /dev/null";
- open STDOUT, '>/dev/null' || die "can't write /dev/null";
+ open STDIN, '<', '/dev/null' or die "can't read /dev/null - $!";
+ open STDOUT, '>', '/dev/null' or die "can't write /dev/null - $!";
}
if (!$self->{env_restart_pve_daemon} && !$debug) {
syslog('info' , "starting server");
}
- POSIX::setsid();
+ POSIX::setsid();
open STDERR, '>&STDOUT' || die "can't close STDERR\n";
}
};
- eval {
+ eval {
if ($self->{max_workers}) {
my $old_sig_chld = $SIG{CHLD};
local $SIG{CHLD} = sub {
&$old_sig_chld(@_) if $old_sig_chld;
};
- # catch worker finished during restart phase
- &$finish_workers($self);
-
# now loop forever (until we receive terminate signal)
- for (;;) {
+ for (;;) {
&$start_workers($self);
sleep(5);
+ &$terminate_old_workers($self);
&$finish_workers($self);
last if $self->{terminate};
}
} else {
$self->run();
- }
+ }
};
my $err = $@;
$name = 'daemon' if !$name; # should not happen
- initlog($name);
-
my $self;
eval {
-
- my $restart = $ENV{RESTART_PVE_DAEMON};
- delete $ENV{RESTART_PVE_DAEMON};
-
- my $lockfd = $ENV{PVE_DAEMON_LOCK_FD};
- delete $ENV{PVE_DAEMON_LOCK_FD};
-
- if (defined($lockfd)) {
- die "unable to parse lock fd '$lockfd'\n"
- if $lockfd !~ m/^(\d+)$/;
- $lockfd = $1; # untaint
- }
-
- die "please run as root\n" if !$restart && ($> != 0);
-
- die "can't create more that one PVE::Daemon" if $daemon_initialized;
- $daemon_initialized = 1;
-
- PVE::INotify::inotify_init();
-
my $class = ref($this) || $this;
- $self = bless {
+ $self = bless {
name => $name,
pidfile => "/var/run/${name}.pid",
- env_restart_pve_daemon => $restart,
- env_pve_lock_fd => $lockfd,
workers => {},
old_workers => {},
}, $class;
die "unknown daemon option '$opt'\n";
}
}
-
- if (my $gidstr = $self->{setgid}) {
- my $gid = getgrnam($gidstr) || die "getgrnam failed - $!\n";
- POSIX::setgid($gid) || die "setgid $gid failed - $!\n";
- $EGID = "$gid $gid"; # this calls setgroups
- # just to be sure
- die "detected strange gid\n" if !($GID eq "$gid $gid" && $EGID eq "$gid $gid");
- }
-
- if (my $uidstr = $self->{setuid}) {
- my $uid = getpwnam($uidstr) || die "getpwnam failed - $!\n";
- POSIX::setuid($uid) || die "setuid $uid failed - $!\n";
- # just to be sure
- die "detected strange uid\n" if !($UID == $uid && $EUID == $uid);
- }
-
- if ($restart && $self->{max_workers}) {
- if (my $wpids = $ENV{PVE_DAEMON_WORKER_PIDS}) {
- foreach my $pid (split(':', $wpids)) {
- if ($pid =~ m/^(\d+)$/) {
- $self->{old_workers}->{$1} = 1;
- }
- }
- }
- }
-
- $self->{nodename} = PVE::INotify::nodename();
- $self->{cmdline} = [];
- foreach my $el (@$cmdline) {
- $el =~ m/^(.*)$/; # untaint
- push @{$self->{cmdline}}, $1;
- }
+ # untaint
+ $self->{cmdline} = [map { /^(.*)$/ } @$cmdline];
$0 = $name;
};
$ENV{RESTART_PVE_DAEMON} = 1;
+ foreach my $ds (@$daemon_sockets) {
+ $ds->fcntl(Fcntl::F_SETFD(), 0);
+ }
+
if ($self->{max_workers}) {
- my @workers = keys %{$self->{workers}};
- push @workers, keys %{$self->{old_workers}};
+ my @workers = (keys %{$self->{workers}}, keys %{$self->{old_workers}});
$ENV{PVE_DAEMON_WORKER_PIDS} = join(':', @workers);
}
sub start {
my ($self, $debug) = @_;
- eval { &$server_run($self, $debug); };
+ eval {
+ $self->setup();
+ &$server_run($self, $debug);
+ };
if (my $err = $@) {
&$log_err("start failed - $err");
exit(-1);
return 0 if !$pid_str;
return 0 if $pid_str !~ m/^(\d+)$/; # untaint
-
+
my $pid = int($1);
return $pid;
};
+# checks if the process was started by systemd
+my $init_ppid = sub {
+ if (getppid() == 1) {
+ return 1;
+ } else {
+ return 0;
+ }
+};
+
sub running {
my ($self) = @_;
code => sub {
my ($param) = @_;
- $self->start($param->{debug});
+ if (&$init_ppid() || $param->{debug}) {
+ $self->start($param->{debug});
+ } else {
+ PVE::Tools::run_command(['systemctl', 'start', $self->{name}]);
+ }
return undef;
- }});
+ }});
}
my $reload_daemon = sub {
if ($self->{env_restart_pve_daemon}) {
$self->start();
} else {
- my ($running, $pid) = $self->running();
+ my ($running, $pid) = $self->running();
if (!$running) {
$self->start();
} else {
code => sub {
my ($param) = @_;
- &$reload_daemon($self, $use_hup);
+ if (&$init_ppid()) {
+ &$reload_daemon($self, $use_hup);
+ } else {
+ PVE::Tools::run_command(['systemctl', $use_hup ? 'reload-or-restart' : 'restart', $self->{name}]);
+ }
return undef;
- }});
+ }});
}
sub register_reload_command {
&$reload_daemon($self, 1);
return undef;
- }});
+ }});
}
sub register_stop_command {
code => sub {
my ($param) = @_;
-
- $self->stop();
+
+ if (&$init_ppid()) {
+ $self->stop();
+ } else {
+ PVE::Tools::run_command(['systemctl', 'stop', $self->{name}]);
+ }
return undef;
- }});
+ }});
}
sub register_status_command {
additionalProperties => 0,
properties => {},
},
- returns => {
+ returns => {
type => 'string',
enum => ['stopped', 'running'],
},
if (defined($sockfd = $ENV{"PVE_DAEMON_SOCKET_$port"}) &&
$self->{env_restart_pve_daemon}) {
- die "unable to parse socket fd '$sockfd'\n"
+ die "unable to parse socket fd '$sockfd'\n"
if $sockfd !~ m/^(\d+)$/;
$sockfd = $1; # untaint
$socket = IO::Socket::IP->new;
- $socket->fdopen($sockfd, 'w') ||
+ $socket->fdopen($sockfd, 'w') ||
die "cannot fdopen file descriptor '$sockfd' - $!\n";
+ $socket->fcntl(Fcntl::F_SETFD(), Fcntl::FD_CLOEXEC);
} else {
- $socket = IO::Socket::IP->new(
- LocalAddr => $host,
+ my %sockargs = (
LocalPort => $port,
Listen => SOMAXCONN,
Proto => 'tcp',
- ReuseAddr => 1) ||
- die "unable to create socket - $@\n";
+ GetAddrInfoFlags => 0,
+ ReuseAddr => 1,
+ );
+ if (defined($host)) {
+ $socket = IO::Socket::IP->new( LocalHost => $host, %sockargs) ||
+ die "unable to create socket - $@\n";
+ } else {
+ # disabling AF_INET6 (by adding ipv6.disable=1 to the kernel cmdline)
+ # causes bind on :: to fail, try 0.0.0.0 in that case
+ $socket = IO::Socket::IP->new( LocalHost => '::', %sockargs) //
+ IO::Socket::IP->new( LocalHost => '0.0.0.0', %sockargs);
+ die "unable to create socket - $@\n" if !$socket;
+ }
# we often observe delays when using Nagle algorithm,
# so we disable that to maximize performance
$ENV{"PVE_DAEMON_SOCKET_$port"} = $socket->fileno;
}
- # remove FD_CLOEXEC bit to reuse on exec
- $socket->fcntl(Fcntl::F_SETFD(), 0);
-
+ push @$daemon_sockets, $socket;
+
return $socket;
}