]> wagnertech.de Git - kivitendo-erp.git/blobdiff - modules/fallback/Daemon/Generic.pm
Perl-Module für Daemons
[kivitendo-erp.git] / modules / fallback / Daemon / Generic.pm
diff --git a/modules/fallback/Daemon/Generic.pm b/modules/fallback/Daemon/Generic.pm
new file mode 100644 (file)
index 0000000..c185e8a
--- /dev/null
@@ -0,0 +1,553 @@
+
+# Copyright (C) 2006, David Muir Sharnoff <perl@dave.sharnoff.org>
+
+package Daemon::Generic;
+
+use strict;
+use warnings;
+require Exporter;
+require POSIX;
+use Getopt::Long;
+use File::Slurp;
+use File::Flock;
+our @ISA = qw(Exporter);
+our @EXPORT = qw(newdaemon);
+
+our $VERSION = 0.71;
+
+our $force_quit_delay = 15;
+our $package = __PACKAGE__;
+our $caller;
+
+sub newdaemon
+{
+       my (%args) = @_;
+       my $pkg = $caller || caller() || 'main';
+
+       my $foo = bless {}, $pkg;
+
+       unless ($foo->isa($package)) {
+               no strict qw(refs);
+               my $isa = \@{"${pkg}::ISA"};
+               unshift(@$isa, $package);
+       }
+
+       bless $foo, 'This::Package::Does::Not::Exist';
+       undef $foo;
+
+       new($pkg, %args);
+}
+
+sub new
+{
+       my ($pkg, %args) = @_;
+
+       if ($pkg eq __PACKAGE__) {
+               $pkg = caller() || 'main';
+       }
+
+       srand(time ^ ($$ << 5))
+               unless $args{no_srand};
+
+       my $av0 = $0;
+       $av0 =~ s!/!/.!g;
+
+       my $self = {
+               gd_args         => \%args,
+               gd_pidfile      => $args{pidfile},
+               gd_logpriority  => $args{logpriority},
+               gd_progname     => $args{progname}
+                                       ? $args{progname}
+                                       : $0,
+               gd_pidbase      => $args{pidbase}
+                                       ? $args{pidbase}
+                                       : ($args{progname} 
+                                               ? "/var/run/$args{progname}"
+                                               : "/var/run/$av0"),
+               gd_foreground   => $args{foreground} || 0,
+               configfile      => $args{configfile}
+                                       ? $args{configfile}
+                                       : ($args{progname}
+                                               ? "/etc/$args{progname}.conf"
+                                               : "/etc/$av0"),
+               debug           => $args{debug} || 0,
+       };
+       bless $self, $pkg;
+
+       $self->gd_getopt;
+       $self->gd_parse_argv;
+
+       my $do = $self->{do} = $ARGV[0];
+
+       $self->gd_help          if $do eq 'help';
+       $self->gd_version       if $do eq 'version';
+       $self->gd_install       if $do eq 'install';
+       $self->gd_uninstall     if $do eq 'uninstall';
+
+       $self->gd_pidfile unless $self->{gd_pidfile};
+
+       my %newconfig = $self->gd_preconfig;
+
+       $self->{gd_pidfile} = $newconfig{pidfile} if $newconfig{pidfile};
+
+       print "Configuration looks okay\n" if $do eq 'check';
+
+       my $pidfile = $self->{gd_pidfile};
+       my $killed = 0;
+       my $locked = 0;
+       if (-e $pidfile) {
+               if ($locked = lock($pidfile, undef, 'nonblocking')) {
+                       # old process is dead
+                       if ($do eq 'status') {
+                           print "$0 dead\n";
+                           exit 1;
+                       }
+               } else {
+                       sleep(2) if -M $pidfile < 2/86400;
+                       my $oldpid = read_file($pidfile);
+                       chomp($oldpid);
+                       if ($oldpid) {
+                               if ($do eq 'stop' or $do eq 'restart') {
+                                       $killed = $self->gd_kill($oldpid);
+                                       $locked = lock($pidfile);
+                                       if ($do eq 'stop') {
+                                               unlink($pidfile);
+                                               exit;
+                                       }
+                               } elsif ($do eq 'reload') {
+                                       if (kill(1,$oldpid)) {
+                                               print "Requested reconfiguration\n";
+                                               exit;
+                                       } else {
+                                               print "Kill failed: $!\n";
+                                       }
+                               } elsif ($do eq 'status') {
+                                       if (kill(0,$oldpid)) {
+                                               print "$0 running - pid $oldpid\n";
+                                               $self->gd_check($pidfile, $oldpid);
+                                               exit 0;
+                                       } else {
+                                               print "$0 dead\n";
+                                               exit 1;
+                                       }
+                               } elsif ($do eq 'check') {
+                                       if (kill(0,$oldpid)) {
+                                               print "$0 running - pid $oldpid\n";
+                                               $self->gd_check($pidfile, $oldpid);
+                                               exit;
+                                       } 
+                               } elsif ($do eq 'start') {
+                                       print "\u$self->{gd_progname} is already running (pid $oldpid)\n";
+                                       exit; # according to LSB, this is no error
+                               }
+                       } else {
+                               $self->gd_error("Pid file $pidfile is invalid but locked, exiting\n");
+                       }
+               }
+       } else {
+               $locked = lock($pidfile, undef, 'nonblocking') 
+                       or die "Could not lock pid file $pidfile: $!";
+       }
+
+       if ($do eq 'reload' || $do eq 'stop' || $do eq 'check' || ($do eq 'restart' && ! $killed)) {
+               print "No $0 running\n";
+       }
+
+       if ($do eq 'stop') {
+               unlink($pidfile);
+               exit;
+       }
+
+       if ($do eq 'status') {
+               print "Unused\n";
+               exit 3;
+       }
+
+       if ($do eq 'check') {
+               $self->gd_check($pidfile);
+               exit 
+       }
+
+       unless ($do eq 'reload' || $do eq 'restart' || $do eq 'start') {
+               $self->gd_other_cmd($do, $locked);
+       }
+
+       unless ($self->{gd_foreground}) {
+               $self->gd_daemonize;
+       }
+
+       $locked or lock($pidfile, undef, 'nonblocking') 
+               or die "Could not lock PID file $pidfile: $!";
+
+       write_file($pidfile, "$$\n");
+
+       print STDERR "Starting up...\n";
+
+       $self->gd_postconfig(%newconfig);
+
+       $self->gd_setup_signals;
+
+       $self->gd_run;
+
+       unlink($pidfile);
+       exit(0);
+}
+
+sub gd_check {}
+
+sub gd_more_opt { return() }
+
+sub gd_getopt
+{
+       my $self = shift;
+       Getopt::Long::Configure("auto_version");
+       GetOptions(
+               'configfile=s'  => \$self->{configfile},
+               'foreground!'   => \$self->{gd_foreground},
+               'debug!'        => \$self->{debug},
+               $self->{gd_args}{options}
+                       ? %{$self->{gd_args}{options}}
+                       : (),
+               $self->gd_more_opt(),
+       ) or exit($self->gd_usage());
+
+       if (@ARGV < ($self->{gd_args}{minimum_args} || 1)) {
+               exit($self->gd_usage());
+       }
+       if (@ARGV > ($self->{gd_args}{maximum_args} || 1)) {
+               exit($self->gd_usage());
+       }
+}
+
+sub gd_parse_argv { }
+
+sub gd_help
+{
+       my $self = shift;
+       exit($self->gd_usage($self->{gd_args}));
+}
+
+sub gd_version
+{
+       my $self = shift;
+       no strict qw(refs);
+       my $v = $self->{gd_args}{version} 
+               || ${ref($self)."::VERSION"} 
+               || $::VERSION 
+               || $main::VERSION 
+               || "?";
+       print "$self->{gd_progname} - version $v\n";;
+       exit;
+} 
+
+sub gd_pidfile
+{
+       my $self = shift;
+       my $x = $self->{configfile};
+       $x =~ s!/!.!g;
+       $self->{gd_pidfile} = "$self->{gd_pidbase}$x.pid";
+}
+
+sub gd_other_cmd
+{
+       my $self = shift;
+       $self->gd_usage;
+       exit(1);
+}
+
+sub gd_redirect_output
+{
+       my $self = shift;
+       return if $self->{gd_foreground};
+       my $logname = $self->gd_logname;
+       my $p = $self->{gd_logpriority} ? "-p $self->{gd_logpriority}" : "";
+       open(STDERR, "|logger $p -t '$logname'") or (print "could not open stderr: $!" && exit(1));
+       close(STDOUT);
+       open(STDOUT, ">&STDERR") or die "redirect STDOUT -> STDERR: $!";
+       close(STDIN);
+}
+
+sub gd_daemonize
+{
+       my $self = shift;
+       print "Starting $self->{gd_progname} server\n";
+       $self->gd_redirect_output();
+       my $pid;
+       POSIX::_exit(0) if $pid = fork;
+       die "Could not fork: $!" unless defined $pid;
+       POSIX::_exit(0) if $pid = fork;
+       die "Could not fork: $!" unless defined $pid;
+
+       POSIX::setsid();
+       select(STDERR);
+       $| = 1;
+       print "Sucessfully daemonized\n";
+}
+
+sub gd_logname
+{
+       my $self = shift;
+       return $self->{gd_progname}."[$$]";
+}
+
+sub gd_reconfig_event
+{
+       my $self = shift;
+       print STDERR "Reconfiguration requested\n";
+       $self->gd_postconfig($self->gd_preconfig());
+}
+
+sub gd_quit_event
+{
+       my $self = shift;
+       print STDERR "Quitting...\n";
+       exit(0);
+}
+
+sub gd_setup_signals
+{
+       my $self = shift;
+       $SIG{INT} = sub { $self->gd_quit_event() };
+       $SIG{HUP} = sub { $self->gd_reconfig_event() };
+}
+
+sub gd_run { die "must defined gd_run()" }
+
+sub gd_error
+{
+       my $self = shift;
+       my $e = shift;
+       my $do = $self->{do};
+       if ($do && $do eq 'stop') {
+               warn $e;
+       } else {
+               die $e;
+       }
+}
+
+sub gd_flags_more { return () }
+
+sub gd_flags
+{
+       my $self = shift;
+       return (
+               '-c file'       => "Specify configuration file (instead of $self->{configfile})",
+               '-f'            => "Run in the foreground (don't detach)",
+               $self->gd_flags_more
+       );
+}
+
+sub gd_commands_more { return () }
+
+sub gd_commands
+{
+       my $self = shift;
+       return (
+               start           => "Starts a new $self->{gd_progname} if there isn't one running already",
+               stop            => "Stops a running $self->{gd_progname}",
+               reload          => "Causes a running $self->{gd_progname} to reload it's config file.  Starts a new one if none is running.",
+               restart         => "Stops a running $self->{gd_progname} if one is running.  Starts a new one.",
+               $self->gd_commands_more(),
+               ($self->gd_can_install()
+                       ? ('install' => "Setup $self->{gd_progname} to run automatically after reboot")
+                       : ()),
+               ($self->gd_can_uninstall()
+                       ? ('uninstall' => "Do not run $self->{gd_progname} after reboots")
+                       : ()),
+               check           => "Check the configuration file and report the daemon state",
+               help            => "Display this usage info",
+               version         => "Display the version of $self->{gd_progname}",
+       )
+}
+
+sub gd_positional_more { return() }
+
+sub gd_alts
+{
+       my $offset = shift;
+       my @results;
+       for (my $i = $offset; $i <= $#_; $i += 2) {
+               push(@results, $_[$i]);
+       }
+       return @results;
+}
+
+sub gd_usage
+{
+       my $self = shift;
+
+       require Text::Wrap;
+       import Text::Wrap;
+
+       my $col = 15;
+
+       my @flags = $self->gd_flags;
+       my @commands = $self->gd_commands;
+       my @positional = $self->gd_positional_more;
+
+       my $summary = "Usage: $self->{gd_progname} ";
+       my $details = '';
+       for my $i (gd_alts(0, @flags)) {
+               $summary .= "[ $i ] ";
+       }
+       $summary .= "{ ";
+       $summary .= join(" | ", gd_alts(0, @commands));
+       $summary .= " } ";
+       $summary .= join(" ", gd_alts(0, @positional));
+
+       my (@all) = (@flags, @commands, @positional);
+       while (@all) {
+               my ($key, $desc) = splice(@all, 0, 2);
+               local($Text::Wrap::columns) = 79;
+               $details .= wrap(
+                       sprintf(" %-${col}s ", $key),
+                       " " x ($col + 2),
+                       $desc);
+               $details .= "\n";
+       }
+
+       print "$summary\n$details";
+       return 0;
+}
+
+sub gd_install_pre {}
+sub gd_install_post {}
+
+sub gd_can_install
+{
+       my $self = shift;
+       require File::Basename;
+       my $basename = File::Basename::basename($0);
+       if (
+               -x "/usr/sbin/update-rc.d"
+               && 
+               -x $0
+               && 
+               $0 !~ m{^(?:/usr|/var)?/tmp/}
+               &&
+               eval { symlink("",""); 1 }
+               && 
+               -d "/etc/init.d"
+               &&
+               ! -e "/etc/init.d/$basename"
+       ) {
+               return sub {
+                       $self->gd_install_pre("update-rc.d");
+                       require Cwd;
+                       my $abs_path = Cwd::abs_path($0);
+                       symlink($abs_path, "/etc/init.d/$basename")
+                               or die "Install failed: symlink /etc/init.d/$basename -> $abs_path: $!\n";
+                       print "+ /usr/sbin/update-rc.d $basename defaults\n";
+                       system("/usr/sbin/update-rc.d", $basename, "defaults");
+                       my $exit = $? >> 8;
+                       $self->gd_install_post("update-rc.d");
+                       exit($exit) if $exit;
+               };
+       }
+
+       return 0;
+}
+
+sub gd_install
+{
+       my $self = shift;
+       my $ifunc = $self->gd_can_install();
+       die "Install command not supported\n" unless $ifunc;
+       &$ifunc($self);
+       exit(0);
+}
+
+sub gd_uninstall_pre {}
+sub gd_uninstall_post {}
+
+sub gd_can_uninstall
+{
+       my $self = shift;
+       require File::Basename;
+       my $basename = File::Basename::basename($0);
+       require Cwd;
+       my $abs_path = Cwd::abs_path($0) || 'no abs path';
+       my $link = readlink("/etc/init.d/$basename") || 'no link';
+       if (
+               $link eq $abs_path
+               && 
+               -x "/usr/sbin/update-rc.d"
+       ) {
+               return sub {
+                       $self->gd_uninstall_pre("update-rc.d");
+                       unlink("/etc/init.d/$basename");
+                       print "+ /usr/sbin/update-rc.d $basename remove\n";
+                       system("/usr/sbin/update-rc.d", $basename, "remove");
+                       my $exit = $? >> 8;
+                       $self->gd_uninstall_post("update-rc.d");
+                       exit($exit) if $exit;
+               }
+       }
+       return 0;
+}
+
+sub gd_uninstall
+{
+       my $self = shift;
+       my $ufunc = $self->gd_can_uninstall();
+       die "Cannot uninstall\n" unless $ufunc;
+       &$ufunc($self);
+       exit(0);
+}
+
+sub gd_kill
+{
+       my ($self, $pid) = @_;
+
+       my $talkmore = 0;
+       my $killed = 0;
+       if (kill(0, $pid)) {
+               $killed = 1;
+               kill(2,$pid);
+               print "Killing $pid\n";
+               my $t = time;
+               sleep(1) if kill(0, $pid);
+               if ($force_quit_delay && kill(0, $pid)) {
+                       print "Waiting for $pid to die...\n";
+                       $talkmore = 1;
+                       while(kill(0, $pid) && time - $t < $force_quit_delay) {
+                               sleep(1);
+                       }
+               }
+               if (kill(15, $pid)) {
+                       print "Killing $pid with -TERM...\n";
+                       if ($force_quit_delay) {
+                               while(kill(0, $pid) && time - $t < $force_quit_delay * 2) {
+                                       sleep(1);
+                               }
+                       } else {
+                               sleep(1) if kill(0, $pid);
+                       }
+               }
+               if (kill(9, $pid)) {
+                       print "Killing $pid with -KILL...\n";
+                       my $k9 = time;
+                       my $max = $force_quit_delay * 4;
+                       $max = 60 if $max < 60;
+                       while(kill(0, $pid)) {
+                               if (time - $k9 > $max) {
+                                       print "Giving up on $pid ever dying.\n";
+                                       exit(1);
+                               }
+                               print "Waiting for $pid to die...\n";
+                               sleep(1);
+                       }
+               }
+               print "Process $pid is gone\n" if $talkmore;
+       } else {
+               print "Process $pid no longer running\n";
+       }
+       return $killed;
+}
+
+sub gd_preconfig { die "gd_preconfig() must be redefined"; }
+
+sub gd_postconfig { }
+
+
+1;