Revision 23d89499
Von Moritz Bunkus vor fast 14 Jahren hinzugefügt
modules/fallback/Daemon/Generic.pm | ||
---|---|---|
1 |
|
|
2 |
# Copyright (C) 2006, David Muir Sharnoff <perl@dave.sharnoff.org> |
|
3 |
|
|
4 |
package Daemon::Generic; |
|
5 |
|
|
6 |
use strict; |
|
7 |
use warnings; |
|
8 |
require Exporter; |
|
9 |
require POSIX; |
|
10 |
use Getopt::Long; |
|
11 |
use File::Slurp; |
|
12 |
use File::Flock; |
|
13 |
our @ISA = qw(Exporter); |
|
14 |
our @EXPORT = qw(newdaemon); |
|
15 |
|
|
16 |
our $VERSION = 0.71; |
|
17 |
|
|
18 |
our $force_quit_delay = 15; |
|
19 |
our $package = __PACKAGE__; |
|
20 |
our $caller; |
|
21 |
|
|
22 |
sub newdaemon |
|
23 |
{ |
|
24 |
my (%args) = @_; |
|
25 |
my $pkg = $caller || caller() || 'main'; |
|
26 |
|
|
27 |
my $foo = bless {}, $pkg; |
|
28 |
|
|
29 |
unless ($foo->isa($package)) { |
|
30 |
no strict qw(refs); |
|
31 |
my $isa = \@{"${pkg}::ISA"}; |
|
32 |
unshift(@$isa, $package); |
|
33 |
} |
|
34 |
|
|
35 |
bless $foo, 'This::Package::Does::Not::Exist'; |
|
36 |
undef $foo; |
|
37 |
|
|
38 |
new($pkg, %args); |
|
39 |
} |
|
40 |
|
|
41 |
sub new |
|
42 |
{ |
|
43 |
my ($pkg, %args) = @_; |
|
44 |
|
|
45 |
if ($pkg eq __PACKAGE__) { |
|
46 |
$pkg = caller() || 'main'; |
|
47 |
} |
|
48 |
|
|
49 |
srand(time ^ ($$ << 5)) |
|
50 |
unless $args{no_srand}; |
|
51 |
|
|
52 |
my $av0 = $0; |
|
53 |
$av0 =~ s!/!/.!g; |
|
54 |
|
|
55 |
my $self = { |
|
56 |
gd_args => \%args, |
|
57 |
gd_pidfile => $args{pidfile}, |
|
58 |
gd_logpriority => $args{logpriority}, |
|
59 |
gd_progname => $args{progname} |
|
60 |
? $args{progname} |
|
61 |
: $0, |
|
62 |
gd_pidbase => $args{pidbase} |
|
63 |
? $args{pidbase} |
|
64 |
: ($args{progname} |
|
65 |
? "/var/run/$args{progname}" |
|
66 |
: "/var/run/$av0"), |
|
67 |
gd_foreground => $args{foreground} || 0, |
|
68 |
configfile => $args{configfile} |
|
69 |
? $args{configfile} |
|
70 |
: ($args{progname} |
|
71 |
? "/etc/$args{progname}.conf" |
|
72 |
: "/etc/$av0"), |
|
73 |
debug => $args{debug} || 0, |
|
74 |
}; |
|
75 |
bless $self, $pkg; |
|
76 |
|
|
77 |
$self->gd_getopt; |
|
78 |
$self->gd_parse_argv; |
|
79 |
|
|
80 |
my $do = $self->{do} = $ARGV[0]; |
|
81 |
|
|
82 |
$self->gd_help if $do eq 'help'; |
|
83 |
$self->gd_version if $do eq 'version'; |
|
84 |
$self->gd_install if $do eq 'install'; |
|
85 |
$self->gd_uninstall if $do eq 'uninstall'; |
|
86 |
|
|
87 |
$self->gd_pidfile unless $self->{gd_pidfile}; |
|
88 |
|
|
89 |
my %newconfig = $self->gd_preconfig; |
|
90 |
|
|
91 |
$self->{gd_pidfile} = $newconfig{pidfile} if $newconfig{pidfile}; |
|
92 |
|
|
93 |
print "Configuration looks okay\n" if $do eq 'check'; |
|
94 |
|
|
95 |
my $pidfile = $self->{gd_pidfile}; |
|
96 |
my $killed = 0; |
|
97 |
my $locked = 0; |
|
98 |
if (-e $pidfile) { |
|
99 |
if ($locked = lock($pidfile, undef, 'nonblocking')) { |
|
100 |
# old process is dead |
|
101 |
if ($do eq 'status') { |
|
102 |
print "$0 dead\n"; |
|
103 |
exit 1; |
|
104 |
} |
|
105 |
} else { |
|
106 |
sleep(2) if -M $pidfile < 2/86400; |
|
107 |
my $oldpid = read_file($pidfile); |
|
108 |
chomp($oldpid); |
|
109 |
if ($oldpid) { |
|
110 |
if ($do eq 'stop' or $do eq 'restart') { |
|
111 |
$killed = $self->gd_kill($oldpid); |
|
112 |
$locked = lock($pidfile); |
|
113 |
if ($do eq 'stop') { |
|
114 |
unlink($pidfile); |
|
115 |
exit; |
|
116 |
} |
|
117 |
} elsif ($do eq 'reload') { |
|
118 |
if (kill(1,$oldpid)) { |
|
119 |
print "Requested reconfiguration\n"; |
|
120 |
exit; |
|
121 |
} else { |
|
122 |
print "Kill failed: $!\n"; |
|
123 |
} |
|
124 |
} elsif ($do eq 'status') { |
|
125 |
if (kill(0,$oldpid)) { |
|
126 |
print "$0 running - pid $oldpid\n"; |
|
127 |
$self->gd_check($pidfile, $oldpid); |
|
128 |
exit 0; |
|
129 |
} else { |
|
130 |
print "$0 dead\n"; |
|
131 |
exit 1; |
|
132 |
} |
|
133 |
} elsif ($do eq 'check') { |
|
134 |
if (kill(0,$oldpid)) { |
|
135 |
print "$0 running - pid $oldpid\n"; |
|
136 |
$self->gd_check($pidfile, $oldpid); |
|
137 |
exit; |
|
138 |
} |
|
139 |
} elsif ($do eq 'start') { |
|
140 |
print "\u$self->{gd_progname} is already running (pid $oldpid)\n"; |
|
141 |
exit; # according to LSB, this is no error |
|
142 |
} |
|
143 |
} else { |
|
144 |
$self->gd_error("Pid file $pidfile is invalid but locked, exiting\n"); |
|
145 |
} |
|
146 |
} |
|
147 |
} else { |
|
148 |
$locked = lock($pidfile, undef, 'nonblocking') |
|
149 |
or die "Could not lock pid file $pidfile: $!"; |
|
150 |
} |
|
151 |
|
|
152 |
if ($do eq 'reload' || $do eq 'stop' || $do eq 'check' || ($do eq 'restart' && ! $killed)) { |
|
153 |
print "No $0 running\n"; |
|
154 |
} |
|
155 |
|
|
156 |
if ($do eq 'stop') { |
|
157 |
unlink($pidfile); |
|
158 |
exit; |
|
159 |
} |
|
160 |
|
|
161 |
if ($do eq 'status') { |
|
162 |
print "Unused\n"; |
|
163 |
exit 3; |
|
164 |
} |
|
165 |
|
|
166 |
if ($do eq 'check') { |
|
167 |
$self->gd_check($pidfile); |
|
168 |
exit |
|
169 |
} |
|
170 |
|
|
171 |
unless ($do eq 'reload' || $do eq 'restart' || $do eq 'start') { |
|
172 |
$self->gd_other_cmd($do, $locked); |
|
173 |
} |
|
174 |
|
|
175 |
unless ($self->{gd_foreground}) { |
|
176 |
$self->gd_daemonize; |
|
177 |
} |
|
178 |
|
|
179 |
$locked or lock($pidfile, undef, 'nonblocking') |
|
180 |
or die "Could not lock PID file $pidfile: $!"; |
|
181 |
|
|
182 |
write_file($pidfile, "$$\n"); |
|
183 |
|
|
184 |
print STDERR "Starting up...\n"; |
|
185 |
|
|
186 |
$self->gd_postconfig(%newconfig); |
|
187 |
|
|
188 |
$self->gd_setup_signals; |
|
189 |
|
|
190 |
$self->gd_run; |
|
191 |
|
|
192 |
unlink($pidfile); |
|
193 |
exit(0); |
|
194 |
} |
|
195 |
|
|
196 |
sub gd_check {} |
|
197 |
|
|
198 |
sub gd_more_opt { return() } |
|
199 |
|
|
200 |
sub gd_getopt |
|
201 |
{ |
|
202 |
my $self = shift; |
|
203 |
Getopt::Long::Configure("auto_version"); |
|
204 |
GetOptions( |
|
205 |
'configfile=s' => \$self->{configfile}, |
|
206 |
'foreground!' => \$self->{gd_foreground}, |
|
207 |
'debug!' => \$self->{debug}, |
|
208 |
$self->{gd_args}{options} |
|
209 |
? %{$self->{gd_args}{options}} |
|
210 |
: (), |
|
211 |
$self->gd_more_opt(), |
|
212 |
) or exit($self->gd_usage()); |
|
213 |
|
|
214 |
if (@ARGV < ($self->{gd_args}{minimum_args} || 1)) { |
|
215 |
exit($self->gd_usage()); |
|
216 |
} |
|
217 |
if (@ARGV > ($self->{gd_args}{maximum_args} || 1)) { |
|
218 |
exit($self->gd_usage()); |
|
219 |
} |
|
220 |
} |
|
221 |
|
|
222 |
sub gd_parse_argv { } |
|
223 |
|
|
224 |
sub gd_help |
|
225 |
{ |
|
226 |
my $self = shift; |
|
227 |
exit($self->gd_usage($self->{gd_args})); |
|
228 |
} |
|
229 |
|
|
230 |
sub gd_version |
|
231 |
{ |
|
232 |
my $self = shift; |
|
233 |
no strict qw(refs); |
|
234 |
my $v = $self->{gd_args}{version} |
|
235 |
|| ${ref($self)."::VERSION"} |
|
236 |
|| $::VERSION |
|
237 |
|| $main::VERSION |
|
238 |
|| "?"; |
|
239 |
print "$self->{gd_progname} - version $v\n";; |
|
240 |
exit; |
|
241 |
} |
|
242 |
|
|
243 |
sub gd_pidfile |
|
244 |
{ |
|
245 |
my $self = shift; |
|
246 |
my $x = $self->{configfile}; |
|
247 |
$x =~ s!/!.!g; |
|
248 |
$self->{gd_pidfile} = "$self->{gd_pidbase}$x.pid"; |
|
249 |
} |
|
250 |
|
|
251 |
sub gd_other_cmd |
|
252 |
{ |
|
253 |
my $self = shift; |
|
254 |
$self->gd_usage; |
|
255 |
exit(1); |
|
256 |
} |
|
257 |
|
|
258 |
sub gd_redirect_output |
|
259 |
{ |
|
260 |
my $self = shift; |
|
261 |
return if $self->{gd_foreground}; |
|
262 |
my $logname = $self->gd_logname; |
|
263 |
my $p = $self->{gd_logpriority} ? "-p $self->{gd_logpriority}" : ""; |
|
264 |
open(STDERR, "|logger $p -t '$logname'") or (print "could not open stderr: $!" && exit(1)); |
|
265 |
close(STDOUT); |
|
266 |
open(STDOUT, ">&STDERR") or die "redirect STDOUT -> STDERR: $!"; |
|
267 |
close(STDIN); |
|
268 |
} |
|
269 |
|
|
270 |
sub gd_daemonize |
|
271 |
{ |
|
272 |
my $self = shift; |
|
273 |
print "Starting $self->{gd_progname} server\n"; |
|
274 |
$self->gd_redirect_output(); |
|
275 |
my $pid; |
|
276 |
POSIX::_exit(0) if $pid = fork; |
|
277 |
die "Could not fork: $!" unless defined $pid; |
|
278 |
POSIX::_exit(0) if $pid = fork; |
|
279 |
die "Could not fork: $!" unless defined $pid; |
|
280 |
|
|
281 |
POSIX::setsid(); |
|
282 |
select(STDERR); |
|
283 |
$| = 1; |
|
284 |
print "Sucessfully daemonized\n"; |
|
285 |
} |
|
286 |
|
|
287 |
sub gd_logname |
|
288 |
{ |
|
289 |
my $self = shift; |
|
290 |
return $self->{gd_progname}."[$$]"; |
|
291 |
} |
|
292 |
|
|
293 |
sub gd_reconfig_event |
|
294 |
{ |
|
295 |
my $self = shift; |
|
296 |
print STDERR "Reconfiguration requested\n"; |
|
297 |
$self->gd_postconfig($self->gd_preconfig()); |
|
298 |
} |
|
299 |
|
|
300 |
sub gd_quit_event |
|
301 |
{ |
|
302 |
my $self = shift; |
|
303 |
print STDERR "Quitting...\n"; |
|
304 |
exit(0); |
|
305 |
} |
|
306 |
|
|
307 |
sub gd_setup_signals |
|
308 |
{ |
|
309 |
my $self = shift; |
|
310 |
$SIG{INT} = sub { $self->gd_quit_event() }; |
|
311 |
$SIG{HUP} = sub { $self->gd_reconfig_event() }; |
|
312 |
} |
|
313 |
|
|
314 |
sub gd_run { die "must defined gd_run()" } |
|
315 |
|
|
316 |
sub gd_error |
|
317 |
{ |
|
318 |
my $self = shift; |
|
319 |
my $e = shift; |
|
320 |
my $do = $self->{do}; |
|
321 |
if ($do && $do eq 'stop') { |
|
322 |
warn $e; |
|
323 |
} else { |
|
324 |
die $e; |
|
325 |
} |
|
326 |
} |
|
327 |
|
|
328 |
sub gd_flags_more { return () } |
|
329 |
|
|
330 |
sub gd_flags |
|
331 |
{ |
|
332 |
my $self = shift; |
|
333 |
return ( |
|
334 |
'-c file' => "Specify configuration file (instead of $self->{configfile})", |
|
335 |
'-f' => "Run in the foreground (don't detach)", |
|
336 |
$self->gd_flags_more |
|
337 |
); |
|
338 |
} |
|
339 |
|
|
340 |
sub gd_commands_more { return () } |
|
341 |
|
|
342 |
sub gd_commands |
|
343 |
{ |
|
344 |
my $self = shift; |
|
345 |
return ( |
|
346 |
start => "Starts a new $self->{gd_progname} if there isn't one running already", |
|
347 |
stop => "Stops a running $self->{gd_progname}", |
|
348 |
reload => "Causes a running $self->{gd_progname} to reload it's config file. Starts a new one if none is running.", |
|
349 |
restart => "Stops a running $self->{gd_progname} if one is running. Starts a new one.", |
|
350 |
$self->gd_commands_more(), |
|
351 |
($self->gd_can_install() |
|
352 |
? ('install' => "Setup $self->{gd_progname} to run automatically after reboot") |
|
353 |
: ()), |
|
354 |
($self->gd_can_uninstall() |
|
355 |
? ('uninstall' => "Do not run $self->{gd_progname} after reboots") |
|
356 |
: ()), |
|
357 |
check => "Check the configuration file and report the daemon state", |
|
358 |
help => "Display this usage info", |
|
359 |
version => "Display the version of $self->{gd_progname}", |
|
360 |
) |
|
361 |
} |
|
362 |
|
|
363 |
sub gd_positional_more { return() } |
|
364 |
|
|
365 |
sub gd_alts |
|
366 |
{ |
|
367 |
my $offset = shift; |
|
368 |
my @results; |
|
369 |
for (my $i = $offset; $i <= $#_; $i += 2) { |
|
370 |
push(@results, $_[$i]); |
|
371 |
} |
|
372 |
return @results; |
|
373 |
} |
|
374 |
|
|
375 |
sub gd_usage |
|
376 |
{ |
|
377 |
my $self = shift; |
|
378 |
|
|
379 |
require Text::Wrap; |
|
380 |
import Text::Wrap; |
|
381 |
|
|
382 |
my $col = 15; |
|
383 |
|
|
384 |
my @flags = $self->gd_flags; |
|
385 |
my @commands = $self->gd_commands; |
|
386 |
my @positional = $self->gd_positional_more; |
|
387 |
|
|
388 |
my $summary = "Usage: $self->{gd_progname} "; |
|
389 |
my $details = ''; |
|
390 |
for my $i (gd_alts(0, @flags)) { |
|
391 |
$summary .= "[ $i ] "; |
|
392 |
} |
|
393 |
$summary .= "{ "; |
|
394 |
$summary .= join(" | ", gd_alts(0, @commands)); |
|
395 |
$summary .= " } "; |
|
396 |
$summary .= join(" ", gd_alts(0, @positional)); |
|
397 |
|
|
398 |
my (@all) = (@flags, @commands, @positional); |
|
399 |
while (@all) { |
|
400 |
my ($key, $desc) = splice(@all, 0, 2); |
|
401 |
local($Text::Wrap::columns) = 79; |
|
402 |
$details .= wrap( |
|
403 |
sprintf(" %-${col}s ", $key), |
|
404 |
" " x ($col + 2), |
|
405 |
$desc); |
|
406 |
$details .= "\n"; |
|
407 |
} |
|
408 |
|
|
409 |
print "$summary\n$details"; |
|
410 |
return 0; |
|
411 |
} |
|
412 |
|
|
413 |
sub gd_install_pre {} |
|
414 |
sub gd_install_post {} |
|
415 |
|
|
416 |
sub gd_can_install |
|
417 |
{ |
|
418 |
my $self = shift; |
|
419 |
require File::Basename; |
|
420 |
my $basename = File::Basename::basename($0); |
|
421 |
if ( |
|
422 |
-x "/usr/sbin/update-rc.d" |
|
423 |
&& |
|
424 |
-x $0 |
|
425 |
&& |
|
426 |
$0 !~ m{^(?:/usr|/var)?/tmp/} |
|
427 |
&& |
|
428 |
eval { symlink("",""); 1 } |
|
429 |
&& |
|
430 |
-d "/etc/init.d" |
|
431 |
&& |
|
432 |
! -e "/etc/init.d/$basename" |
|
433 |
) { |
|
434 |
return sub { |
|
435 |
$self->gd_install_pre("update-rc.d"); |
|
436 |
require Cwd; |
|
437 |
my $abs_path = Cwd::abs_path($0); |
|
438 |
symlink($abs_path, "/etc/init.d/$basename") |
|
439 |
or die "Install failed: symlink /etc/init.d/$basename -> $abs_path: $!\n"; |
|
440 |
print "+ /usr/sbin/update-rc.d $basename defaults\n"; |
|
441 |
system("/usr/sbin/update-rc.d", $basename, "defaults"); |
|
442 |
my $exit = $? >> 8; |
|
443 |
$self->gd_install_post("update-rc.d"); |
|
444 |
exit($exit) if $exit; |
|
445 |
}; |
|
446 |
} |
|
447 |
|
|
448 |
return 0; |
|
449 |
} |
|
450 |
|
|
451 |
sub gd_install |
|
452 |
{ |
|
453 |
my $self = shift; |
|
454 |
my $ifunc = $self->gd_can_install(); |
|
455 |
die "Install command not supported\n" unless $ifunc; |
|
456 |
&$ifunc($self); |
|
457 |
exit(0); |
|
458 |
} |
|
459 |
|
|
460 |
sub gd_uninstall_pre {} |
|
461 |
sub gd_uninstall_post {} |
|
462 |
|
|
463 |
sub gd_can_uninstall |
|
464 |
{ |
|
465 |
my $self = shift; |
|
466 |
require File::Basename; |
|
467 |
my $basename = File::Basename::basename($0); |
|
468 |
require Cwd; |
|
469 |
my $abs_path = Cwd::abs_path($0) || 'no abs path'; |
|
470 |
my $link = readlink("/etc/init.d/$basename") || 'no link'; |
|
471 |
if ( |
|
472 |
$link eq $abs_path |
|
473 |
&& |
|
474 |
-x "/usr/sbin/update-rc.d" |
|
475 |
) { |
|
476 |
return sub { |
|
477 |
$self->gd_uninstall_pre("update-rc.d"); |
|
478 |
unlink("/etc/init.d/$basename"); |
|
479 |
print "+ /usr/sbin/update-rc.d $basename remove\n"; |
|
480 |
system("/usr/sbin/update-rc.d", $basename, "remove"); |
|
481 |
my $exit = $? >> 8; |
|
482 |
$self->gd_uninstall_post("update-rc.d"); |
|
483 |
exit($exit) if $exit; |
|
484 |
} |
|
485 |
} |
|
486 |
return 0; |
|
487 |
} |
|
488 |
|
|
489 |
sub gd_uninstall |
|
490 |
{ |
|
491 |
my $self = shift; |
|
492 |
my $ufunc = $self->gd_can_uninstall(); |
|
493 |
die "Cannot uninstall\n" unless $ufunc; |
|
494 |
&$ufunc($self); |
|
495 |
exit(0); |
|
496 |
} |
|
497 |
|
|
498 |
sub gd_kill |
|
499 |
{ |
|
500 |
my ($self, $pid) = @_; |
|
501 |
|
|
502 |
my $talkmore = 0; |
|
503 |
my $killed = 0; |
|
504 |
if (kill(0, $pid)) { |
|
505 |
$killed = 1; |
|
506 |
kill(2,$pid); |
|
507 |
print "Killing $pid\n"; |
|
508 |
my $t = time; |
|
509 |
sleep(1) if kill(0, $pid); |
|
510 |
if ($force_quit_delay && kill(0, $pid)) { |
|
511 |
print "Waiting for $pid to die...\n"; |
|
512 |
$talkmore = 1; |
|
513 |
while(kill(0, $pid) && time - $t < $force_quit_delay) { |
|
514 |
sleep(1); |
|
515 |
} |
|
516 |
} |
|
517 |
if (kill(15, $pid)) { |
|
518 |
print "Killing $pid with -TERM...\n"; |
|
519 |
if ($force_quit_delay) { |
|
520 |
while(kill(0, $pid) && time - $t < $force_quit_delay * 2) { |
|
521 |
sleep(1); |
|
522 |
} |
|
523 |
} else { |
|
524 |
sleep(1) if kill(0, $pid); |
|
525 |
} |
|
526 |
} |
|
527 |
if (kill(9, $pid)) { |
|
528 |
print "Killing $pid with -KILL...\n"; |
|
529 |
my $k9 = time; |
|
530 |
my $max = $force_quit_delay * 4; |
|
531 |
$max = 60 if $max < 60; |
|
532 |
while(kill(0, $pid)) { |
|
533 |
if (time - $k9 > $max) { |
|
534 |
print "Giving up on $pid ever dying.\n"; |
|
535 |
exit(1); |
|
536 |
} |
|
537 |
print "Waiting for $pid to die...\n"; |
|
538 |
sleep(1); |
|
539 |
} |
|
540 |
} |
|
541 |
print "Process $pid is gone\n" if $talkmore; |
|
542 |
} else { |
|
543 |
print "Process $pid no longer running\n"; |
|
544 |
} |
|
545 |
return $killed; |
|
546 |
} |
|
547 |
|
|
548 |
sub gd_preconfig { die "gd_preconfig() must be redefined"; } |
|
549 |
|
|
550 |
sub gd_postconfig { } |
|
551 |
|
|
552 |
|
|
553 |
1; |
modules/fallback/Daemon/Generic/Event.pm | ||
---|---|---|
1 |
|
|
2 |
# Copyright (C) 2006, David Muir Sharnoff <muir@idiom.com> |
|
3 |
|
|
4 |
package Daemon::Generic::Event; |
|
5 |
|
|
6 |
use strict; |
|
7 |
use warnings; |
|
8 |
require Daemon::Generic; |
|
9 |
require Event; |
|
10 |
require Exporter; |
|
11 |
|
|
12 |
our @ISA = qw(Daemon::Generic Exporter); |
|
13 |
our @EXPORT = @Daemon::Generic::EXPORT; |
|
14 |
our $VERSION = 0.3; |
|
15 |
|
|
16 |
sub newdaemon |
|
17 |
{ |
|
18 |
local($Daemon::Generic::caller) = caller() || 'main'; |
|
19 |
local($Daemon::Generic::package) = __PACKAGE__; |
|
20 |
Daemon::Generic::newdaemon(@_); |
|
21 |
} |
|
22 |
|
|
23 |
sub gd_setup_signals |
|
24 |
{ |
|
25 |
my $self = shift; |
|
26 |
my $reload_event = Event->signal( |
|
27 |
signal => 'HUP', |
|
28 |
desc => 'reload on SIGHUP', |
|
29 |
prio => 6, |
|
30 |
cb => sub { |
|
31 |
$self->gd_reconfig_event; |
|
32 |
$self->{gd_timer}->cancel() |
|
33 |
if $self->{gd_timer}; |
|
34 |
$self->gd_setup_timer(); |
|
35 |
}, |
|
36 |
); |
|
37 |
my $quit_event = Event->signal( |
|
38 |
signal => 'INT', |
|
39 |
cb => sub { $self->gd_quit_event; }, |
|
40 |
); |
|
41 |
} |
|
42 |
|
|
43 |
sub gd_setup_timer |
|
44 |
{ |
|
45 |
my $self = shift; |
|
46 |
if ($self->can('gd_run_body')) { |
|
47 |
my $interval = ($self->can('gd_interval') && $self->gd_interval()) || 1; |
|
48 |
$self->{gd_timer} = Event->timer( |
|
49 |
cb => [ $self, 'gd_run_body' ], |
|
50 |
interval => $interval, |
|
51 |
hard => 0, |
|
52 |
); |
|
53 |
} |
|
54 |
} |
|
55 |
|
|
56 |
sub gd_run |
|
57 |
{ |
|
58 |
my $self = shift; |
|
59 |
$self->gd_setup_timer(); |
|
60 |
Event::loop(); |
|
61 |
} |
|
62 |
|
|
63 |
sub gd_quit_event |
|
64 |
{ |
|
65 |
my $self = shift; |
|
66 |
print STDERR "Quitting...\n"; |
|
67 |
Event::unloop_all(); |
|
68 |
} |
|
69 |
|
|
70 |
1; |
|
71 |
|
|
72 |
=head1 NAME |
|
73 |
|
|
74 |
Daemon::Generic::Event - Generic daemon framework with Event.pm |
|
75 |
|
|
76 |
=head1 SYNOPSIS |
|
77 |
|
|
78 |
use Daemon::Generic::Event; |
|
79 |
|
|
80 |
@ISA = qw(Daemon::Generic::Event); |
|
81 |
|
|
82 |
sub gd_preconfig { |
|
83 |
# stuff |
|
84 |
} |
|
85 |
|
|
86 |
=head1 DESCRIPTION |
|
87 |
|
|
88 |
Daemon::Generic::Event is a subclass of L<Daemon::Generic> that |
|
89 |
predefines some methods: |
|
90 |
|
|
91 |
=over 15 |
|
92 |
|
|
93 |
=item gd_run() |
|
94 |
|
|
95 |
Setup a periodic callback to C<gd_run_body()> if there is a C<gd_run_body()>. |
|
96 |
Call C<Event::loop()>. |
|
97 |
|
|
98 |
=item gd_setup_signals() |
|
99 |
|
|
100 |
Bind SIGHUP to call C<gd_reconfig_event()>. |
|
101 |
Bind SIGINT to call C<gd_quit_event()>. |
|
102 |
|
|
103 |
=back |
|
104 |
|
|
105 |
To use Daemon::Generic::Event, you have to provide a C<gd_preconfig()> |
|
106 |
method. It can be empty if you have a C<gd_run_body()>. |
|
107 |
|
|
108 |
Set up your own events in C<gd_preconfig()> and C<gd_postconfig()>. |
|
109 |
|
|
110 |
If you have a C<gd_run_body()> method, it will be called once per |
|
111 |
second or every C<gd_interval()> seconds if you have a C<gd_interval()> |
|
112 |
method. Unlike in L<Daemon::Generic::While1>, C<gd_run_body()> should |
|
113 |
not include a call to C<sleep()>. |
|
114 |
|
|
115 |
=head1 THANK THE AUTHOR |
|
116 |
|
|
117 |
If you need high-speed internet services (T1, T3, OC3 etc), please |
|
118 |
send me your request-for-quote. I have access to very good pricing: |
|
119 |
you'll save money and get a great service. |
|
120 |
|
|
121 |
=head1 LICENSE |
|
122 |
|
|
123 |
Copyright(C) 2006 David Muir Sharnoff <muir@idiom.com>. |
|
124 |
This module may be used and distributed on the same terms |
|
125 |
as Perl itself. |
|
126 |
|
modules/fallback/Daemon/Generic/While1.pm | ||
---|---|---|
1 |
# Copyright (C) 2006, David Muir Sharnoff <muir@idiom.com> |
|
2 |
|
|
3 |
package Daemon::Generic::While1; |
|
4 |
|
|
5 |
use strict; |
|
6 |
use warnings; |
|
7 |
use Carp; |
|
8 |
require Daemon::Generic; |
|
9 |
require POSIX; |
|
10 |
require Exporter; |
|
11 |
|
|
12 |
our @ISA = qw(Daemon::Generic Exporter); |
|
13 |
our @EXPORT = @Daemon::Generic::EXPORT; |
|
14 |
our $VERSION = 0.3; |
|
15 |
|
|
16 |
sub newdaemon |
|
17 |
{ |
|
18 |
local($Daemon::Generic::caller) = caller() || 'main'; |
|
19 |
local($Daemon::Generic::package) = __PACKAGE__; |
|
20 |
Daemon::Generic::newdaemon(@_); |
|
21 |
} |
|
22 |
|
|
23 |
sub gd_setup_signals |
|
24 |
{ |
|
25 |
my ($self) = @_; |
|
26 |
$SIG{HUP} = sub { |
|
27 |
$self->{gd_sighup} = time; |
|
28 |
}; |
|
29 |
my $child; |
|
30 |
$SIG{INT} = sub { |
|
31 |
$self->{gd_sigint} = time; |
|
32 |
# |
|
33 |
# We'll be getting a SIGTERM in a bit if we're not dead, so let's use it. |
|
34 |
# |
|
35 |
$SIG{TERM} = sub { |
|
36 |
$self->gd_quit_event(); |
|
37 |
kill(15, $child) if $child; # if we're still alive, let's stay that way |
|
38 |
}; |
|
39 |
}; |
|
40 |
} |
|
41 |
|
|
42 |
sub gd_sleep |
|
43 |
{ |
|
44 |
my ($self, $period) = @_; |
|
45 |
croak "Sleep period must be defined" unless defined $period; |
|
46 |
my $hires; |
|
47 |
if ($period*1000 != int($period*1000)) { |
|
48 |
$hires = 1; |
|
49 |
require Time::HiRes; |
|
50 |
import Time::HiRes qw(time sleep); |
|
51 |
} |
|
52 |
my $t = time; |
|
53 |
while (time - $t < $period) { |
|
54 |
return if $self->{gd_sigint}; |
|
55 |
return if $self->{gd_sighup}; |
|
56 |
if ($hires) { |
|
57 |
my $p = (time - $t < 1) |
|
58 |
? time - $t |
|
59 |
: 1; |
|
60 |
sleep($p); |
|
61 |
} else { |
|
62 |
sleep(1); |
|
63 |
} |
|
64 |
} |
|
65 |
} |
|
66 |
|
|
67 |
sub gd_run |
|
68 |
{ |
|
69 |
my ($self) = @_; |
|
70 |
while(1) { |
|
71 |
if ($self->{gd_sigint}) { |
|
72 |
$self->{gd_sigint} = 0; |
|
73 |
$self->gd_quit_event(); |
|
74 |
} |
|
75 |
|
|
76 |
if ($self->{gd_sighup}) { |
|
77 |
$self->{gd_sighup} = 0; |
|
78 |
$self->gd_reconfig_event(); |
|
79 |
} |
|
80 |
|
|
81 |
$self->gd_run_body(); |
|
82 |
} |
|
83 |
} |
|
84 |
|
|
85 |
sub gd_reconfig_event |
|
86 |
{ |
|
87 |
my $self = shift; |
|
88 |
print STDERR "Reconfiguration requested\n"; |
|
89 |
$self->gd_postconfig($self->gd_preconfig()); |
|
90 |
} |
|
91 |
|
|
92 |
sub gd_quit_event |
|
93 |
{ |
|
94 |
print STDERR "Quitting...\n"; |
|
95 |
exit(0); |
|
96 |
} |
|
97 |
|
|
98 |
|
|
99 |
sub gd_run_body { die "must override gd_run_body()" } |
|
100 |
|
|
101 |
1; |
|
102 |
|
|
103 |
=head1 NAME |
|
104 |
|
|
105 |
Daemon::Generic::While1 - Daemon framework with default while(1) loop |
|
106 |
|
|
107 |
=head1 SYNOPSIS |
|
108 |
|
|
109 |
@ISA = qw(Daemon::Generic::While1); |
|
110 |
|
|
111 |
sub gd_run_body { |
|
112 |
# stuff |
|
113 |
} |
|
114 |
|
|
115 |
=head1 DESCRIPTION |
|
116 |
|
|
117 |
This is a slight variation on L<Daemon::Generic>: a default |
|
118 |
C<gd_run()> provided. It has a while(1) loop that calls |
|
119 |
C<gd_run_body()> over and over. It checks for reconifg and |
|
120 |
and terminate events and only actions them between calls |
|
121 |
to C<gd_run_body()>. |
|
122 |
|
|
123 |
Terminate events will be forced through after |
|
124 |
C<$Daemon::Generic::force_quit_delay> seconds if |
|
125 |
C<gd_run_body()> doesn't return quickly enough. |
|
126 |
|
|
127 |
=head1 SUBCLASS METHODS REQUIRD |
|
128 |
|
|
129 |
The following method is required to be overridden to subclass |
|
130 |
Daemon::Generic::While1: |
|
131 |
|
|
132 |
=over 15 |
|
133 |
|
|
134 |
=item gd_run_body() |
|
135 |
|
|
136 |
This method will be called over and over. This method should |
|
137 |
include a call to C<sleep(1)> (or a bit more). Reconfig events |
|
138 |
will not interrupt it. Quit events will only interrupt it |
|
139 |
after 15 seconds. |
|
140 |
|
|
141 |
=back |
|
142 |
|
|
143 |
=head1 ADDITIONAL METHODS |
|
144 |
|
|
145 |
The following additional methods are available for your use |
|
146 |
(as compared to L<Daemon::Generic>): |
|
147 |
|
|
148 |
=over 15 |
|
149 |
|
|
150 |
=item gd_sleep($period) |
|
151 |
|
|
152 |
This will sleep for C<$period> seconds but in one-second |
|
153 |
intervals so that if a SIGINT or SIGHUP arrives the sleep |
|
154 |
period can end more quickly. |
|
155 |
|
|
156 |
Using this makes it safe for C<gd_run_body()> to sleep for |
|
157 |
longer than C<$Daemon::Generic::force_quit_delay> seconds |
|
158 |
at a time. |
|
159 |
|
|
160 |
=back |
|
161 |
|
|
162 |
=head1 ADDITIONAL MEMBER DATA |
|
163 |
|
|
164 |
The following additional bits of member data are defined: |
|
165 |
|
|
166 |
=over 15 |
|
167 |
|
|
168 |
=item gd_sigint |
|
169 |
|
|
170 |
The time at which an (unprocessed) SIGINT was recevied. |
|
171 |
|
|
172 |
=item gd_sighup |
|
173 |
|
|
174 |
The time at which an (unprocessed) SIGHUP was recevied. |
|
175 |
|
|
176 |
=back |
|
177 |
|
|
178 |
=head1 THANK THE AUTHOR |
|
179 |
|
|
180 |
If you need high-speed internet services (T1, T3, OC3 etc), please |
|
181 |
send me your request-for-quote. I have access to very good pricing: |
|
182 |
you'll save money and get a great service. |
|
183 |
|
|
184 |
=head1 LICENSE |
|
185 |
|
|
186 |
Copyright(C) 2006 David Muir Sharnoff <muir@idiom.com>. |
|
187 |
This module may be used and distributed on the same terms |
|
188 |
as Perl itself. |
|
189 |
|
modules/fallback/File/Flock.pm | ||
---|---|---|
1 |
# Copyright (C) 1996, 1998 David Muir Sharnoff |
|
2 |
|
|
3 |
package File::Flock; |
|
4 |
|
|
5 |
require Exporter; |
|
6 |
@ISA = qw(Exporter); |
|
7 |
@EXPORT = qw(lock unlock lock_rename); |
|
8 |
|
|
9 |
use Carp; |
|
10 |
use POSIX qw(EAGAIN EACCES EWOULDBLOCK ENOENT EEXIST O_EXCL O_CREAT O_RDWR); |
|
11 |
use Fcntl qw(LOCK_SH LOCK_EX LOCK_NB LOCK_UN); |
|
12 |
use IO::File; |
|
13 |
|
|
14 |
use vars qw($VERSION $debug $av0debug); |
|
15 |
|
|
16 |
BEGIN { |
|
17 |
$VERSION = 2008.01; |
|
18 |
$debug = 0; |
|
19 |
$av0debug = 0; |
|
20 |
} |
|
21 |
|
|
22 |
use strict; |
|
23 |
no strict qw(refs); |
|
24 |
|
|
25 |
my %locks; # did we create the file? |
|
26 |
my %lockHandle; |
|
27 |
my %shared; |
|
28 |
my %pid; |
|
29 |
my %rm; |
|
30 |
|
|
31 |
sub new |
|
32 |
{ |
|
33 |
my ($pkg, $file, $shared, $nonblocking) = @_; |
|
34 |
&lock($file, $shared, $nonblocking) or return undef; |
|
35 |
return bless \$file, $pkg; |
|
36 |
} |
|
37 |
|
|
38 |
sub DESTROY |
|
39 |
{ |
|
40 |
my ($this) = @_; |
|
41 |
unlock($$this); |
|
42 |
} |
|
43 |
|
|
44 |
sub lock |
|
45 |
{ |
|
46 |
my ($file, $shared, $nonblocking) = @_; |
|
47 |
|
|
48 |
my $f = new IO::File; |
|
49 |
|
|
50 |
my $created = 0; |
|
51 |
my $previous = exists $locks{$file}; |
|
52 |
|
|
53 |
# the file may be springing in and out of existance... |
|
54 |
OPEN: |
|
55 |
for(;;) { |
|
56 |
if (-e $file) { |
|
57 |
unless (sysopen($f, $file, O_RDWR)) { |
|
58 |
redo OPEN if $! == ENOENT; |
|
59 |
croak "open $file: $!"; |
|
60 |
} |
|
61 |
} else { |
|
62 |
unless (sysopen($f, $file, O_CREAT|O_EXCL|O_RDWR)) { |
|
63 |
redo OPEN if $! == EEXIST; |
|
64 |
croak "open >$file: $!"; |
|
65 |
} |
|
66 |
print STDERR " {$$ " if $debug; # } |
|
67 |
$created = 1; |
|
68 |
} |
|
69 |
last; |
|
70 |
} |
|
71 |
$locks{$file} = $created || $locks{$file} || 0; |
|
72 |
$shared{$file} = $shared; |
|
73 |
$pid{$file} = $$; |
|
74 |
|
|
75 |
$lockHandle{$file} = $f; |
|
76 |
|
|
77 |
my $flags; |
|
78 |
|
|
79 |
$flags = $shared ? LOCK_SH : LOCK_EX; |
|
80 |
$flags |= LOCK_NB |
|
81 |
if $nonblocking; |
|
82 |
|
|
83 |
local($0) = "$0 - locking $file" if $av0debug && ! $nonblocking; |
|
84 |
my $r = flock($f, $flags); |
|
85 |
|
|
86 |
print STDERR " ($$ " if $debug and $r; |
|
87 |
|
|
88 |
if ($r) { |
|
89 |
# let's check to make sure the file wasn't |
|
90 |
# removed on us! |
|
91 |
|
|
92 |
my $ifile = (stat($file))[1]; |
|
93 |
my $ihandle; |
|
94 |
eval { $ihandle = (stat($f))[1] }; |
|
95 |
croak $@ if $@; |
|
96 |
|
|
97 |
return 1 if defined $ifile |
|
98 |
and defined $ihandle |
|
99 |
and $ifile == $ihandle; |
|
100 |
|
|
101 |
# oh well, try again |
|
102 |
flock($f, LOCK_UN); |
|
103 |
close($f); |
|
104 |
return File::Flock::lock($file); |
|
105 |
} |
|
106 |
|
|
107 |
return 1 if $r; |
|
108 |
if ($nonblocking and |
|
109 |
(($! == EAGAIN) |
|
110 |
or ($! == EACCES) |
|
111 |
or ($! == EWOULDBLOCK))) |
|
112 |
{ |
|
113 |
if (! $previous) { |
|
114 |
delete $locks{$file}; |
|
115 |
delete $lockHandle{$file}; |
|
116 |
delete $shared{$file}; |
|
117 |
delete $pid{$file}; |
|
118 |
} |
|
119 |
if ($created) { |
|
120 |
# oops, a bad thing just happened. |
|
121 |
# We don't want to block, but we made the file. |
|
122 |
&background_remove($f, $file); |
|
123 |
} |
|
124 |
close($f); |
|
125 |
return 0; |
|
126 |
} |
|
127 |
croak "flock $f $flags: $!"; |
|
128 |
} |
|
129 |
|
|
130 |
# |
|
131 |
# get a lock on a file and remove it if it's empty. This is to |
|
132 |
# remove files that were created just so that they could be locked. |
|
133 |
# |
|
134 |
# To do this without blocking, defer any files that are locked to the |
|
135 |
# the END block. |
|
136 |
# |
|
137 |
sub background_remove |
|
138 |
{ |
|
139 |
my ($f, $file) = @_; |
|
140 |
|
|
141 |
if (flock($f, LOCK_EX|LOCK_NB)) { |
|
142 |
unlink($file) |
|
143 |
if -s $file == 0; |
|
144 |
flock($f, LOCK_UN); |
|
145 |
return 1; |
|
146 |
} else { |
|
147 |
$rm{$file} = 1 |
|
148 |
unless exists $rm{$file}; |
|
149 |
return 0; |
|
150 |
} |
|
151 |
} |
|
152 |
|
|
153 |
sub unlock |
|
154 |
{ |
|
155 |
my ($file) = @_; |
|
156 |
|
|
157 |
if (ref $file eq 'File::Flock') { |
|
158 |
bless $file, 'UNIVERSAL'; # avoid destructor later |
|
159 |
$file = $$file; |
|
160 |
} |
|
161 |
|
|
162 |
croak "no lock on $file" unless exists $locks{$file}; |
|
163 |
my $created = $locks{$file}; |
|
164 |
my $unlocked = 0; |
|
165 |
|
|
166 |
|
|
167 |
my $size = -s $file; |
|
168 |
if ($created && defined($size) && $size == 0) { |
|
169 |
if ($shared{$file}) { |
|
170 |
$unlocked = |
|
171 |
&background_remove($lockHandle{$file}, $file); |
|
172 |
} else { |
|
173 |
# { |
|
174 |
print STDERR " $$} " if $debug; |
|
175 |
unlink($file) |
|
176 |
or croak "unlink $file: $!"; |
|
177 |
} |
|
178 |
} |
|
179 |
delete $locks{$file}; |
|
180 |
delete $pid{$file}; |
|
181 |
|
|
182 |
my $f = $lockHandle{$file}; |
|
183 |
|
|
184 |
delete $lockHandle{$file}; |
|
185 |
|
|
186 |
return 0 unless defined $f; |
|
187 |
|
|
188 |
print STDERR " $$) " if $debug; |
|
189 |
$unlocked or flock($f, LOCK_UN) |
|
190 |
or croak "flock $file UN: $!"; |
|
191 |
|
|
192 |
close($f); |
|
193 |
return 1; |
|
194 |
} |
|
195 |
|
|
196 |
sub lock_rename |
|
197 |
{ |
|
198 |
my ($oldfile, $newfile) = @_; |
|
199 |
|
|
200 |
if (exists $locks{$newfile}) { |
|
201 |
unlock $newfile; |
|
202 |
} |
|
203 |
delete $locks{$newfile}; |
|
204 |
delete $shared{$newfile}; |
|
205 |
delete $pid{$newfile}; |
|
206 |
delete $lockHandle{$newfile}; |
|
207 |
delete $rm{$newfile}; |
|
208 |
|
|
209 |
$locks{$newfile} = $locks{$oldfile} if exists $locks{$oldfile}; |
|
210 |
$shared{$newfile} = $shared{$oldfile} if exists $shared{$oldfile}; |
|
211 |
$pid{$newfile} = $pid{$oldfile} if exists $pid{$oldfile}; |
|
212 |
$lockHandle{$newfile} = $lockHandle{$oldfile} if exists $lockHandle{$oldfile}; |
|
213 |
$rm{$newfile} = $rm{$oldfile} if exists $rm{$oldfile}; |
|
214 |
|
|
215 |
delete $locks{$oldfile}; |
|
216 |
delete $shared{$oldfile}; |
|
217 |
delete $pid{$oldfile}; |
|
218 |
delete $lockHandle{$oldfile}; |
|
219 |
delete $rm{$oldfile}; |
|
220 |
} |
|
221 |
|
|
222 |
# |
|
223 |
# Unlock any files that are still locked and remove any files |
|
224 |
# that were created just so that they could be locked. |
|
225 |
# |
|
226 |
END { |
|
227 |
my $f; |
|
228 |
for $f (keys %locks) { |
|
229 |
&unlock($f) |
|
230 |
if $pid{$f} == $$; |
|
231 |
} |
|
232 |
|
|
233 |
my %bgrm; |
|
234 |
for my $file (keys %rm) { |
|
235 |
my $f = new IO::File; |
|
236 |
if (sysopen($f, $file, O_RDWR)) { |
|
237 |
if (flock($f, LOCK_EX|LOCK_NB)) { |
|
238 |
unlink($file) |
|
239 |
if -s $file == 0; |
|
240 |
flock($f, LOCK_UN); |
|
241 |
} else { |
|
242 |
$bgrm{$file} = 1; |
|
243 |
} |
|
244 |
close($f); |
|
245 |
} |
|
246 |
} |
|
247 |
if (%bgrm) { |
|
248 |
my $ppid = fork; |
|
249 |
croak "cannot fork" unless defined $ppid; |
|
250 |
my $pppid = $$; |
|
251 |
my $b0 = $0; |
|
252 |
$0 = "$b0: waiting for child ($ppid) to fork()"; |
|
253 |
unless ($ppid) { |
|
254 |
my $pid = fork; |
|
255 |
croak "cannot fork" unless defined $pid; |
|
256 |
unless ($pid) { |
|
257 |
for my $file (keys %bgrm) { |
|
258 |
my $f = new IO::File; |
|
259 |
if (sysopen($f, $file, O_RDWR)) { |
|
260 |
if (flock($f, LOCK_EX)) { |
|
261 |
unlink($file) |
|
262 |
if -s $file == 0; |
|
263 |
flock($f, LOCK_UN); |
|
264 |
} |
|
265 |
close($f); |
|
266 |
} |
|
267 |
} |
|
268 |
print STDERR " $pppid] $pppid)" if $debug; |
|
269 |
} |
|
270 |
kill(9, $$); # exit w/o END or anything else |
|
271 |
} |
|
272 |
waitpid($ppid, 0); |
|
273 |
kill(9, $$); # exit w/o END or anything else |
|
274 |
} |
|
275 |
} |
|
276 |
|
|
277 |
1; |
|
278 |
|
|
279 |
__DATA__ |
|
280 |
|
|
281 |
=head1 NAME |
|
282 |
|
|
283 |
File::Flock - file locking with flock |
|
284 |
|
|
285 |
=head1 SYNOPSIS |
|
286 |
|
|
287 |
use File::Flock; |
|
288 |
|
|
289 |
lock($filename); |
|
290 |
|
|
291 |
lock($filename, 'shared'); |
|
292 |
|
|
293 |
lock($filename, undef, 'nonblocking'); |
|
294 |
|
|
295 |
lock($filename, 'shared', 'nonblocking'); |
|
296 |
|
|
297 |
unlock($filename); |
|
298 |
|
|
299 |
my $lock = new File::Flock '/somefile'; |
|
300 |
|
|
301 |
lock_rename($oldfilename, $newfilename) |
|
302 |
|
|
303 |
=head1 DESCRIPTION |
|
304 |
|
|
305 |
Lock files using the flock() call. If the file to be locked does not |
|
306 |
exist, then the file is created. If the file was created then it will |
|
307 |
be removed when it is unlocked assuming it's still an empty file. |
|
308 |
|
|
309 |
Locks can be created by new'ing a B<File::Flock> object. Such locks |
|
310 |
are automatically removed when the object goes out of scope. The |
|
311 |
B<unlock()> method may also be used. |
|
312 |
|
|
313 |
B<lock_rename()> is used to tell File::Flock when a file has been |
|
314 |
renamed (and thus the internal locking data that is stored based |
|
315 |
on the filename should be moved to a new name). B<unlock()> the |
|
316 |
new name rather than the original name. |
|
317 |
|
|
318 |
=head1 LICENSE |
|
319 |
|
|
320 |
File::Flock may be used/modified/distibuted on the same terms |
|
321 |
as perl itself. |
|
322 |
|
|
323 |
=head1 AUTHOR |
|
324 |
|
|
325 |
David Muir Sharnoff <muir@idiom.org> |
|
326 |
|
|
327 |
|
Auch abrufbar als: Unified diff
Perl-Module für Daemons