#!/usr/bin/perl ###################################################################### # LegacyWorlds Beta 5 # System control script # # Without arguments: # should run as root; creates a FIFO from which it reads commands. # # With arguments: # writes arguments to the pipe ###################################################################### ### ## Configuration # $fifoPath = "/tmp/.lwFifo"; $ctrlPath = "/tmp/.lwControl"; $fsUser = 0; $fsGroup = 33; ### ## Main code below # use IO::File; require POSIX; # If we have arguments, write to the FIFO and exit if (@ARGV > 0) { if ($ARGV[0] eq '--start') { &start(); } elsif ($ARGV[0] eq '--stop') { &stop(); } else { &sendMessage(@ARGV); exit(0); } } # Find the script's path use FindBin qw($Bin); if (! -f "$Bin/legacyworlds.xml") { die "$0: could not find 'legacyworlds.xml' in '$Bin'\n"; } $lwConf = "$Bin/legacyworlds.xml"; # Fork and detach $pid = fork(); if ($pid < 0) { die("$0: failed to fork\n"); } elsif ($pid != 0) { exit(0); } # Detach POSIX::setsid(); close STDIN; close STDOUT; close STDERR; open(STDIN, "/dev/null"); open(STDERR, ">/dev/null"); # First create the pipe if it doesn't exist if (! -p $fifoPath) { if (-e $fifoPath) { die "$0: '$fifoPath' is not a pipe\n"; } else { POSIX::mkfifo($fifoPath, 0400) or die "$0: unable to create '$fifoPath'\n"; } } # Set the pipe's owner and group if ($> == 0) { chown $fsUser, $fsGroup, $fifoPath; } else { chown $>, $fsGroup, $fifoPath; } chmod 0620, $fifoPath; # Create the control directory if needed if (! -d $ctrlPath) { if (-e $ctrlPath) { die "$0: '$ctrlPath' is not a directory\n"; } else { mkdir $ctrlPath, 0700 or die "$0: unable to create '$ctrlPath'\n"; } } # Set the owner and group if ($> == 0) { chown $fsUser, $fsGroup, $ctrlPath; } else { chown $>, $fsGroup, $ctrlPath; } chmod 0770, $ctrlPath; # Define commands %commands = ( DIE => \&endController, MERGE => \&mergeConfiguration, TMPID => \&tickManagerID, TMINIT => \&tickManagerStart, TMSTOP => \&tickManagerStop, READY => \&gameReady, START => \&gameStart, SETEND => \&gameEnd, NOEND => \&gameCancelEnd, "END" => \&gameChangeEnd, SETDEF => \&setDefaultGame, BOTON => \&startBot, BOTOFF => \&killBot, PCPID => \&proxyDetectorID, PCON => \&startProxyDetector, PCOFF => \&stopProxyDetector ); # Reader loop while (1) { # Wait for someone to write to the pipe close(FIFO); open(FIFO, "< $fifoPath") or die "$0: unable to open '$fifoPath'\n"; # Read it $command = ; next unless defined $command; chomp($command); next if $command =~ /[^A-Za-z0-9\s]/; # Extract the actual command my @args = split /\s+/, $command; $command = shift @args; # Check if the command is allowed next unless defined $commands{$command}; #print "Got command $command, arguments = (" . join(', ', @args) . ")\n"; &{$commands{$command}}(@args); } ### ## Helper functions # sub sendMessage { my @args = @_; die "$0: FIFO '$fifoPath' doesn't exist\n" unless -p $fifoPath; open(FIFO, "> $fifoPath") or die "$0: unable to open FIFO '$fifoPath'\n"; print FIFO (join(' ', @args) . "\n"); close(FIFO); } sub start { $pid = fork(); if ($pid == -1) { die "$0: could not fork\n"; } elsif ($pid) { print "LegacyWorlds - Initialising game\n"; print " -> Controller\n"; sleep(1); print " -> Proxy detector\n"; &sendMessage("PCON"); sleep(1); print " -> Ticks manager\n"; &sendMessage("TMINIT"); sleep(1); print " -> IRC bot\n"; &sendMessage("BOTON"); exit(0); } } sub stop { print "LegacyWolrds - Shutting down\n"; print " -> IRC bot\n"; &sendMessage("BOTOFF"); sleep(1); print " -> Ticks manager\n"; &sendMessage("TMSTOP"); sleep(1); print " -> Proxy detector\n"; &sendMessage("PCOFF"); sleep(1); print " -> Controller\n"; &sendMessage("DIE"); exit(0); } ### ## Command functions # # # Function that terminates the controller # sub endController { exit(0); } # # Function that adds a new game # sub mergeConfiguration { my $sourceFile = shift; return unless -f "$ctrlPath/config.$sourceFile.xml"; # Read the new snippet open(NEWCONF, "< $ctrlPath/config.$sourceFile.xml"); my @newConfiguration = ; close(NEWCONF); # Read the old configuration open(OLDCONF, "< $lwConf"); my @oldConfiguration = ; close(OLDCONF); # Merge it my @merged = (); foreach my $oldLine (@oldConfiguration) { if ($oldLine =~ /^\s+<\/Games>\s*$/) { @merged = (@merged, @newConfiguration, "\n"); } @merged = (@merged, $oldLine); } # Write the new configuration file open(OLDCONF, "> $lwConf"); print OLDCONF @merged; close OLDCONF; # Remove the source file unlink "$ctrlPath/config.$sourceFile.xml"; } # # Tick manager PID update # sub tickManagerID { my $pid = shift; return unless $pid; open(PIDFILE, "> $ctrlPath/tickManager.pid"); print PIDFILE "$pid " . time() . "\n"; close(PIDFILE); } # # Start tick manager # sub tickManagerStart { return if &tickManagerStatus(); return unless -f "$Bin/ticks.php"; if ($> == 0) { system("su - lwticks"); } else { system("cd $Bin; php ticks.php"); } } # # Stop tick manager # sub tickManagerStop { my $pid; return unless ($pid = &tickManagerStatus()); kill 15, $pid; unlink("$ctrlPath/tickManager.pid"); } # # Start IRC bot # sub startBot { &killBot(); if ($> == 0) { system("su - lwbot"); } else { system("cd $Bin/../ircbot; (php bot.php &) /dev/null 2>&1"); } } # # Stop IRC bot # sub killBot { return unless -f "$ctrlPath/ircbot.pid"; my $pid = `cat $ctrlPath/ircbot.pid`; kill 15, $pid; unlink("$ctrlPath/ircbot.pid"); } # # Check tick manager status # sub tickManagerStatus { return 0 unless -f "$ctrlPath/tickManager.pid"; open(PIDFILE, "< $ctrlPath/tickManager.pid"); my $data = ; close(PIDFILE); chomp($data); my ($pid, $time) = split / /, $data; return (time() - $time < 22 ? $pid : 0); } # # Make a game ready # sub gameReady { my $gName = shift; # Read the current configuration open(OLDCONF, "< $lwConf"); my @oldConfiguration = ; close(OLDCONF); # Generate new configuration my @newConf = (); foreach my $line (@oldConfiguration) { if ($line =~ / $lwConf"); print NEWCONF @newConf; close(NEWCONF); } # # Start the game earlier or later # sub gameStart { my $gName = shift; my $when = shift; return if ($when ne "EARLY" && $when ne "LATE"); $when = ($when eq 'EARLY') ? -1 : 1; $when *= 24 * 60 * 60; # Read the current configuration open(OLDCONF, "< $lwConf"); my @oldConfiguration = ; close(OLDCONF); # Generate new configuration my @newConf = (); my $state = 0; foreach my $line (@oldConfiguration) { if ($state == 0 && $line =~ //) { $state = 0; } elsif ($line =~ //) { my $fTick = $1; $fTick += $when; $line =~ s/\sfirst="[0-9]+"/ first="$fTick"/; } } push @newConf, $line; } # Write configuration file open(NEWCONF, "> $lwConf"); print NEWCONF @newConf; close(NEWCONF); } # # Set a running game to end # sub gameEnd { my $gName = shift; my $when = shift; $when = $when * 60 * 60 + time(); # Read the current configuration open(OLDCONF, "< $lwConf"); my @oldConfiguration = ; close(OLDCONF); # Generate new configuration my @newConf = (); my $state = 0; foreach my $line (@oldConfiguration) { if ($state == 0 && $line =~ //) { $state = 0; } elsif ($line =~ //) { my $fTick = $1; $line =~ s/\sfirst="[0-9]+"/ first="$fTick" last="$when"/; } } push @newConf, $line; } # Write configuration file open(NEWCONF, "> $lwConf"); print NEWCONF @newConf; close(NEWCONF); } # # Set an ending game's status back to running/victory # sub gameCancelEnd { my $gName = shift; # Read the current configuration open(OLDCONF, "< $lwConf"); my @oldConfiguration = ; close(OLDCONF); # Generate new configuration my @newConf = (); my $state = 0; foreach my $line (@oldConfiguration) { if ($state == 0 && $line =~ //) { $state = 0; } elsif ($line =~ / $lwConf"); print NEWCONF @newConf; close(NEWCONF); } # # End the game earlier or later # sub gameChangeEnd { my $gName = shift; my $when = shift; return if ($when ne "EARLY" && $when ne "LATE" && $when ne "NOW"); if ($when ne 'NOW') { $when = ($when eq 'EARLY') ? -1 : 1; $when *= 24 * 60 * 60; } # Read the current configuration open(OLDCONF, "< $lwConf"); my @oldConfiguration = ; close(OLDCONF); # Generate new configuration my @newConf = (); my $state = 0; foreach my $line (@oldConfiguration) { if ($state == 0 && $line =~ //) { $state = 0; } elsif ($line =~ / $lwConf"); print NEWCONF @newConf; close(NEWCONF); } # # Changes the default game # sub setDefaultGame { my $gName = shift; return if ($gName eq ""); # Read the current configuration open(OLDCONF, "< $lwConf"); my @oldConfiguration = ; close(OLDCONF); # Generate new configuration my @newConf = (); foreach my $line (@oldConfiguration) { if ($line =~ / $lwConf"); print NEWCONF @newConf; close(NEWCONF); } # # Proxy detector PID update # sub proxyDetectorID { my $pid = shift; return unless $pid; open(PIDFILE, "> $ctrlPath/proxyDetector.pid"); print PIDFILE "$pid " . time() . "\n"; close(PIDFILE); } # # Start proxy detector # sub startProxyDetector { return if &proxyDetectorStatus(); return unless -f "$Bin/proxycheck.php"; if ($> == 0) { system("su - lwproxy"); } else { system("cd $Bin; php proxycheck.php"); } } # # Stop proxy detector # sub stopProxyDetector { my $pid; return unless ($pid = &proxyDetectorStatus()); kill 15, $pid; unlink("$ctrlPath/proxyDetector.pid"); } # # Check proxy detector status # sub proxyDetectorStatus { return 0 unless -f "$ctrlPath/proxyDetector.pid"; open(PIDFILE, "< $ctrlPath/proxyDetector.pid"); my $data = ; close(PIDFILE); chomp($data); my ($pid, $time) = split / /, $data; return (time() - $time < 22 ? $pid : 0); }