improve doc
[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 #
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 $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
57 my $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
71 my $server_cleanup = sub {
72     my ($self) = @_;
73
74     unlink $self->{pidfile} . ".lock";
75     unlink $self->{pidfile};
76 };
77
78 my $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
114     POSIX::setsid(); 
115
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);
131     };
132
133     if ($self->can('hup')) {
134         $SIG{HUP} = sub {
135             eval { $self->hup() };
136             warn $@ if $@;
137         };
138     }
139
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
155 sub 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
171     my $self = bless { 
172         name => $name,
173         run_dir => '/var/run',
174     }, $class;
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;
182         } elsif ($opt eq 'run_dir') {
183             $self->{$opt} = $value;
184         } else {
185             die "unknown option '$opt'";
186         }
187     }
188
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
198     return $self;
199 }
200
201 sub exit_daemon {
202     my ($self, $status) = @_;
203
204     syslog("info", "server stopped");
205
206     &$server_cleanup($self);
207
208     exit($status);
209 }
210
211 sub 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
229 sub init {
230     my ($self) = @_;
231
232 }
233
234 # please overwrite in subclass
235 sub 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
244 # please define in subclass
245 #sub hup {
246 #    my ($self) = @_;
247 #
248 #    syslog('info' , "received signal HUP (restart)");
249 #}
250
251 # please overwrite in subclass
252 sub run {
253     my ($self) = @_;
254
255     for (;;) { # forever
256         syslog('info' , "server is running");
257         sleep(5);
258     }
259 }
260
261 sub start {
262     my ($self, $debug) = @_;
263
264     &$server_run($self, $debug);
265 }
266
267 my $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
281 sub running {
282     my ($self) = @_;
283
284     my $pid = &$read_pid($self);
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
294 sub stop {
295     my ($self) = @_;
296
297     my $pid = &$read_pid($self);
298
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
326 sub 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
356 my $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
376 sub register_restart_command {
377     my ($self, $class, $use_hup, $description) = @_;
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
393             &$reload_daemon($self, $use_hup);
394
395             return undef;
396         }});               
397 }
398
399 sub 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);
417
418             return undef;
419         }});               
420 }
421
422 sub 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
445 sub 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
468 1;
469