]> git.proxmox.com Git - pve-common.git/blob - data/PVE/Daemon.pm
add abstract class to implement daemons
[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 # * daemon restart (option 'restart_on_error')
10
11 use strict;
12 use warnings;
13 use PVE::SafeSyslog;
14 use PVE::INotify;
15
16 use POSIX ":sys_wait_h";
17 use Fcntl ':flock';
18 use Getopt::Long;
19 use Time::HiRes qw (gettimeofday);
20
21 use 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
34 my $daemon_initialized = 0; # we only allow one instance
35
36 my $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
55 my $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
69 my $server_cleanup = sub {
70 my ($self) = @_;
71
72 unlink $self->{pidfile} . ".lock";
73 unlink $self->{pidfile};
74 };
75
76 my $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
112 if ($restart) {
113 syslog('info' , "restarting server");
114 } else {
115 syslog('info' , "starting server");
116 }
117
118 open STDERR, '>&STDOUT' || die "can't close STDERR\n";
119
120 $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = sub {
121 $SIG{INT} = 'DEFAULT';
122
123 eval { $self->shutdown(); };
124 warn $@ if $@;
125
126 &$server_cleanup($self);
127 };
128
129 $SIG{HUP} = sub {
130 eval { $self->hup(); };
131 warn $@ if $@;
132 };
133
134 eval { $self->run() };
135 my $err = $@;
136
137 if ($err) {
138 syslog ('err', "ERROR: $err");
139 if (my $wait_time = $self->{restart_on_error}) {
140 $self->restart_daemon($wait_time);
141 } else {
142 $self->exit_daemon(-1);
143 }
144 }
145
146 $self->exit_daemon(0);
147 };
148
149 sub new {
150 my ($this, $name, $cmdline, %params) = @_;
151
152 die "please run as root\n" if $> != 0;
153
154 die "missing name" if !$name;
155
156 die "can't create more that one PVE::Daemon" if $daemon_initialized;
157 $daemon_initialized = 1;
158
159 PVE::INotify::inotify_init();
160
161 initlog($name);
162
163 my $class = ref($this) || $this;
164
165 my $self = bless { name => $name }, $class;
166
167 $self->{pidfile} = "/var/run/${name}.pid";
168
169 $self->{nodename} = PVE::INotify::nodename();
170
171 $self->{cmdline} = $cmdline;
172
173 $0 = $name;
174
175 foreach my $opt (keys %params) {
176 my $value = $params{$opt};
177 if ($opt eq 'restart_on_error') {
178 $self->{$opt} = $value;
179 } elsif ($opt eq 'stop_wait_time') {
180 $self->{$opt} = $value;
181 } else {
182 die "unknown option '$opt'";
183 }
184 }
185
186 return $self;
187 }
188
189 sub exit_daemon {
190 my ($self, $status) = @_;
191
192 syslog("info", "server stopped");
193
194 &$server_cleanup($self);
195
196 exit($status);
197 }
198
199 sub restart_daemon {
200 my ($self, $waittime) = @_;
201
202 syslog('info', "server shutdown (restart)");
203
204 $ENV{RESTART_PVE_DAEMON} = 1;
205
206 sleep($waittime) if $waittime; # avoid high server load due to restarts
207
208 PVE::INotify::inotify_close();
209
210 exec (@{$self->{cmdline}});
211
212 exit (-1); # never reached?
213 }
214
215 # please overwrite in subclass
216 # this is called at startup - before forking
217 sub init {
218 my ($self) = @_;
219
220 }
221
222 # please overwrite in subclass
223 sub shutdown {
224 my ($self) = @_;
225
226 syslog('info' , "server closing");
227
228 # wait for children
229 1 while (waitpid(-1, POSIX::WNOHANG()) > 0);
230 }
231
232 # please overwrite in subclass
233 sub hup {
234 my ($self) = @_;
235
236 syslog('info' , "received signal HUP (restart)");
237 }
238
239 # please overwrite in subclass
240 sub run {
241 my ($self) = @_;
242
243 for (;;) { # forever
244 syslog('info' , "server is running");
245 sleep(5);
246 }
247 }
248
249 sub start {
250 my ($self, $debug) = @_;
251
252 &$server_run($self, $debug);
253 }
254
255 sub running {
256 my ($self) = @_;
257
258 my $pid = int(PVE::Tools::file_read_firstline($self->{pidfile}) || 0);
259
260 if ($pid) {
261 my $res = PVE::ProcFSTools::check_process_running($pid) ? 1 : 0;
262 return wantarray ? ($res, $pid) : $res;
263 }
264
265 return wantarray ? (0, 0) : 0;
266 }
267
268 sub stop {
269 my ($self) = @_;
270
271 my $pid = int(PVE::Tools::file_read_firstline($self->{pidfile}) || 0);
272 return if !$pid;
273
274 if (PVE::ProcFSTools::check_process_running($pid)) {
275 kill(15, $pid); # send TERM signal
276 # give some time
277 my $wait_time = $self->{stop_wait_time} || 5;
278 my $running = 1;
279 for (my $i = 0; $i < $wait_time; $i++) {
280 $running = PVE::ProcFSTools::check_process_running($pid);
281 last if !$running;
282 sleep (1);
283 }
284
285 syslog('err', "server still running - send KILL") if $running;
286
287 # to be sure
288 kill(9, $pid);
289 waitpid($pid, 0);
290 }
291
292 if (-f $self->{pidfile}) {
293 # try to get the lock
294 &$lockpidfile($self);
295 &$server_cleanup($self);
296 }
297 }
298
299 sub register_start_command {
300 my ($self, $class, $description) = @_;
301
302 $class->register_method({
303 name => 'start',
304 path => 'start',
305 method => 'POST',
306 description => $description || "Start the daemon.",
307 parameters => {
308 additionalProperties => 0,
309 properties => {
310 debug => {
311 description => "Debug mode - stay in foreground",
312 type => "boolean",
313 optional => 1,
314 default => 0,
315 },
316 },
317 },
318 returns => { type => 'null' },
319
320 code => sub {
321 my ($param) = @_;
322
323 $self->start($param->{debug});
324
325 return undef;
326 }});
327 }
328
329 sub register_restart_command {
330 my ($self, $class, $description) = @_;
331
332 $class->register_method({
333 name => 'restart',
334 path => 'restart',
335 method => 'POST',
336 description => $description || "Restart the daemon (or start if not running).",
337 parameters => {
338 additionalProperties => 0,
339 properties => {},
340 },
341 returns => { type => 'null' },
342
343 code => sub {
344 my ($param) = @_;
345
346 if (my $restart = $ENV{RESTART_PVE_DAEMON}) {
347 $self->start();
348 } else {
349 my ($running, $pid) = $self->running();
350 if (!$running) {
351 $self->start();
352 } else {
353 kill(1, $pid);
354 }
355 }
356
357 return undef;
358 }});
359 }
360
361 sub register_stop_command {
362 my ($self, $class, $description) = @_;
363
364 $class->register_method({
365 name => 'stop',
366 path => 'stop',
367 method => 'POST',
368 description => $description || "Stop the daemon.",
369 parameters => {
370 additionalProperties => 0,
371 properties => {},
372 },
373 returns => { type => 'null' },
374
375 code => sub {
376 my ($param) = @_;
377
378 $self->stop();
379
380 return undef;
381 }});
382 }
383
384 sub register_status_command {
385 my ($self, $class, $description) = @_;
386
387 $class->register_method({
388 name => 'status',
389 path => 'status',
390 method => 'GET',
391 description => "Get daemon status.",
392 parameters => {
393 additionalProperties => 0,
394 properties => {},
395 },
396 returns => {
397 type => 'string',
398 enum => ['stopped', 'running'],
399 },
400 code => sub {
401 my ($param) = @_;
402
403 return $self->running() ? 'running' : 'stopped';
404 }});
405 }
406
407 1;
408