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