]> git.proxmox.com Git - pve-common.git/blame - data/PVE/Daemon.pm
improve doc
[pve-common.git] / data / PVE / Daemon.pm
CommitLineData
390802ab
DM
1package 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)
4fc69176
DM
9# * restart by stop/start, exec, or signal HUP
10# * daemon restart on error (option 'restart_on_error')
11#
390802ab
DM
12
13use strict;
14use warnings;
15use PVE::SafeSyslog;
16use PVE::INotify;
17
18use POSIX ":sys_wait_h";
19use Fcntl ':flock';
20use Getopt::Long;
21use Time::HiRes qw (gettimeofday);
22
23use 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
36my $daemon_initialized = 0; # we only allow one instance
37
38my $lockpidfile = sub {
39 my ($self) = @_;
40
41 my $lkfn = $self->{pidfile} . ".lock";
42
43 if (!open (FLCK, ">>$lkfn")) {
44 my $msg = "can't aquire lock on file '$lkfn' - $!";
45 syslog ('err', $msg);
46 die "ERROR: $msg\n";
47 }
48
49 if (!flock (FLCK, LOCK_EX|LOCK_NB)) {
50 close (FLCK);
51 my $msg = "can't aquire lock '$lkfn' - $!";
52 syslog ('err', $msg);
53 die "ERROR: $msg\n";
54 }
55};
56
57my $writepidfile = sub {
58 my ($self) = @_;
59
60 my $pidfile = $self->{pidfile};
61
62 if (!open (PIDFH, ">$pidfile")) {
63 my $msg = "can't open pid file '$pidfile' - $!";
64 syslog ('err', $msg);
65 die "ERROR: $msg\n";
66 }
67 print PIDFH "$$\n";
68 close (PIDFH);
69};
70
71my $server_cleanup = sub {
72 my ($self) = @_;
73
74 unlink $self->{pidfile} . ".lock";
75 unlink $self->{pidfile};
76};
77
78my $server_run = sub {
79 my ($self, $debug) = @_;
80
81 &$lockpidfile($self);
82
83 # run in background
84 my $spid;
85
86 my $restart = $ENV{RESTART_PVE_DAEMON};
87
88 delete $ENV{RESTART_PVE_DAEMON};
89
90 $self->{debug} = 1 if $debug;
91
92 $self->init();
93
94 if (!$debug) {
95 open STDIN, '</dev/null' || die "can't read /dev/null";
96 open STDOUT, '>/dev/null' || die "can't write /dev/null";
97 }
98
99 if (!$restart && !$debug) {
100 PVE::INotify::inotify_close();
101 $spid = fork();
102 if (!defined ($spid)) {
103 my $msg = "can't put server into background - fork failed";
104 syslog('err', $msg);
105 die "ERROR: $msg\n";
106 } elsif ($spid) { # parent
107 exit (0);
108 }
109 PVE::INotify::inotify_init();
110 }
111
112 &$writepidfile($self);
113
891b9097
DM
114 POSIX::setsid();
115
390802ab
DM
116 if ($restart) {
117 syslog('info' , "restarting server");
118 } else {
119 syslog('info' , "starting server");
120 }
121
122 open STDERR, '>&STDOUT' || die "can't close STDERR\n";
123
124 $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = sub {
125 $SIG{INT} = 'DEFAULT';
126
127 eval { $self->shutdown(); };
128 warn $@ if $@;
129
130 &$server_cleanup($self);
390802ab
DM
131 };
132
bdb5acce
DM
133 if ($self->can('hup')) {
134 $SIG{HUP} = sub {
135 eval { $self->hup() };
136 warn $@ if $@;
137 };
138 }
139
390802ab
DM
140 eval { $self->run() };
141 my $err = $@;
142
143 if ($err) {
144 syslog ('err', "ERROR: $err");
145 if (my $wait_time = $self->{restart_on_error}) {
146 $self->restart_daemon($wait_time);
147 } else {
148 $self->exit_daemon(-1);
149 }
150 }
151
152 $self->exit_daemon(0);
153};
154
155sub new {
156 my ($this, $name, $cmdline, %params) = @_;
157
158 die "please run as root\n" if $> != 0;
159
160 die "missing name" if !$name;
161
162 die "can't create more that one PVE::Daemon" if $daemon_initialized;
163 $daemon_initialized = 1;
164
165 PVE::INotify::inotify_init();
166
167 initlog($name);
168
169 my $class = ref($this) || $this;
170
bdb5acce
DM
171 my $self = bless {
172 name => $name,
173 run_dir => '/var/run',
174 }, $class;
390802ab
DM
175
176 foreach my $opt (keys %params) {
177 my $value = $params{$opt};
178 if ($opt eq 'restart_on_error') {
179 $self->{$opt} = $value;
180 } elsif ($opt eq 'stop_wait_time') {
181 $self->{$opt} = $value;
bdb5acce
DM
182 } elsif ($opt eq 'run_dir') {
183 $self->{$opt} = $value;
390802ab
DM
184 } else {
185 die "unknown option '$opt'";
186 }
187 }
188
bdb5acce
DM
189 $self->{pidfile} = "$self->{run_dir}/${name}.pid";
190
191 $self->{nodename} = PVE::INotify::nodename();
192
193 $self->{cmdline} = $cmdline;
194
195 $0 = $name;
196
197
390802ab
DM
198 return $self;
199}
200
201sub exit_daemon {
202 my ($self, $status) = @_;
203
204 syslog("info", "server stopped");
205
206 &$server_cleanup($self);
207
208 exit($status);
209}
210
211sub restart_daemon {
212 my ($self, $waittime) = @_;
213
214 syslog('info', "server shutdown (restart)");
215
216 $ENV{RESTART_PVE_DAEMON} = 1;
217
218 sleep($waittime) if $waittime; # avoid high server load due to restarts
219
220 PVE::INotify::inotify_close();
221
222 exec (@{$self->{cmdline}});
223
224 exit (-1); # never reached?
225}
226
227# please overwrite in subclass
228# this is called at startup - before forking
229sub init {
230 my ($self) = @_;
231
232}
233
234# please overwrite in subclass
235sub shutdown {
236 my ($self) = @_;
237
238 syslog('info' , "server closing");
239
240 # wait for children
241 1 while (waitpid(-1, POSIX::WNOHANG()) > 0);
242}
243
bdb5acce
DM
244# please define in subclass
245#sub hup {
246# my ($self) = @_;
247#
248# syslog('info' , "received signal HUP (restart)");
249#}
390802ab
DM
250
251# please overwrite in subclass
252sub run {
253 my ($self) = @_;
254
255 for (;;) { # forever
256 syslog('info' , "server is running");
257 sleep(5);
258 }
259}
260
261sub start {
262 my ($self, $debug) = @_;
263
264 &$server_run($self, $debug);
265}
266
bdb5acce
DM
267my $read_pid = sub {
268 my ($self) = @_;
269
270 my $pid_str = PVE::Tools::file_read_firstline($self->{pidfile});
271
272 return 0 if !$pid_str;
273
274 return 0 if $pid_str !~ m/^(\d+)$/; # untaint
275
276 my $pid = int($1);
277
278 return $pid;
279};
280
390802ab
DM
281sub running {
282 my ($self) = @_;
283
bdb5acce 284 my $pid = &$read_pid($self);
390802ab
DM
285
286 if ($pid) {
287 my $res = PVE::ProcFSTools::check_process_running($pid) ? 1 : 0;
288 return wantarray ? ($res, $pid) : $res;
289 }
290
291 return wantarray ? (0, 0) : 0;
292}
293
294sub stop {
295 my ($self) = @_;
296
bdb5acce
DM
297 my $pid = &$read_pid($self);
298
390802ab
DM
299 return if !$pid;
300
301 if (PVE::ProcFSTools::check_process_running($pid)) {
302 kill(15, $pid); # send TERM signal
303 # give some time
304 my $wait_time = $self->{stop_wait_time} || 5;
305 my $running = 1;
306 for (my $i = 0; $i < $wait_time; $i++) {
307 $running = PVE::ProcFSTools::check_process_running($pid);
308 last if !$running;
309 sleep (1);
310 }
311
312 syslog('err', "server still running - send KILL") if $running;
313
314 # to be sure
315 kill(9, $pid);
316 waitpid($pid, 0);
317 }
318
319 if (-f $self->{pidfile}) {
320 # try to get the lock
321 &$lockpidfile($self);
322 &$server_cleanup($self);
323 }
324}
325
326sub register_start_command {
327 my ($self, $class, $description) = @_;
328
329 $class->register_method({
330 name => 'start',
331 path => 'start',
332 method => 'POST',
333 description => $description || "Start the daemon.",
334 parameters => {
335 additionalProperties => 0,
336 properties => {
337 debug => {
338 description => "Debug mode - stay in foreground",
339 type => "boolean",
340 optional => 1,
341 default => 0,
342 },
343 },
344 },
345 returns => { type => 'null' },
346
347 code => sub {
348 my ($param) = @_;
349
350 $self->start($param->{debug});
351
352 return undef;
353 }});
354}
355
bdb5acce
DM
356my $reload_daemon = sub {
357 my ($self, $use_hup) = @_;
358
359 if (my $restart = $ENV{RESTART_PVE_DAEMON}) {
360 $self->start();
361 } else {
362 my ($running, $pid) = $self->running();
363 if (!$running) {
364 $self->start();
365 } else {
366 if ($use_hup) {
367 kill(1, $pid);
368 } else {
369 $self->stop();
370 $self->start();
371 }
372 }
373 }
374};
375
390802ab 376sub register_restart_command {
bdb5acce 377 my ($self, $class, $use_hup, $description) = @_;
390802ab
DM
378
379 $class->register_method({
380 name => 'restart',
381 path => 'restart',
382 method => 'POST',
383 description => $description || "Restart the daemon (or start if not running).",
384 parameters => {
385 additionalProperties => 0,
386 properties => {},
387 },
388 returns => { type => 'null' },
389
390 code => sub {
391 my ($param) = @_;
392
bdb5acce
DM
393 &$reload_daemon($self, $use_hup);
394
395 return undef;
396 }});
397}
398
399sub register_reload_command {
400 my ($self, $class, $description) = @_;
401
402 $class->register_method({
403 name => 'reload',
404 path => 'reload',
405 method => 'POST',
406 description => $description || "Reload daemon configuration (or start if not running).",
407 parameters => {
408 additionalProperties => 0,
409 properties => {},
410 },
411 returns => { type => 'null' },
412
413 code => sub {
414 my ($param) = @_;
415
416 &$reload_daemon($self, 1);
390802ab
DM
417
418 return undef;
419 }});
420}
421
422sub register_stop_command {
423 my ($self, $class, $description) = @_;
424
425 $class->register_method({
426 name => 'stop',
427 path => 'stop',
428 method => 'POST',
429 description => $description || "Stop the daemon.",
430 parameters => {
431 additionalProperties => 0,
432 properties => {},
433 },
434 returns => { type => 'null' },
435
436 code => sub {
437 my ($param) = @_;
438
439 $self->stop();
440
441 return undef;
442 }});
443}
444
445sub register_status_command {
446 my ($self, $class, $description) = @_;
447
448 $class->register_method({
449 name => 'status',
450 path => 'status',
451 method => 'GET',
452 description => "Get daemon status.",
453 parameters => {
454 additionalProperties => 0,
455 properties => {},
456 },
457 returns => {
458 type => 'string',
459 enum => ['stopped', 'running'],
460 },
461 code => sub {
462 my ($param) = @_;
463
464 return $self->running() ? 'running' : 'stopped';
465 }});
466}
467
4681;
469