Advertisement
Guest User

Perl Script

a guest
Feb 2nd, 2017
734
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Perl 6 14.71 KB | None | 0 0
  1. #!/usr/bin/perl
  2. #
  3. #   This file is part of PsychoStats.
  4. #
  5. #   Written by Jason Morriss <stormtrooper@psychostats.com>
  6. #   Copyright 2008 Jason Morriss
  7. #
  8. #   PsychoStats is free software: you can redistribute it and/or modify
  9. #   it under the terms of the GNU General Public License as published by
  10. #   the Free Software Foundation, either version 3 of the License, or
  11. #   (at your option) any later version.
  12. #
  13. #   PsychoStats is distributed in the hope that it will be useful,
  14. #   but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16. #   GNU General Public License for more details.
  17. #
  18. #   You should have received a copy of the GNU General Public License
  19. #   along with PsychoStats.  If not, see <http://www.gnu.org/licenses/>.
  20. #
  21. #   $Id: stats.pl 564 2008-10-10 12:26:35Z lifo $
  22. #
  23.  
  24. BEGIN { # FindBin isn't going to work on systems that run the stats.pl as SETUID
  25.     use strict;
  26.     use warnings;
  27.  
  28.     use FindBin;
  29.     use lib $FindBin::Bin;
  30.     use lib $FindBin::Bin . "/lib";
  31. }
  32.  
  33. BEGIN { # make sure we're running the minimum version of perl required
  34.     my $minver = 5.08;
  35.     my $curver = 0.0;
  36.     my ($major,$minor,$release) = split(/\./,sprintf("%vd", $^V));
  37.     $curver = sprintf("%d.%02d",$major,$minor);
  38.     if ($curver < $minver) {
  39.         print "Perl v$major.$minor.$release is too old to run PsychoStats.\n";
  40.         print "Minimum version $minver is required. You must upgrade before continuing.\n";
  41.         if (lc substr($^O,0,-2) eq "mswin") {
  42.             print "\nPress ^C or <enter> to exit.\n";
  43.             <>;
  44.         }
  45.         exit 1;
  46.     }
  47. }
  48.  
  49. BEGIN { # do checks for required modules
  50.     our %PM_LOADED = ();
  51.     my @modules = qw( DBI DBD::mysql );
  52.     my @failed_at_life = ();
  53.     my %bad_kitty = ();
  54.     foreach my $module (@modules) {
  55.         my $V = '';
  56.         eval "use $module; \$V = \$${module}::VERSION;";
  57.         if ($@) {   # module not found
  58.             push(@failed_at_life, $module);
  59.         } else {    # module loaded ok; store for later, if -V is used for debugging purposes
  60.             $PM_LOADED{$module} = $V;
  61.         }
  62.     }
  63.  
  64.     # check the version of modules
  65.     # DBD::mysql needs to be 3.x at a minimum
  66.     if ($PM_LOADED{'DBD::mysql'} and substr($PM_LOADED{'DBD::mysql'},0,1) lt '3') {
  67.         $bad_kitty{'DBD::mysql'} = '3.0008';
  68.     }
  69.  
  70.     # if anything failed, kill ourselves, life isn't worth living.
  71.     if (@failed_at_life or scalar keys %bad_kitty) {
  72.         print "PsychoStats failed initialization!\n";
  73.         if (@failed_at_life) {
  74.             print "The following modules are required and could not be loaded.\n";
  75.             print "\t" . join("\n\t", @failed_at_life) . "\n";
  76.             print "\n";
  77.         } else {
  78.             print "The following modules need to be upgraded to the version shown below\n";
  79.             print "\t$_ v$bad_kitty{$_} or newer (currently installed: $PM_LOADED{$_})\n" for keys %bad_kitty;
  80.             print "\n";
  81.         }
  82.  
  83.         if (lc substr($^O,0,-2) eq "mswin") {   # WINDOWS
  84.             print "You can install the modules listed by using the Perl Package Manager.\n";
  85.             print "Typing 'ppm' at the Start->Run menu usually will open it up. Enter the module\n";
  86.             print "name and have it install. Then rerun PsychoStats.\n";
  87.             print "\nPress ^C or <enter> to exit.\n";
  88.             <>;
  89.         } else {                # LINUX
  90.             print "You can install the modules listed using either CPAN or if your distro\n";
  91.             print "supports it by installing a binary package with your package manager like\n";
  92.             print "'yum' (fedora / redhat), 'apt-get' or 'aptitude' (debian).\n";
  93.         }
  94.         exit 1;
  95.     }
  96. }
  97.  
  98. use POSIX qw( :sys_wait_h setsid );
  99. use File::Spec::Functions qw(catfile);
  100. use PS::CmdLine;
  101. use PS::DB;
  102. use PS::Config;                 # use'd here only for the loadfile() function
  103. use PS::ConfigHandler;
  104. use PS::ErrLog;
  105. use PS::Feeder;
  106. use PS::Game;
  107. use util qw( :win compacttime );
  108.  
  109. # The $VERSION and $PACKAGE_DATE are automatically updated via the packaging script.
  110. our $VERSION = '3.2';
  111. our $PACKAGE_DATE = time;
  112. our $REVISION = ('$Rev: 564 $' =~ /(\d+)/)[0] || '000';
  113.  
  114. our $DEBUG = 0;                 # Global DEBUG level
  115. our $DEBUGFILE = undef;             # Global debug file to write debug info too
  116. our $ERR;                   # Global Error handler (PS::Debug uses this)
  117. our $DBCONF = {};               # Global database config
  118. our $GRACEFUL_EXIT = 0; #-1;            # (used in CATCH_CONTROL_C)
  119.  
  120. $SIG{INT} = \&CATCH_CONTROL_C;
  121.  
  122. my ($opt, $dbconf, $db, $conf);
  123. my $starttime = time;
  124. my $total_logs = 0;
  125. my $total_lines = 0;
  126.  
  127. eval { binmode(STDOUT, ":encoding(utf8)"); };
  128.  
  129. $opt = new PS::CmdLine;             # Initialize command line paramaters
  130. $DEBUG = $opt->get('debug') || 0;       # sets global debugging for ALL CLASSES
  131.  
  132. # display our version and exit
  133. if ($opt->get('version')) {
  134.     print "PsychoStats version $VERSION (rev $REVISION)\n";
  135.     print "Packaged on " . scalar(localtime $PACKAGE_DATE) . "\n";
  136. #   print "Author:  Jason Morriss <stormtrooper\@psychostats.com>\n";
  137.     print "Website: http://www.psychostats.com/\n";
  138.     print "Perl version " . sprintf("%vd", $^V) . " ($^O)\n";
  139.     print "Loaded Modules:\n";
  140.     my $len = 1;
  141.     foreach my $pm (keys %PM_LOADED) {  # get max length first, so we can be pretty
  142.         $len = length($pm) if length($pm) > $len;
  143.     }
  144.     $len += 2;
  145.     foreach my $pm (keys %PM_LOADED) {
  146.         printf("  %-${len}sv%s\n", $pm, $PM_LOADED{$pm});
  147.     }
  148.     exit;
  149. }
  150.  
  151. if (defined(my $df = $opt->get('debugfile'))) {
  152.     $df = 'debug.txt' unless $df;       # if filename is empty
  153.     $DEBUGFILE = $df;
  154.     $DEBUG = 1 unless $DEBUG;       # force DEBUG on if we're specifying a file
  155.     $opt->debug("DEBUG START: " . scalar(localtime) . " (level $DEBUG) File: $DEBUGFILE");
  156. }
  157.  
  158. # Load the basic stats.cfg for database settings (unless 'noconfig' is specified on the command line)
  159. # The config filename can be specified on the commandline, otherwise stats.cfg is used. If that file
  160. # does not exist then the config is loaded from the __DATA__ block of this file.
  161. $dbconf = {};
  162. if (!$opt->get('noconfig')) {
  163.     if ($opt->get('config')) {
  164.         PS::Debug->debug("Loading DB config from " . $opt->get('config'));
  165.         $dbconf = PS::Config->loadfile( $opt->get('config') );
  166.     } elsif (-e catfile($FindBin::Bin, 'stats.cfg')) {
  167.         PS::Debug->debug("Loading DB config from stats.cfg");
  168.         $dbconf = PS::Config->loadfile( catfile($FindBin::Bin, 'stats.cfg') );
  169.     } else {
  170.         PS::Debug->debug("Loading DB config from __DATA__");
  171.         $dbconf = PS::Config->loadfile( *DATA );
  172.     }
  173. } else {
  174.     PS::Debug->debug("-noconfig specified, No DB config loaded.");
  175. }
  176.  
  177. # Initialize the primary Database object
  178. # Allow command line options to override settings loaded from config
  179. $DBCONF = {
  180.     dbtype      => $opt->dbtype || $dbconf->{dbtype},
  181.     dbhost      => $opt->dbhost || $dbconf->{dbhost},
  182.     dbport      => $opt->dbport || $dbconf->{dbport},
  183.     dbname      => $opt->dbname || $dbconf->{dbname},
  184.     dbuser      => $opt->dbuser || $dbconf->{dbuser},
  185.     dbpass      => $opt->dbpass || $dbconf->{dbpass},
  186.     dbtblprefix => $opt->dbtblprefix || $dbconf->{dbtblprefix},
  187.     dbcompress  => $opt->dbcompress || $dbconf->{dbcompress}
  188. };
  189. $db = new PS::DB($DBCONF);
  190.  
  191. $conf = new PS::ConfigHandler($opt, $db);
  192. my $total = $conf->load(qw( main ));
  193. $ERR = new PS::ErrLog($conf, $db);          # Now all error messages will be logged to the DB
  194.  
  195. $db->init_tablenames($conf);
  196. $db->init_database;
  197.  
  198. # if a gametype was specified update the config
  199. my $confupdated = 0;
  200. if (defined $opt->get('gametype') and $conf->getconf('gametype','main') ne $opt->get('gametype')) {
  201.     my $old = $conf->getconf('gametype', 'main');
  202.     $db->update($db->{t_config}, { value => $opt->get('gametype') }, [ conftype => 'main', section => undef, var => 'gametype' ]);
  203.     $conf->set('gametype', $opt->get('gametype'), 'main');
  204.     $ERR->info("Changing gametype from '$old' to '" . $conf->getconf('gametype') . "' (per command line)");
  205.     $confupdated = 1;
  206. }
  207.  
  208. # if a modtype was specified update the config
  209. if (defined $opt->get('modtype') and $conf->getconf('modtype','main') ne $opt->get('modtype')) {
  210.     my $old = $conf->getconf('modtype', 'main');
  211.     $db->update($db->{t_config}, { value => $opt->get('modtype') }, [ conftype => 'main', section => undef, var => 'modtype' ]);
  212.     $conf->set('modtype', $opt->get('modtype'), 'main');
  213.     $ERR->info("Changing modtype from '$old' to '" . $conf->getconf('modtype') . "' (per command line)");
  214.     $confupdated = 1;
  215. }
  216.  
  217. # reinitialize the tables if the config was updated above...
  218. if ($confupdated) {
  219.     $db->init_tablenames($conf);
  220.     $db->init_database;
  221. }
  222.  
  223. # handle a 'stats reset' request
  224. if (defined $opt->get('reset')) {
  225.     my $game = new PS::Game($conf, $db);
  226.     my $res = $opt->get('reset');
  227.     my $all = (index($opt->get('reset'),'all') >= 0);
  228.     my %del = (
  229.         players     => ($all || (index($res,'player') >= 0)),
  230.         clans       => ($all || (index($res,'clan') >= 0)),
  231.         weapons     => ($all || (index($res,'weapon') >= 0)),
  232.         heatmaps    => ($all || (index($res,'heat') >= 0)),
  233.     );
  234.     $game->reset(%del);
  235.     &main::exit;
  236. }
  237.  
  238. $ERR->debug2("$total config settings loaded.");
  239. $ERR->fatal("No 'gametype' configured.") unless $conf->get_main('gametype');
  240. $ERR->info("PsychoStats v$VERSION initialized.");
  241.  
  242. # if -unknown is specified, temporarily enable report_unknown
  243. if ($opt->get('unknown')) {
  244.     $conf->set('errlog.report_unknown', 1, 'main');
  245. }
  246.  
  247. # ------------------------------------------------------------------------------
  248. # rescan clantags
  249. if (defined $opt->get('scanclantags')) {
  250.     my $game = new PS::Game($conf, $db);
  251.     my $all = lc $opt->get('scanclantags') eq 'all' ? 1 : 0;
  252.     $::ERR->info("Rescanning clantags for ranked players.");
  253.     if ($all) {
  254.         $::ERR->info("Removing ALL player to clan relationships.");
  255.         $::ERR->info("All clans will be deleted except profiles.");
  256.         $game->delete_clans(0);
  257.     }
  258.  
  259.     $game->rescan_clans;
  260.  
  261.     # force a daily 'clans' update to verify what clans rank
  262.     $opt->set('daily', ($opt->get('daily') || '') . ',clans');
  263. }
  264.  
  265. # ------------------------------------------------------------------------------
  266. # PERFORM DAILY OPERATIONS and exit if we did any (no logs should be processed)
  267. if ($opt->get('daily')) {
  268.     &main::exit if do_daily($opt->get('daily'));
  269. }
  270.  
  271. # ------------------------------------------------------------------------------
  272. # process log sources ... the endless while loop is a placeholder.
  273. my $more_logs = !$opt->get('nologs');
  274. while ($more_logs) { # infinite loop
  275.     my $logsource = load_logsources();
  276.     if (!defined $logsource or @$logsource == 0) {
  277.         $ERR->fatal("No log sources defined! You must configure a log source (or use -log on command line)!");
  278.     }
  279.  
  280.     my @total;
  281.     my $game = new PS::Game($conf, $db);
  282.     foreach my $source (@$logsource) {
  283.         my $feeder = new PS::Feeder($source, $game, $conf, $db);
  284.         next unless $feeder;
  285.  
  286.         # Let Feeder initialize (read directories, establish remote connections, etc).
  287.         my $type = $feeder->init;   # 1=wait; 0=error; -1=nowait;
  288.         next unless $type;      # ERROR
  289.  
  290.         $conf->setinfo('stats.lastupdate', time) unless $conf->get_info('stats.lastupdate');
  291.         @total = $game->process_feed($feeder);
  292.         $total_logs  += $total[0];
  293.         $total_lines += $total[1];
  294.         $conf->setinfo('stats.lastupdate', time);
  295.         $feeder->done;
  296.  
  297.         last if $GRACEFUL_EXIT > 0;
  298.     }
  299.     &main::exit if $GRACEFUL_EXIT > 0;
  300.  
  301.     last;
  302. }
  303.  
  304. # check to make sure we don't need to do any daily updates before we exit
  305. check_daily($conf) unless $opt->get('nodaily');
  306.  
  307. END {
  308.     $ERR->info("PsychoStats v$VERSION exiting (elapsed: " . compacttime(time-$starttime) . ", logs: $total_logs, lines: $total_lines)") if defined $ERR;
  309.     $opt->debug("DEBUG END: " . scalar(localtime) . " (level $DEBUG) File: $DEBUGFILE") if $DEBUGFILE and defined $opt;
  310. }
  311.  
  312. # ------- FUNCTIONS ------------------------------------------------------------
  313.  
  314. # returns a list of log sources
  315. sub load_logsources {
  316.     my $list = [];
  317.     if ($opt->get('logsource')) {
  318.         my $game = new PS::Game($conf, $db);
  319.         my $log = new PS::Feeder($opt->get('logsource'), $game, $conf, $db);
  320.         if (!$log) {
  321.             $ERR->fatal("Error loading logsource from command line.");
  322.         }
  323.         push(@$list, $log->{logsource});
  324.     } else {
  325.         $list = $db->get_rows_hash("SELECT * FROM $db->{t_config_logsources} WHERE enabled=1 ORDER BY idx");
  326.     }
  327.     return wantarray ? @$list : [ @$list ];
  328. }
  329.  
  330. # do daily updates, if needed
  331. sub check_daily {
  332.     my ($conf) = @_;
  333.     my @dodaily = ();
  334.     do_daily(join(',', @PS::Game::DAILY));
  335. }
  336.  
  337. sub do_daily {
  338.     my ($daily) = @_;
  339.     $daily = lc $opt->get('daily') unless defined $daily;
  340.     return 0 unless $daily;
  341.  
  342.     my %valid = map { $_ => 0 } @PS::Game::DAILY;
  343.     my @badlist = ();
  344.     foreach (split(/,/, $daily)) {
  345.         if (exists $valid{$_}) {
  346.             $valid{$_}++
  347.         } else {
  348.             push(@badlist, $_) if $_ ne '';
  349.         }
  350.     }
  351.     $ERR->warn("Ignoring invalid daily options: " . join(',', map { "'$_'" } @badlist)) if @badlist;
  352.     $daily = join(',', $valid{all} ? @PS::Game::DAILY[1..$#PS::Game::DAILY] : grep { $valid{$_} } @PS::Game::DAILY);
  353.  
  354.     if (!$daily) {
  355.         $ERR->fatal("-daily was specified with no valid options. Must have at least one of the following: " . join(',', @PS::Game::DAILY), 1);
  356.     }
  357.     $ERR->info("Daily updates about to be performed: $daily");
  358.  
  359.     my $game = new PS::Game($conf, $db);
  360.     foreach (split(/,/, $daily)) {
  361.         my $func = "daily_" . $_;
  362.         if ($game->can($func)) {
  363.             $game->$func;
  364.         } else {
  365.             $ERR->warn("Ignoring daily update '$_': No game support");
  366.         }
  367.     }
  368.  
  369.     return 1;
  370. }
  371.  
  372. sub run_as_daemon {
  373.     my ($pid_file) = @_;
  374.     defined(my $pid = fork) or die "Can't fork process: $!";
  375.     exit if $pid;   # the parent exits
  376.  
  377.     # 1st generation child
  378.     open(STDIN, '/dev/null');
  379.     open(STDOUT, '>>/dev/null') unless $DEBUG;
  380.     open(STDERR, '>>/dev/null') unless $DEBUG;
  381.     chdir('/');     # run from root so we don't lock other potential mounts or directories
  382.     setsid();       # POSIX; sets us as the process leader (our parent PID is 1)
  383.     umask(0);
  384.  
  385.     # 2nd generation child (for SysV; avoids re-acquiring a controlling terminal)
  386.     # setsid() needs to be done before this, see above.
  387.     defined($pid = fork) or die "Can't fork sub-process: $!";
  388.     exit if $pid;
  389.     # now we're no longer the process leader but are in process group 1.
  390.  
  391.     if ($pid_file) {
  392.         open(F, ">$pid_file") or warn("Can not write PID $$ to file: $pid_file: $!\n");
  393.         print F $$;
  394.         close(F);
  395.         chmod 0644, $pid_file;
  396.     }
  397. }
  398.  
  399. # PS::ErrLog points to this to actually exit on a fatal error, incase I need to do some cleanup
  400. sub main::exit {
  401. #   <> if iswindows();
  402.     CORE::exit(@_)
  403. }
  404.  
  405. sub CATCH_CONTROL_C {
  406.     $GRACEFUL_EXIT++;
  407.     if ($GRACEFUL_EXIT == 0) {      # WONT HAPPEN (GRACEFUL_EXIT defaults to 0 now)
  408.         if ($opt->get('daemon')) {
  409.                 $GRACEFUL_EXIT++;
  410.             goto C_HERE;
  411.         }
  412.         syswrite(STDERR, "Caught ^C -- Are you sure? One more will attempt a gracefull exit.\n");
  413.     } elsif ($GRACEFUL_EXIT == 1) {
  414. C_HERE:
  415.         syswrite(STDERR, "Caught ^C -- Please wait while I try to exit gracefully.\n");
  416.     } else {
  417.         syswrite(STDERR, "Caught ^C -- Alright! I'm done!!! (some data may have been lost)\n");
  418.         &main::exit;
  419.     }
  420.     $SIG{INT} = \&CATCH_CONTROL_C;
  421. }
  422.  
  423. __DATA__
  424.  
  425. # If no stats.cfg exists then this config is loaded instead
  426.  
  427. dbtype = mysql
  428. dbhost = localhost
  429. dbport =
  430. dbname =
  431. dbuser =
  432. dbpass =
  433. dbtblprefix = ps_
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement