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