From: Stephen L Johnson Date: Tue, 23 Nov 1999 03:39:31 +0000 (+0000) Subject: Initial revision X-Git-Tag: spong-2_6-beta7~13 X-Git-Url: http://git.etc.gen.nz/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=adf36bfce73e51ad2004801f804704c35125e90a;p=spong.git Initial revision --- diff --git a/src/spong-server.pl b/src/spong-server.pl new file mode 100755 index 0000000..eb3abff --- /dev/null +++ b/src/spong-server.pl @@ -0,0 +1,778 @@ +#!@@PERL@@ +# +# This process splits into two processes. The parent process listens at port +# 1970 and responds to update requests. It takes update information coming +# from the various spong clients and saves it to the local database. +# +# The child process listens at port 1969 and responds to query requests, like +# ones coming from the spong and wwwspong clients. It spits back either text +# or html output that is suitable for display. +# +# History +# (1) Rewritten so that it answered both updates and queries (Ed Jul 22, 1997) +# (2) Added code to handle "*-purple" named status files (Stephen L Johnson Jun 03, 1999) + +use lib "@@LIBDIR@@"; + +use Spong::HostList; +use Spong::Host; +use Spong::ServiceList; +use Spong::Service; +use Spong::HistoryList; +use Spong::History; +use Spong::Daemon; + +use Time::Local; + +use Sys::Hostname; +use File::Path; +use Socket; +use Config; + +if( $ARGV[0] eq "--debug" ) { $debug = 1; shift; } +if( $ARGV[0] eq "--restart" ) { $restart = 1; shift; } +if( $ARGV[0] eq "--kill" ) { $kill = 1; shift; } + +$me = "@@BINDIR@@/spong-server"; +$smessage = "@@BINDIR@@/spong-message"; +$conf_file = $ARGV[0] || "@@ETCDIR@@/spong.conf"; +$hosts_file = "@@ETCDIR@@/spong.hosts"; +$groups_file = "@@ETCDIR@@/spong.groups"; +($HOST) = gethostbyname(&Sys::Hostname::hostname()); +$HOST =~ tr/A-Z/a-z/; + +$upd_pid = 0; +$bb_pid = 0; +$shutdown = 0; + +&load_config_files(); # Loads the user specified configuration information +Spong::Daemon::Daemonize() unless ($debug || $restart || $kill) ; +&handle_signals(); # Set up handlers, and signal the current server if asked + +# Establish a list of all the services that we monitor so that we can be +# consistant in the output that we display regardless of the group that we +# are showing. + +%main::SERVICES = (); # The total list of services that we monitor + +foreach $host ( Spong::HostList->new( "all" )->hosts() ) { + foreach $service ( $host->service_names() ) { $main::SERVICES{$service}=1;}} + +# Split into two processes, one process listens at port 1970 for update +# messages, and the other process listens at port 1960 and responds to query +# messages. Both processes are "single-threaded", they don't fork off +# additional subprocesses, they just deal with the message setting at their +# queue, and then process the next message as it comes in. + +$SIG{'CHLD'} = \&chld_handler; + +if( $upd_pid = fork() ) { + if( $bb_pid = fork() ) { + &listen_for_queries(); + } elsif( defined $bb_pid ) { + &listen_for_bb_updates(); + } else { + die "Can't bb fork: $!"; + } +} elsif( defined $upd_pid ) { + &listen_for_updates(); +} else { + die "Can't update fork: $!"; +} + +# We should not get here, if we do then something is wrong... + +die "Error: Exiting spong-server!\n"; + + +# This procedure listens for connections at port 1970 (the update port), and +# each time a connection is made, it grabs the info that is being sent to us, +# and hands it off to the function that saves that info to the database. Then +# go back to listening for more connections. This procedure does not exit, it +# just continues to listen for update messages. + +sub listen_for_updates { + # Set up the socket to listen to + + $SIG{'PIPE'} = 'IGNORE'; + $SIG{'QUIT'} = sub {&debug('spong updates caught QUIT signal, exiting'); + close SERVER; exit;}; + $SIG{'HUP'} = sub {&debug('spong updates caught HUP signal, exiting'); + close SERVER; exit;}; + + + my $proto = getprotobyname( 'tcp' ); + my $p = pack( "l", 1 ); + + socket( SERVER, PF_INET, SOCK_STREAM, $proto ) || die "socket: $!"; + setsockopt( SERVER, SOL_SOCKET, SO_REUSEADDR, $p ) || die "sockopt: $!"; + bind( SERVER, sockaddr_in( $SPONG_UPDATE_PORT, INADDR_ANY ) ) || + die "bind: $!"; + listen( SERVER, SOMAXCONN ) || die "listen: $!"; + + &debug( "update server socket setup, listening for connections" ); + + while( 1 ) { + next unless ( $paddr = accept( CLIENT, SERVER ) ); + + # &validate_connection( $paddr ); - need to do something here... + + # Read all from the client, and disconnect, we process the message next. + + my $header = ; chomp $header; + my( $message, $cnt, $line ) = ( "", "", "" ); + while( defined( $line = ) ) { + last if ($cnt += length($line)) > 100000; + $message .= $line; + } + close( CLIENT ); + + # Now depending on what kind of message it is, pass it off to a routine + # that can process the message. Currently valid messages are "status", + # "ack", "config", and "stat". + + if( $header =~ /^status\b/ ) { &save_status( $header, $message ); next; } + if( $header =~ /^ack-del\b/ ) { &del_ack( $header, $message ); next; } + if( $header =~ /^ack\b/ ) { &save_ack( $header, $message ); next; } + # if( $header =~ /^config\b/ ) { &save_config( $header, $message ); } + # if( $header =~ /^stat\b/ ) { &save_stat( $header, $message ); } + } +} + +# This procedure listens for connections at port 1984 (the BigBrother port), and +# each time a connection is made, it grabs the info that is being sent to us, +# and hands it off to the function that saves that info to the database. Then +# go back to listening for more connections. This procedure does not exit, it +# just continues to listen for update messages. + +sub listen_for_bb_updates { + # Set up the socket to listen to + + $SIG{'PIPE'} = 'IGNORE'; + $SIG{'QUIT'} = sub {&debug('bb updates caught QUIT signal, exiting'); + close SERVER; exit;}; + $SIG{'HUP'} = sub {&debug('bb updates caught HUP signal, exiting'); + close SERVER; exit;}; + + my $proto = getprotobyname( 'tcp' ); + my $p = pack( "l", 1 ); + + socket( SERVER, PF_INET, SOCK_STREAM, $proto ) || die "socket: $!"; + setsockopt( SERVER, SOL_SOCKET, SO_REUSEADDR, $p ) || die "sockopt: $!"; + bind( SERVER, sockaddr_in( $SPONG_BB_UPDATE_PORT, INADDR_ANY ) ) || + die "bind: $!"; + listen( SERVER, SOMAXCONN ) || die "listen: $!"; + + &debug( "bb update server socket setup, listening for connections" ); + + while( 1 ) { + next unless ( $paddr = accept( CLIENT, SERVER ) ); + + # &validate_connection( $paddr ); - need to do something here... + + # Read all from the client, and disconnect, we process the message next. + + my $header = ; chomp $header; + my( $message, $cnt, $line ) = ( "", "", "" ); + while( defined( $line = ) ) { + last if ($cnt += length($line)) > 100000; + $message .= $line; + } + close( CLIENT ); + +&debug($message); + +# # Now depending on what kind of message it is, pass it off to a routine +# # that can process the message. Currently valid messages are "status", +# # "ack", "config", and "stat". + + if( $header =~ /^status\b/ ) { &save_bb_status( $header, $message ); next;} +# if( $header =~ /^ack-del\b/ ) { &del_ack( $header, $message ); next; } +# if( $header =~ /^ack\b/ ) { &save_ack( $header, $message ); next; } +# # if( $header =~ /^config\b/ ) { &save_config( $header, $message ); } +# # if( $header =~ /^stat\b/ ) { &save_stat( $header, $message ); } + } +} + +# This procedure listens for connections at port 1969 (the query port), and +# each time a connection is made, it grabs the message that is being sent to +# us, and hands it off to the function that responds to that type of query. +# That function will return information corresponding to the user's query and +# that will be passed back to the client making the request. This procedure does not exit, it just to listen for more query requests. + +sub listen_for_queries { + # Set up the socket to listen to + + $SIG{'PIPE'} = 'IGNORE'; + + my $proto = getprotobyname( 'tcp' ); + my $p = pack( "l", 1 ); + + socket( SERVER, PF_INET, SOCK_STREAM, $proto ) || die "socket: $!"; + setsockopt( SERVER, SOL_SOCKET, SO_REUSEADDR, $p ) || die "sockopt: $!"; + bind( SERVER, sockaddr_in( $SPONG_QUERY_PORT, INADDR_ANY ) ) || + die "bind: $!"; + listen( SERVER, SOMAXCONN ) || die "listen: $!"; + + &debug( "query server socket setup, listening for connections" ); + + while( 1 ) { + next unless ( $paddr = accept( CLIENT, SERVER ) ); + + # &validate_connection( $paddr ); - need to do something here... + + # Read the entire query from the client, don't disconnect like we do with + # updates however, as we need to send back some information. Query + # requests are simple one line messages. + + my $header = ; chomp $header; + my( $query, $hosts, $type, $view, $other ) = + ( $header =~ /^(\w+)\s+\[([^\]]*)\]\s+(\w+)\s+(\w+)\b\s*(.*)$/ ); + + # Now depending on what kind of request it is, pass it off to a routine + # that can process the message. + + my( @args ) = ( $hosts, $type, $view ); # Just shortens up the code... + my $output = select CLIENT; + + &debug( "[$$] showing $query information for $hosts [$type:$view]" ); + + if( $query eq "problems" ) { &show_problems( @args ); } + if( $query eq "summary" ) { &show_summary( @args ); } + if( $query eq "history" ) { &show_history( @args ); } + if( $query eq "host" ) { &show_host( @args ); } + if( $query eq "services" ) { &show_services( @args ); } + if( $query eq "acks" ) { &show_acks( @args ); } + if( $query eq "stats" ) { &show_stats( @args ); } + if( $query eq "config" ) { &show_config( @args ); } + if( $query eq "info" ) { &show_info( @args ); } + if( $query eq "service" ) { &show_service( @args, $other ); } + + close( CLIENT ); + select $output; + } +} + +# =========================================================================== +# Database update methods. These all take messages from clients and update +# specific parts of the database. +# =========================================================================== + + +# Take the incoming message, run it through some error checking, save it to +# the database - update the history, and send the information off to +# spong-message and let it decide if it should page/email someone... + +sub save_status { + my( $header, $message ) = @_; + my( $cmd, $host, $service, $color, $time, $sum, $path, $start, $duration ); + + # Do some checking on the message. If it appears bogus, then just + # log a message, and close the connection. + + if( $header =~ /^(\w+) (\S+) (\w+) (\w+) (\d+) (.*)$/ ) { + ($cmd, $host, $service, $color, $time, $sum) = ($1, $2, $3, $4, $5, $6); + + if( $host !~ m/^[a-z0-9_\-\.]+$/ ) { + &error( "save_status: invalid host [$host]" ); return; } + if( $service !~ m/^[a-z0-9_\-\.]+$/ ) { + &error( "save_status: invalid service [$service]" ); return; } + if( $color ne "red" && $color ne "yellow" && $color ne "green" ) { + &error( "save_status: invalid color [$color]" ); return;} + if( $time !~ m/^\d+$/ ) { + &error( "save_status: invalid time [$time]" ); return; } + } else { + &error( "save_status: invalid header [$header]" ); return; + } + + return if $main::HOSTS{$host} eq ""; + + + $start = $time; # Default start time to event time + + # Try to read start time from the status file + if( -f "$SPONGDB/$host/services/$service-$color" ) { + open FILE,"<$SPONGDB/$host/services/$service-$color"; + my $header = ; chomp $header; + if ($header =~ /^timestamp (\d+) (\d+)/) { $start = $1; } + close FILE; + } + + # Before we change the state of the database, call message_user()... + + $duration = $time - $start; + &message_user( "status", $host, $service, $color, $time, $sum, $duration ); + + # Make sure that this service is registered in our master service list. + + $main::SERVICES{$service} = 1; + + # Save the status information to the database, if there is a change in + # color, then update the history table and state file as well. + + if( ! -f "$SPONGDB/$host/services/$service-$color" ) { + $data = "status $time $service $color $sum\n"; + &save_data( ">>", "$SPONGDB/$host/history/current", $data); + } + + &debug( "[$$] updating status for $host/$service/$color" ); + $path = "$SPONGDB/$host/services"; + foreach( "red", "yellow", "green", "purple" ) { unlink "$path/$service-$_"; } + + $data = "timestamp $start $time\n$time $sum\n$message\n"; + &save_data( ">>", "$SPONGDB/$host/services/$service-$color", $data ); +} + + +# Take a BigBrother status message and convert it to a Spong status +# message and send it to save_status for processing + +sub save_bb_status { + my( $header, $message ) = @_; + my( $cmd, $host, $service, $color, $bbtime, $time, $sum ); + +&debug("The old header is = $header"); + +# if ( $header =~ m/^(\w+) (\w+)\.(\w+) (\w+) (\w{3} \w{3}\s+\d+ \d{2}:\d{2}:\d{2} \w+ \d{4})\s+(.*)/ ) { + if ( $header =~ m/^(\w+) ([\w,-_]+)\.(\w+) (\w+) (\w{3} \w{3}\s+\d+ \d{2}:\d{2}:\d{2}[ A-Z]+\d{4})\s+(.*)$/ ) { + ($cmd, $host, $service, $color, $bbtime, $sum) = ($1, $2, $3, $4, $5, $6); + + &debug("cmd = '$cmd' : host = '$host' : service = '$service' : " . + "color = '$color' : time = '$bbtime' : summary = '$sum' "); + + # Convert the system date format to unix time format + if ($bbtime =~ /\w+ (\w{3}) +(\d+) (\d{2}):(\d{2}):(\d{2})[ A-Z]+(\d{4})/) { + my ($mon, $day, $hr, $min, $sec, $yr) = ($1, $2, $3, $4, $5, $6); + + my (@MoY) = ('Jan','Feb','Mar','Apr','May','Jun', + 'Jul','Aug','Sep','Oct','Nov','Dec'); + foreach ( 0..11 ) { if ($mon eq $MoY[$_]) { $mon = $_; last; } } + + $time = timelocal($sec,$mon,$hr,$day,$mon,$yr); + } else { + &error( "save_bb_status: invalid bb time [$bbtime]" ); + return; + } + } + + # Check to see if FQDN is in Summary + if ($sum =~ m/\[([A-Za-z0-9._\-]+)\]\w*(.*)/ ) { + $sum = $2; + $host = $1; +&debug("FQDN found in \$sum: $host '$sum'"); + } + + # Convert the commas in the BB machine back to periods. + $host =~ s/\,/\./g; + + $header = "$cmd $host $service $color $time $sum"; + +&debug("The new header is = $header"); + + save_status($header,$message); +} + + + +# Take the incoming acknowledgment message, run it through some error checking +# save it to the database. + +sub save_ack { + my( $header, $message ) = @_; + my( $cmd, $host, $service, $color, $time, $sum, $path, $file ); + + # Do some checking on the message. If it appears bogus, then just + # log a message, and close the connection. + + if( $header =~ /^(\w+) (\S+) (\S+) (\d+) (\d+) (.*)$/ ) { + ($cmd, $host, $service, $now, $time, $user) = ($1, $2, $3, $4, $5, $6); + + if( $host !~ m/^[a-z0-9_\-\.]+$/ ) { + &error( "save_ack: invalid host [$host]" ); return; } + if( $service !~ m/^[a-z0-9_\-\.\,]+$/ ) { + &error( "save_ack: invalid service [$service]" ); return; } + if( $now !~ m/^\d+$/ ) { + &error( "save_ack: invalid time [$now]" ); return; } + if( $time !~ m/^\d+$/ ) { + &error( "save_ack: invalid time [$time]" ); return; } + if( $user !~ m/^[a-zA-Z0-9_\-\.]+\@[a-zA-Z0-9_\-\.]+$/ ) { + &error( "save_ack: invalid user [$user]" ); return; } + } else { + &error( "save_ack: invalid header [$header]" ); return; + } + + return if $main::HOSTS{$host} eq ""; + + # Before we change the state of the database, call message_user()... + +# &message_user( "ack", $host, $service, $color, $summ); + + # Clean up expired acknowledgments. + + opendir( DIR, "$SPONGDB/$host/acks" ); + while( defined( $file = readdir( DIR ) ) ) { + next unless $file =~ /^\d+-\d+-(\d+)$/; + unlink "$SPONGDB/$host/acks/$file" if( $1 < time() ); + } + closedir( DIR ); + + # Let the history reflect that the acknowledgment happened, and save data. + + &debug( "[$$] setting acknowledgment for $host/$service" ); + + $data = "ack $now $service $user\n"; + &save_data( ">>", "$SPONGDB/$host/history/current", $data ); + + $data = "$user $service\n$message\n"; + $file = "$SPONGDB/$host/acks/" . int(rand($$)) . "-$now-$time"; + &save_data( ">>", $file, $data ); +} + +# Take the incoming acknowledgment message, run it through some error checking +# pull apart the information that we need from the id, and then remove the +# ack from the database. + +sub del_ack { + my( $header, $message ) = @_; + my( $cmd, $host, $service, $end, $path, $file ); + + # Do some checking on the message. If it appears bogus, then just + # log a message, and close the connection. + + if( $header =~ /^(\S+) ([a-z0-9_\-\.]+)-([a-z0-9_\-\.]+)-(\d+)\s*$/i ) { + ($cmd, $host, $service, $end) = ($1, $2, $3, $4); + } else { + &error( "del_ack: invalid header [$header]" ); return; + } + + if( $main::HOSTS{$host} eq "" ) { + &error( "del_ack: invalid host [$host]" ); return; } + + # Remove the acknowledgement that the user specified. + + opendir( DIR, "$main::SPONGDB/$host/acks" ); + while( defined( $file = readdir( DIR ) ) ) { + next unless $file =~ /-$end$/; + unlink "$SPONGDB/$host/acks/$file"; + } + closedir( DIR ); + + # I don't updated the history when an ack is deleted, I suppose I should + # but it makes things a little messing (since an update is just a delete + # and a re-add). +} + +# =========================================================================== +# Database query methods. These all take messages from clients and returns +# information (in the form of text or html output) reflecting the current +# status of the database. +# =========================================================================== + + +sub show_problems { + my( $hosts, $type, $view ) = @_; + Spong::HostList->new( hostlist($hosts) )->display_problems( $type, $view );} + +sub show_summary { + my( $hosts, $type, $view ) = @_; + Spong::HostList->new( hostlist( $hosts ) )->display( $type, $view ); } + +sub show_history { + my( $hosts, $type, $view ) = @_; + my $hostlist = Spong::HostList->new( hostlist( $hosts ) ); + my $historylist = Spong::HistoryList->new(); + my $host; + + foreach $host ( $hostlist->hosts() ) { + $historylist->add_history( $host->history() ); } + + $historylist->display( $type, $view ); +} + +sub show_acks { + my( $hosts, $type, $view ) = @_; + my $hostlist = Spong::HostList->new( hostlist( $hosts ) ); + my $acklist = Spong::AckList->new(); + my $host; + + foreach $host ( $hostlist->hosts() ) { + $acklist->add_acks( $host->acks() ); } + + $acklist->display( $type, $view ); +} + + +# Need to check if the list we get back is an object or not!!! + +sub show_host { + my( $hosts, $type, $view ) = @_; + Spong::Host->new( $hosts )->display( $type, $view ); } + +sub show_services { + my( $hosts, $type, $view ) = @_; + Spong::Host->new( $hosts )->service_list()->display( $type, $view ); } + +sub show_stats { + my( $hosts, $type, $view ) = @_; + Spong::Host->new( $hosts )->stats_list()->display( $type, $view ); } + +sub show_config { + my( $hosts, $type, $view ) = @_; + Spong::Host->new( $hosts )->config()->display( $type, $view ); } + +sub show_info { + my( $hosts, $type, $view ) = @_; + Spong::Host->new( $hosts )->info()->display( $type, $view ); } + + +sub show_service { + my( $hosts, $type, $view, $other ) = @_; + Spong::Host->new( $hosts )->service( $other )->display( $type, $view ); } + + + + +# =========================================================================== +# Utility functions, and signal handlers... +# =========================================================================== + +# This function will collect information from various sources, run it through +# a set of rules defined by the user and optionally call the spong-message +# program which will either email or page a human with a message providing +# information about the event that just happened. Note - this function gets +# called with every event that comes in, so it should be somewhat fast. + +sub message_user { + my ( $cmd, $host, $service, $color, $time, $sum, $duration ) = @_; + + if ($SEND_MESSAGE eq 'NONE') { return; } + + if (! defined $duration || $duration eq "") { $duration = 0; } + + $SIG{'CHLD'} = 'IGNORE'; + + if ( $cmd eq "status" ) { + if ($color eq 'red' && $duration != 0) { + if ( -f $smessage ) { + &debug("color is red, calling smessage for escalations"); + system $smessage, $color, $host, $service, $time, $sum, $duration; + } else { + &error( "could not send message, $smessage not found" ); + } + return; + } + + if ( $SEND_MESSAGE eq "RED" ) { + # If status has changed to red, call spong-mesage + if ( ! -f "$SPONGDB/$host/services/$service-red" + and $color eq "red" ) { + if ( -f $smessage ) { + &debug("change in state to red, messaging a human"); + system $smessage, $color, $host, $service, $time, $sum, $duration; + } else { + &error( "could not send message, $smessage not found" ); + } + } } elsif ( $SEND_MESSAGE eq "RED-CHANGE" ) { + # If status has changed and either color is red, call spong-message + if ( ( -f "$SPONGDB/$host/services/$service-red" + or $color eq "red" ) + and ! -f "$SPONGDB/$host/services/$service-$color" ) { + if ( -f $smessage ) { + &debug("change in state to/from red, messaging a human"); + system $smessage, $color, $host, $service, $time, $sum, $duration; + } else { + &error( "could not send message, $smessage not found" ); + } + } + } elsif ( $SEND_MESSAGE eq "CHANGE" ) { + # If status has changed + if ( ! -f "$SPONGDB/$host/services/$service.$color" ) { + if ( -f $smessage ) { + &debug("change in state, messaging a human"); + system $smessage, $color, $host, $service, $time, $sum, $duration; + + } else { + &error( "could not send message, $smessage not found" ); + } + } + } + } + + $SIG{'CHLD'} = \&chld_handler; +} + + +# This function just loads in all the configuration information from the +# spong.conf, spong.hosts, and spong.groups files. + +sub load_config_files { + my( $evalme, $inhosts ); + + require $conf_file || die "Can't load $conf_file: $!"; + if( -f "$conf_file.$HOST" ) { + require "$conf_file.$HOST" || die "Can't load $conf_file.$HOST: $!"; + } else { + my $tmp = (split( /\./, $HOST ))[0]; + if( -f "$conf_file.$tmp" ) { # for lazy typist + require "$conf_file.$tmp" || die "Can't load $conf_file.$tmp: $!"; + } + } + + # Read in the spong.hosts file. We are a little nasty here in that we do + # some junk to scan through the file so that we can maintain the order of + # the hosts as they appear in the file. + + open( HOSTS, $hosts_file ) || die "Can't load $hosts_file: $!"; + while( ) { + $evalme .= $_; + if( /^\s*%HOSTS\s*=\s*\(/ ) { $inhosts = 1; } + if( $inhosts && /^\s*[\'\"]?([^\s\'\"]+)[\'\"]?\s*\=\>\s*\{/ ) { + push( @HOSTS_LIST, $1 ); } + } + close( HOSTS ); + eval $evalme || die "Invalid spong.hosts file: $@"; + + # Fallback, if we didn't read things correctly... + + if( sort @HOSTS_LIST != sort keys %HOSTS ) { + @HOSTS_LIST = sort keys %HOSTS; } + + # Read in the group file + + require $groups_file if -f $groups_file; +} + + +# This is part of the set up code, this sets up the signal handlers, and +# handles any command line arguments that are given to us that tell us to +# signal the current running spong-server program. + +sub handle_signals { + + # Set up some signal handlers to handle our death gracefully, and also + # listen for the HUP signal, and if we see that, we re-exec ourself. + + $SIG{'QUIT'} = \&exit_handler; + $SIG{'TERM'} = \&exit_handler; + $SIG{'HUP'} = \&hup_handler; + + # If the user gives us the --restart or --kill flags, then we signal the + # currently running spong-client process, and tell it to either die, or + # re-exec itself (which causes it to re-read it's configuration files. + + if( $restart || $kill ) { + open( PID, "$SPONGTMP/spong-server.pid" ) || die "Can't find pid: $!"; + my $pid = ; chomp $pid; + close PID; + + if( $restart ) { + &debug( "telling pid $pid to restart" ); kill( 'HUP', $pid ); } + if( $kill ) { + &debug( "telling pid $pid to die" ); kill( 'QUIT', $pid );} + + exit(0); + } + + # Write out our pid files, so that others can signal us. + + system( "echo $$ >$SPONGTMP/spong-server.pid" ); +} + + +# Converts a command line representation of a host list to something more +# useful to the Spong objects. + +sub hostlist { + my $hosts = shift; + my $hostlist; + + $hosts = "all" if $hosts eq ""; + if( $hosts =~ /^all$/i || ref( $GROUPS{$hosts} ) eq "HASH" ) { + $hostlist = $hosts; + } else { + $hostlist = [ split( /\,/, $hosts ) ]; + } + + return $hostlist; +} + + +# This takes a file name, and some data to write (or append) to that file, and +# saves that data to the file, fixing permissions and checking for errors along +# the way... + +sub save_data { + my( $mode, $file, $data ) = @_; + my( $dir ) = ( $file =~ /^(.*)\/[^\/]+$/ ); + my $umask; + + $umask = umask(); + umask 022; + mkpath( $dir, 0, 0777 ) if ! -d $dir; + chmod 0755, $dir; + open( DATA, "$mode $file" ) || &error("save_data: can't save to $file: $!"); + select((select(DATA), $| = 1)[0]); + print DATA $data; + close( DATA ); + chmod 0644, $file; + umask $umask; +} + + +# Output functions, one for debugging information, the other for errors. + +sub debug { print STDOUT scalar localtime, " ", $_[0], "\n" if $main::debug; } +sub error { warn scalar localtime(), " Error: ", $_[0], "\n"; } + + +# Signal handlers... + +sub exit_handler { + &debug( "caught QUIT signal, exiting..." ); + $shutdown = 1; + kill QUIT,$upd_pid,$bb_pid; + unlink "$SPONGTMP/spong-server.pid" if -f "$SPONGTMP/spong-server.pid"; + exit(0); +} + +sub hup_handler { + &debug( "caught HUP signal, restarting..." ); + $shutdown = 1; + $SIG{CHLD} = 'DEFAULT'; + kill QUIT,$upd_pid; waitpid($upd_pid,0); + kill QUIT,$bb_pid; waitpid($bb_pid,0); + unlink "$SPONGTMP/spong-server.pid" if -f "$SPONGTMP/spong-server.pid"; + close( SERVER ); + if( $debug ) { exec $me, "--debug"; } else { exec $me; } +} + +# If the child process dies for some reason, then we restart it. + +sub chld_handler { + my($pid) = wait(); + $SIG{'CHLD'} = \&chld_handler; # In case of SYS V libraries + if ($shutdown) { + &debug( 'Shutting down, not restarting children'); + return; + } + &debug( "caught CHLD signal, restarting child..." ); + + if ($pid == $upd_pid) { + if( $upd_pid = fork() ) { + return; + } elsif( defined $upd_pid ) { + &listen_for_updates(); + } else { + die "Can't fork: $!"; + } + } elsif ($pid == $bb_pid ) { + if( $bb_pid = fork() ) { + return; + } elsif( defined $bb_pid ) { + &listen_for_bb_updates(); + } else { + die "Can't fork: $!"; + } + } +} +