#!/usr/bin/perl # gameserver startup script init file # # chkconfig: 2345 99 99 # description: Game Manager # # config: /etc/games/configs # # Red Hat run 'chkconfig --add games' to add this init script to the system # # ** now that the red hat crud is taken care of ** # Name: Game Server Startup Script # Description: Perl script to manage linux dedicated game servers # Version: 1.1 # Authors: Jason Jorgensen # Copyright (C) 2003 Jason Jorgensen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ########################################################################### # # Dependencies: - must have qstat install for verification that a game is running like its suppose to be. # - 'netstat -lep' must stay the same from when this was developed, else the &get_whats_running() function must be modified my $debug = 0; my $gameserver_user = "gameserver"; my $configdir = "/etc/games/configs"; my $chrootdir = "/usr/local/games"; my $prevbroken_file = "/tmp/previously_broken_game_servers"; my ($scriptname) =( $0 =~ /.*\/(.*)$/ ); my $gameserver_username; my $config_dir; my @selected; my $arg1; my $qstatbinary = `which qstat`; chomp $qstatbinary; my @options = ('start', 'stop', 'restart', 'list'); if ($qstatbinary) {push @options, ('cronfix', 'fix', 'status'); } my @configs; my @unsortedallconfigfiles; # my @allconfigfiles; # my @allports; # All derived from @configs my @allprocessnames; # my @allpids; # my %validports; &configs_setup(); #populate the above config specific variables my @runninggames; my @allrunningports; # my @allrunningprocessnames; # All derived from @runninggames my @allrunningpids; # &running_games_setup(); #populate the above running game specific variables my %pids; &correlate_pids(); if ($debug) { foreach my $key (keys %pids) { print "$key: $pids{$key}\n"; } } #main my $numberofargs = $#ARGV; if ($numberofargs > -1) { $arg1 = shift @ARGV; } if ($numberofargs > 0) { @selected = &set_selected(@ARGV); } else { @selected = @configs; } CASE: { if ($arg1 =~ /^start$/i) { print "Starting games\n"; &start(@selected); last CASE; } if ($arg1 =~ /^stop$/i) { print "Stopping games\n"; &stop(@selected); last CASE; } if ($arg1 =~ /^restart$/i) { print "Restarting games\n"; &stop(@selected); sleep 4; &running_games_setup(); &correlate_pids(); &start(@selected); last CASE; } if ($arg1 =~ /^cronfix$/i) { (my $brokenpids, my @brokenservers) = &whats_broke(); #find out whats broken and return a scalar of bad pids and a list of the servers that are down # my $env = `printenv`; # print "$env\n"; print "DEBUG: broken pids: $brokenpids brokenservers: @brokenservers\n" if $debug; if (@brokenservers) { my @prevbrokenservers = &read_prevbroken(); foreach my $brokenserver (@brokenservers) { if (grep /$brokenserver/, @prevbrokenservers) { print "Cron Job, automatically fixing game servers\n"; print "Listing all broken games: \n"; print " ".join("\n ", @brokenservers)."\n"; @selected = &set_selected(@brokenservers); &stop(@selected); sleep 4; &running_games_setup(); &correlate_pids(); $ENV{'PATH'} = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/usr/bin/X11:./:/usr/X11R6/bin:/usr/local/bin:/root/bin:/usr/local/bin"; &start(@selected); } else { &write_prevbroken(@brokenservers); } } } last CASE; } if ($arg1 =~ /^fix$/i) { print "Fixing games: \n"; (my $brokenpids, my @brokenservers) = &whats_broke(); #find out whats broken and return a scalar of bad pids and a list of the servers that are down print "Listing all broken games: \n"; print " ".join("\n ", @brokenservers)."\n"; @selected = &set_selected(@brokenservers); &stop(@selected); sleep 4; &running_games_setup(); &correlate_pids(); &start(@selected); last CASE; } if ($arg1 =~ /^status$/i) { print "Status of games: \n"; &status(); last CASE; } if ($arg1 =~ /^list$/i) { print "Listing all games: \n"; print " ".join("\n ", @allconfigfiles)."\n"; last CASE; } print "Usage: /etc/init.d/$scriptname {".join("|",@options)."}\n"; print " With 'start|stop|restart' you can specify specific servers from 'list'\n"; } exit 0; sub start { my @selected = @_; foreach my $config (@selected) { print "DEBUG: startpath: $config->{'startpath'}\n" if $debug; if (! $pids{$config->{'configfile'}}) { print "Starting game: $config->{'configfile'}\n"; chdir $config->{'startpath'} || &error("Not a valid start path, can't start server"); # Change directories or bitch print "DEBUG: system(\"su $gameserver_user -c '$config->{'binary'} $config->{'commonargs'} $config->{'args'}' &/dev/null &\")\n" if $debug; my $errorcode = system("su $gameserver_user -c '$config->{'binary'} $config->{'commonargs'} $config->{'args'}' >/dev/null 2>&1 &") && die "couldnt start $config->{'configfile'}: $?\n"; # Start the game! print "Return Code: $errorcode\n" if $debug; } else { &error("game is already running, choose restart"); } } } sub stop { my @selected = @_; print "DEBUG: selected: @selected[0]->{'configfile'}\n" if $debug; foreach my $config (@selected) { print "Stopping game: $config->{'configfile'}\n"; print "DEBUG: pids to be killed: $pids{$config->{'configfile'}}\n" if $debug; system("kill $pids{$config->{'configfile'}} >/dev/null 2>&1"); system("kill -9 $pids{$config->{'configfile'}} >/dev/null 2>&1"); } } sub status{ my $status = &qstat(@configs); my @statuslines = split(/\n/, $status); print " Config File Type IP/Port Server Name Map MP CP \n"; foreach my $line (@statuslines) { if ($line =~ /::/) { my @line = split /::/, $line; if (@line[$#line] =~ /[DOWN|TIMEOUT]/) { print swrite(<<'END', @line); @<<<<<<<<<<<<<<<<<<< @<< @<<<<<<<<<<<<<<< @<<<<<<<<<<<<<< **** WARNING **** END } else { print swrite(<<'END', @line); @<<<<<<<<<<<<<<<<<<< @<< @<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<< @<< @<< @<< @<< END } } else { print " $line\n"; } } } sub set_selected { my @selections = @_; my @selected; foreach my $selection (@selections) { foreach my $config (@configs) { if ($selection eq $config->{'configfile'}) { push @selected, $config; } } } print "DEBUG: selected servers: @selected\n" if $debug; return @selected; } sub configs_setup { @configs = &read_configs(); undef @unsortedallconfigfiles; # undef @allconfigfiles; # undef @allports; # All derived from @configs undef @allprocessnames; # undef @allpids; # undef %validports; # foreach my $hashref (@configs) { foreach my $key (keys %{$hashref}) { if ($key eq 'configfile') { push @unsortedallconfigfiles, $hashref->{$key}; } if ($key eq 'port') { push @allports, $hashref->{$key}; } if ($key eq 'ports') { push @{$validports{$hashref->{'processname'}}}, (split /\ /,$hashref->{$key}); } if ($key eq 'pid') { push @allpids, $hashref->{$key}; } if ($key eq 'processname') { push @allprocessnames, $hashref->{$key}; } } } if ($debug) { print "Valid Ports\n"; foreach my $process (keys %validports) { print "$process: @{$validports{$process}}\n"; } } @allconfigfiles = sort @unsortedallconfigfiles; } sub running_games_setup { @runninggames = &get_whats_running(); undef @allrunningports; # undef @allrunningprocessnames; # All derived from @runninggames undef @allrunningpids; # foreach my $hashref (@runninggames) { foreach my $key (keys %{$hashref}) { if ($key eq 'port') { push @allrunningports, $hashref->{$key}; } if ($key eq 'pid') { push @allrunningpids, $hashref->{$key}; } if ($key eq 'processname') { push @allrunningprocessnames, $hashref->{$key}; } } } } sub correlate_pids { undef %pids; foreach my $runninggame (@runninggames) { foreach my $config (@configs) { print "DEBUG: equal?: $runninggame->{'processname'} eq $config->{'processname'}) && ($runninggame->{'port'} == $config->{'port'})) { \$pids{$config->{'configfile'}} = $runninggame->{'pid'};\n" if $debug; if (($runninggame->{'processname'} eq $config->{'processname'}) && ($runninggame->{'port'} == $config->{'port'})) { $pids{$config->{'configfile'}} = $runninggame->{'pid'}; } print "DEBUG: pids for $config->{'configfile'}: $pids{$config->{'configfile'}} \n" if $debug; } } } sub get_whats_running { my $line; my @splittitles; my @games; my @cols; open (NETSTAT, "netstat -lep 2>/dev/null |"); while () { $line = $_; if ($line =~ /.*Proto.*/) { @splittitles = ($line =~ /([^ ]* name|[^ ]* Address|[^ ]*) +/g); # get all the titles of the output table (thanks to EE) #@splittitles = split /\s+/, $line; # get all the titles of the output table (old and broken) foreach my $title (@splittitles) { push @cols, (index $line, $title); # get all the starting positions for all the titles in the table } print "DEBUG: \@cols: @cols\n" if $debug; my $counter = 0; if ($debug) { foreach my $element (@splittitles) { print $counter.":".$element."\t"; $counter++; } print "\n"; } shift @cols; } if ($line =~ /.*$gameserver_user.*/){ my @splitline; my $prev = 0; foreach my $col (@cols, (length $line)) { my $temp = substr($line, $prev, $col - $prev); $temp =~ s/^\s+//; $temp =~ s/\s+$//; push @splitline, $temp; $prev = $col; } if ($debug) { my $counter = 0; foreach my $element (@splitline) { print $counter.":".$element."\t"; $counter++; } print "\n"; } my $game; (my $crap, my $port) = split /:/, $splitline[3]; (my $pid, my $processname) = split /\//, $splitline[8]; # print "DEBUG: if (grep /^$processname$/, @allprocessnames) {\n"; if (grep /^$processname$/, @allprocessnames) { $game->{'pid'} = $pid; $game->{'port'} = $port; $game->{'processname'} = $processname; push @games, $game; } } } close NETSTAT; if ($debug) { foreach my $hashref (@games) { print "DEBUG: ============================================================\n"; foreach my $key (keys %{$hashref}) { print "DEBUG: $key => $hashref->{$key}\n"; } } } return @games; } sub read_prevbroken { my @prevbroken; # array of hashrefs containing the data from all the valid config files my $line; open (PREVBROKE, "<$prevbroken_file"); while () { $line = $_; chomp $line; if ($line !~ /^#/) { push @prevbroken, $line; } } close PREVBROKE; print "DEBUG: previously broken game servers: @prevbroken\n" if $debug; return @prevbroken; } sub write_prevbroken { my @prevbroken = @_; # array of hashrefs containing the data from all the valid config files my $line; open (PREVBROKE, ">$prevbroken_file"); foreach my $brokenserver (@prevbroken) { print PREVBROKE $brokenserver."\n"; } close PREVBROKE; print "DEBUG: previously broken game servers: @prevbroken\n" if $debug; } sub read_configs { my @configfiles; # array of config file names my $configfile; # single filename my @configs; # array of hashrefs containing the data from all the valid config files my $variable; # unused variable used for split my $temp; # used for a short time before populating the final variable for that data my @temp; opendir(CONFIGDIR, "$configdir"); @configfiles = grep !/^\./, readdir CONFIGDIR; closedir CONFIGDIR; foreach $configfile (@configfiles) { my $config; # single hashref containing the data from a valid config file my @invalid; open (FILE, "$configdir/$configfile"); while () { chomp; print "DEBUG: current config line: $_\n" if $debug; ($variable, @temp) = split /=/, $_; $temp = join '=', @temp; $temp =~ s/^[\"|\']//; $temp =~ s/[\"|\']$//; SWITCH: { if ($variable =~ /^STARTPATH$/) { $config->{'startpath'} = $temp; last SWITCH; } if ($variable =~ /^BINARY$/) { $config->{'binary'} = $temp; last SWITCH; } if ($variable =~ /^PROCESSNAME$/) { $config->{'processname'} = $temp; last SWITCH; } if ($variable =~ /^PORT$/) { $config->{'port'} = $temp; last SWITCH; } if ($variable =~ /^PORTS$/) { $config->{'ports'} = $temp; last SWITCH; } if ($variable =~ /^COMMONARGS$/) { $config->{'commonargs'} = $temp; last SWITCH; } if ($variable =~ /^ARGS$/) { $config->{'args'} = $temp; last SWITCH; } if ($variable =~ /^QSTAT$/) { $config->{'qstat'} = $temp; last SWITCH; } } } $config->{'configfile'} = $configfile; print "DEBUG: configfile: $config->{'configfile'} startpath: $config->{'startpath'} binary:$config->{'binary'} processname:$config->{'processname'} ports:$config->{'ports'}\n" if $debug; if (! $config->{'startpath'}) { push @invalid, "No STARTPATH specified"; } if (! $config->{'binary'} ) { push @invalid, "No BINARY specified"; } if (! $config->{'processname'} ) { push @invalid, "No PROCESSNAME specified"; } if (! $config->{'port'} ) { push @invalid, "No PORT specified"; } if (! $config->{'ports'} ) { push @invalid, "No PORTS specified"; } if (! @invalid) { push @configs, $config; } else { &error("$configdir/$configfile is an invalid config file and will be ignored"); foreach my $invalidresponse (@invalid) { &error(" $invalidresponse"); } } } close FILE; if ($debug) { foreach my $hashref (@configs) { print "DEBUG: ============================================================\n"; foreach my $key (keys %{$hashref}) { print "DEBUG: $key => $hashref->{$key}\n"; } } } return @configs; } sub whats_broke { my $downserverpids; my @downservers; foreach my $game (@configs) { if ($game->{'qstat'}) { print "DEBUG: qstat call: $qstatbinary -raw :: -$game->{'qstat'} 127.0.0.1:$game->{'port'}\n"if $debug; my $result = &qstat($game); chomp $result; print "DEBUG: result: $result\n" if $debug; my @splitresult = split /::/, $result; if ($splitresult[$#splitresult] =~ /DOWN.*/ || $splitresult[$#splitresult] =~ /TIMEOUT.*/) { foreach my $runninggame (@runninggames) { print "DEUBG: (($game->{'port'} == $runninggame->{'port'}) && ($game->{'processname'} eq $runninggame->{'processname'}))\n" if $debug; if (($game->{'port'} == $runninggame->{'port'}) && ($game->{'processname'} eq $runninggame->{'processname'})) { $downserverpids .= $runninggame->{'pid'}." "; } } push @downservers, $game->{'configfile'}; } } } print "DEBUG: downservers: @downservers |downserverpids: $downserverpids\n" if $debug; return $downserverpids, @downservers; } sub qstat { my @selected = @_; my $result; foreach my $selection (@selected) { $result .= "$selection->{'configfile'}::"; if ($selection->{'qstat'}) { $result .= `$qstatbinary -raw :: -$selection->{'qstat'} 127.0.0.1:$selection->{'port'}`; chomp $result; } else { $result .= "This game does not support qstat for status.\n"; } } return $result; } sub swrite { my $format = shift; $^A = ""; formline($format,@_); return $^A; } sub error { my $error = shift; print "ERROR: $error\n"; }