]> git.proxmox.com Git - pve-common.git/blob - data/PVE/Daemon.pm
f03e7cf9f0aec047e9eb5a896dd698212913e624
[pve-common.git] / data / PVE / Daemon.pm
1 package PVE::Daemon;
2
3 # Abstract class to implement Daemons
4 #
5 # Features:
6 # * lock and write PID file /var/run/$name.pid to make sure onyl
7 # one instance is running.
8 # * correctly daemonize (redirect STDIN/STDOUT)
9 # * restart by stop/start, exec, or signal HUP
10 # * daemon restart on error (option 'restart_on_error')
11 # * handle worker processes (option 'max_workers')
12
13 use strict;
14 use warnings;
15 use PVE::SafeSyslog;
16 use PVE::INotify;
17
18 use POSIX ":sys_wait_h";
19 use Fcntl ':flock';
20 use Getopt::Long;
21 use Time::HiRes qw (gettimeofday);
22
23 use base qw(PVE::CLIHandler);
24
25 $SIG{'__WARN__'} = sub {
26 my $err = $@;
27 my $t = $_[0];
28 chomp $t;
29 print "$t\n";
30 syslog('warning', "WARNING: %s", $t);
31 $@ = $err;
32 };
33
34 $ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
35
36 my $daemon_initialized = 0; # we only allow one instance
37
38 my $close_daemon_lock = sub {
39 my ($self) = @_;
40
41 close $self->{daemon_lock_fh};
42 delete $self->{daemon_lock_fh};
43 };
44
45 my $lockpidfile = sub {
46 my ($self) = @_;
47
48 my $lkfn = $self->{pidfile} . ".lock";
49
50 $self->{daemon_lock_fh} = IO::File->new(">>$lkfn");
51 if (!$self->{daemon_lock_fh}) {
52 my $msg = "can't aquire lock on file '$lkfn' - $!";
53 syslog ('err', $msg);
54 die "ERROR: $msg\n";
55 }
56
57 for (my $i = 0; $i < 5; $i ++) {
58 return if flock ($self->{daemon_lock_fh}, LOCK_EX|LOCK_NB);
59 sleep(1);
60 }
61
62 if (!flock ($self->{daemon_lock_fh}, LOCK_EX|LOCK_NB)) {
63 &$close_daemon_lock($self);
64 my $msg = "can't aquire lock '$lkfn' - $!";
65 syslog ('err', $msg);
66 die "ERROR: $msg\n";
67 }
68 };
69
70 my $writepidfile = sub {
71 my ($self) = @_;
72
73 my $pidfile = $self->{pidfile};
74
75 if (!open (PIDFH, ">$pidfile")) {
76 my $msg = "can't open pid file '$pidfile' - $!";
77 syslog ('err', $msg);
78 die "ERROR: $msg\n";
79 }
80 print PIDFH "$$\n";
81 close (PIDFH);
82 };
83
84 my $server_cleanup = sub {
85 my ($self) = @_;
86
87 unlink $self->{pidfile} . ".lock";
88 unlink $self->{pidfile};
89 };
90
91 my $finish_workers = sub {
92 my ($self) = @_;
93
94 foreach my $cpid (keys %{$self->{workers}}) {
95 my $waitpid = waitpid($cpid, WNOHANG);
96 if (defined($waitpid) && ($waitpid == $cpid)) {
97 delete ($self->{workers}->{$cpid});
98 syslog('info', "worker $cpid finished");
99 }
100 }
101 };
102
103 my $start_workers = sub {
104 my ($self) = @_;
105
106 return if $self->{terminate};
107
108 my $count = 0;
109 foreach my $cpid (keys %{$self->{workers}}) {
110 $count++;
111 }
112
113 my $need = $self->{max_workers} - $count;
114
115 return if $need <= 0;
116
117 syslog('info', "starting $need worker(s)");
118
119 while ($need > 0) {
120 my $pid = fork;
121
122 if (!defined ($pid)) {
123 syslog('err', "can't fork worker");
124 sleep (1);
125 } elsif ($pid) { # parent
126 $self->{workers}->{$pid} = 1;
127 syslog('info', "worker $pid started");
128 $need--;
129 } else {
130 $0 = "$self->{name} worker";
131
132 &$close_daemon_lock($self);
133
134 PVE::INotify::inotify_close();
135
136 for my $sig (qw(CHLD HUP INT TERM QUIT)) {
137 $SIG{$sig} = 'DEFAULT'; # restore default handler
138 # AnyEvent signals only works if $SIG{XX} is
139 # undefined (perl event loop)
140 delete $SIG{$sig}; # so that we can handle events with AnyEvent
141 }
142
143 eval { $self->run(); };
144 if (my $err = $@) {
145 syslog('err', $err);
146 sleep(5); # avoid fast restarts
147 }
148
149 syslog('info', "worker exit");
150 exit (0);
151 }
152 }
153 };
154
155 my $terminate_server = sub {
156 my ($self) = @_;
157
158 $self->{terminate} = 1; # set flag to avoid worker restart
159
160 if (!$self->{max_workers}) {
161 eval { $self->shutdown(); };
162 warn $@ if $@;
163 return;
164 }
165
166 eval { $self->shutdown(); };
167 warn $@ if $@;
168
169 # we have workers - terminate them
170
171 foreach my $cpid (keys %{$self->{workers}}) {
172 kill(15, $cpid); # TERM childs
173 }
174
175 # nicely shutdown childs (give them max 10 seconds to shut down)
176 my $previous_alarm = alarm(10);
177 eval {
178 local $SIG{ALRM} = sub { die "timeout\n" };
179
180 while ((my $pid = waitpid (-1, 0)) > 0) {
181 if (defined($self->{workers}->{$pid})) {
182 delete($self->{workers}->{$pid});
183 syslog('info', "worker $pid finished");
184 }
185 }
186 alarm(0); # avoid race condition
187 };
188 my $err = $@;
189
190 alarm ($previous_alarm);
191
192 if ($err) {
193 syslog('err', "error stopping workers (will kill them now) - $err");
194 foreach my $cpid (keys %{$self->{workers}}) {
195 # KILL childs still alive!
196 if (kill (0, $cpid)) {
197 delete($self->{workers}->{$cpid});
198 syslog("err", "kill worker $cpid");
199 kill(9, $cpid);
200 # fixme: waitpid?
201 }
202 }
203 }
204 };
205
206 my $server_run = sub {
207 my ($self, $debug) = @_;
208
209 &$lockpidfile($self);
210
211 # run in background
212 my $spid;
213
214 my $restart = $ENV{RESTART_PVE_DAEMON};
215
216 delete $ENV{RESTART_PVE_DAEMON};
217
218 $self->{debug} = 1 if $debug;
219
220 $self->init();
221
222 if (!$debug) {
223 open STDIN, '</dev/null' || die "can't read /dev/null";
224 open STDOUT, '>/dev/null' || die "can't write /dev/null";
225 }
226
227 if (!$restart && !$debug) {
228 PVE::INotify::inotify_close();
229 $spid = fork();
230 if (!defined ($spid)) {
231 my $msg = "can't put server into background - fork failed";
232 syslog('err', $msg);
233 die "ERROR: $msg\n";
234 } elsif ($spid) { # parent
235 exit (0);
236 }
237 PVE::INotify::inotify_init();
238 }
239
240 &$writepidfile($self);
241
242 POSIX::setsid();
243
244 if ($restart) {
245 syslog('info' , "restarting server");
246 } else {
247 syslog('info' , "starting server");
248 }
249
250 open STDERR, '>&STDOUT' || die "can't close STDERR\n";
251
252 my $old_sig_term = $SIG{TERM};
253 local $SIG{TERM} = sub {
254 local ($@, $!, $?); # do not overwrite error vars
255 syslog('info', "received signal TERM");
256 &$terminate_server($self);
257 &$server_cleanup($self);
258 &$old_sig_term(@_) if $old_sig_term;
259 };
260
261 my $old_sig_quit = $SIG{QUIT};
262 local $SIG{QUIT} = sub {
263 local ($@, $!, $?); # do not overwrite error vars
264 syslog('info', "received signal QUIT");
265 &$terminate_server($self);
266 &$server_cleanup($self);
267 &$old_sig_quit(@_) if $old_sig_quit;
268 };
269
270 my $old_sig_int = $SIG{INT};
271 local $SIG{INT} = sub {
272 local ($@, $!, $?); # do not overwrite error vars
273 syslog('info', "received signal INT");
274 $SIG{INT} = 'DEFAULT'; # allow to terminate now
275 &$terminate_server($self);
276 &$server_cleanup($self);
277 &$old_sig_int(@_) if $old_sig_int;
278 };
279
280 $SIG{HUP} = sub {
281 local ($@, $!, $?); # do not overwrite error vars
282 syslog('info', "received signal HUP");
283 if ($self->{max_workers}) {
284 &$terminate_server($self);
285 $self->{got_hup_signal} = 1;
286 } elsif ($self->can('hup')) {
287 eval { $self->hup() };
288 warn $@ if $@;
289 }
290 };
291
292 eval {
293 if ($self->{max_workers}) {
294 my $old_sig_chld = $SIG{CHLD};
295 local $SIG{CHLD} = sub {
296 local ($@, $!, $?); # do not overwrite error vars
297 &$finish_workers($self);
298 &$old_sig_chld(@_) if $old_sig_chld;
299 };
300
301 for (;;) { # forever
302 &$start_workers($self);
303 sleep(5);
304 &$finish_workers($self);
305 last if $self->{terminate};
306 }
307
308 } else {
309 $self->run();
310 }
311 };
312 my $err = $@;
313
314 if ($err) {
315 syslog ('err', "ERROR: $err");
316
317 # fixme: kill all workers
318
319 if (my $wait_time = $self->{restart_on_error}) {
320 $self->restart_daemon($wait_time);
321 } else {
322 $self->exit_daemon(-1);
323 }
324 }
325
326 if ($self->{got_hup_signal}) {
327 $self->restart_daemon();
328 } else {
329 $self->exit_daemon(0);
330 }
331 };
332
333 sub new {
334 my ($this, $name, $cmdline, %params) = @_;
335
336 die "please run as root\n" if !$ENV{RESTART_PVE_DAEMON} && ($> != 0);
337
338 die "missing name" if !$name;
339
340 die "can't create more that one PVE::Daemon" if $daemon_initialized;
341 $daemon_initialized = 1;
342
343 PVE::INotify::inotify_init();
344
345 initlog($name);
346
347 my $class = ref($this) || $this;
348
349 my $self = bless {
350 name => $name,
351 run_dir => '/var/run',
352 workers => {},
353 }, $class;
354
355 foreach my $opt (keys %params) {
356 my $value = $params{$opt};
357 if ($opt eq 'restart_on_error') {
358 $self->{$opt} = $value;
359 } elsif ($opt eq 'stop_wait_time') {
360 $self->{$opt} = $value;
361 } elsif ($opt eq 'run_dir') {
362 $self->{$opt} = $value;
363 } elsif ($opt eq 'max_workers') {
364 $self->{$opt} = $value;
365 } else {
366 die "unknown option '$opt'";
367 }
368 }
369
370 $self->{pidfile} = "$self->{run_dir}/${name}.pid";
371
372 $self->{nodename} = PVE::INotify::nodename();
373
374 $self->{cmdline} = [];
375
376 foreach my $el (@$cmdline) {
377 $el =~ m/^(.*)$/; # untaint
378 push @{$self->{cmdline}}, $1;
379 }
380
381 $0 = $name;
382
383 return $self;
384 }
385
386 sub exit_daemon {
387 my ($self, $status) = @_;
388
389 syslog("info", "server stopped");
390
391 &$server_cleanup($self);
392
393 exit($status);
394 }
395
396 sub restart_daemon {
397 my ($self, $waittime) = @_;
398
399 syslog('info', "server shutdown (restart)");
400
401 $ENV{RESTART_PVE_DAEMON} = 1;
402
403 sleep($waittime) if $waittime; # avoid high server load due to restarts
404
405 PVE::INotify::inotify_close();
406
407 exec (@{$self->{cmdline}});
408
409 exit (-1); # never reached?
410 }
411
412 # please overwrite in subclass
413 # this is called at startup - before forking
414 sub init {
415 my ($self) = @_;
416
417 }
418
419 # please overwrite in subclass
420 sub shutdown {
421 my ($self) = @_;
422
423 syslog('info' , "server closing");
424
425 if (!$self->{max_workers}) {
426 # wait for children
427 1 while (waitpid(-1, POSIX::WNOHANG()) > 0);
428 }
429 }
430
431 # please define in subclass
432 #sub hup {
433 # my ($self) = @_;
434 #
435 # syslog('info' , "received signal HUP (restart)");
436 #}
437
438 # please overwrite in subclass
439 sub run {
440 my ($self) = @_;
441
442 for (;;) { # forever
443 syslog('info' , "server is running");
444 sleep(5);
445 }
446 }
447
448 sub start {
449 my ($self, $debug) = @_;
450
451 &$server_run($self, $debug);
452 }
453
454 my $read_pid = sub {
455 my ($self) = @_;
456
457 my $pid_str = PVE::Tools::file_read_firstline($self->{pidfile});
458
459 return 0 if !$pid_str;
460
461 return 0 if $pid_str !~ m/^(\d+)$/; # untaint
462
463 my $pid = int($1);
464
465 return $pid;
466 };
467
468 sub running {
469 my ($self) = @_;
470
471 my $pid = &$read_pid($self);
472
473 if ($pid) {
474 my $res = PVE::ProcFSTools::check_process_running($pid) ? 1 : 0;
475 return wantarray ? ($res, $pid) : $res;
476 }
477
478 return wantarray ? (0, 0) : 0;
479 }
480
481 sub stop {
482 my ($self) = @_;
483
484 my $pid = &$read_pid($self);
485
486 return if !$pid;
487
488 if (PVE::ProcFSTools::check_process_running($pid)) {
489 kill(15, $pid); # send TERM signal
490 # give some time
491 my $wait_time = $self->{stop_wait_time} || 5;
492 my $running = 1;
493 for (my $i = 0; $i < $wait_time; $i++) {
494 $running = PVE::ProcFSTools::check_process_running($pid);
495 last if !$running;
496 sleep (1);
497 }
498
499 syslog('err', "server still running - send KILL") if $running;
500
501 # to be sure
502 kill(9, $pid);
503 waitpid($pid, 0);
504 }
505
506 if (-f $self->{pidfile}) {
507 # try to get the lock
508 &$lockpidfile($self);
509 &$server_cleanup($self);
510 }
511 }
512
513 sub register_start_command {
514 my ($self, $class, $description) = @_;
515
516 $class->register_method({
517 name => 'start',
518 path => 'start',
519 method => 'POST',
520 description => $description || "Start the daemon.",
521 parameters => {
522 additionalProperties => 0,
523 properties => {
524 debug => {
525 description => "Debug mode - stay in foreground",
526 type => "boolean",
527 optional => 1,
528 default => 0,
529 },
530 },
531 },
532 returns => { type => 'null' },
533
534 code => sub {
535 my ($param) = @_;
536
537 $self->start($param->{debug});
538
539 return undef;
540 }});
541 }
542
543 my $reload_daemon = sub {
544 my ($self, $use_hup) = @_;
545
546 if (my $restart = $ENV{RESTART_PVE_DAEMON}) {
547 $self->start();
548 } else {
549 my ($running, $pid) = $self->running();
550 if (!$running) {
551 $self->start();
552 } else {
553 if ($use_hup) {
554 syslog('info', "send HUP to $pid");
555 kill 1, $pid;
556 } else {
557 $self->stop();
558 $self->start();
559 }
560 }
561 }
562 };
563
564 sub register_restart_command {
565 my ($self, $class, $use_hup, $description) = @_;
566
567 $class->register_method({
568 name => 'restart',
569 path => 'restart',
570 method => 'POST',
571 description => $description || "Restart the daemon (or start if not running).",
572 parameters => {
573 additionalProperties => 0,
574 properties => {},
575 },
576 returns => { type => 'null' },
577
578 code => sub {
579 my ($param) = @_;
580
581 &$reload_daemon($self, $use_hup);
582
583 return undef;
584 }});
585 }
586
587 sub register_reload_command {
588 my ($self, $class, $description) = @_;
589
590 $class->register_method({
591 name => 'reload',
592 path => 'reload',
593 method => 'POST',
594 description => $description || "Reload daemon configuration (or start if not running).",
595 parameters => {
596 additionalProperties => 0,
597 properties => {},
598 },
599 returns => { type => 'null' },
600
601 code => sub {
602 my ($param) = @_;
603
604 &$reload_daemon($self, 1);
605
606 return undef;
607 }});
608 }
609
610 sub register_stop_command {
611 my ($self, $class, $description) = @_;
612
613 $class->register_method({
614 name => 'stop',
615 path => 'stop',
616 method => 'POST',
617 description => $description || "Stop the daemon.",
618 parameters => {
619 additionalProperties => 0,
620 properties => {},
621 },
622 returns => { type => 'null' },
623
624 code => sub {
625 my ($param) = @_;
626
627 $self->stop();
628
629 return undef;
630 }});
631 }
632
633 sub register_status_command {
634 my ($self, $class, $description) = @_;
635
636 $class->register_method({
637 name => 'status',
638 path => 'status',
639 method => 'GET',
640 description => "Get daemon status.",
641 parameters => {
642 additionalProperties => 0,
643 properties => {},
644 },
645 returns => {
646 type => 'string',
647 enum => ['stopped', 'running'],
648 },
649 code => sub {
650 my ($param) = @_;
651
652 return $self->running() ? 'running' : 'stopped';
653 }});
654 }
655
656 1;
657