441 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
		
		
			
		
	
	
			441 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
| 
								 | 
							
								#!/usr/bin/perl
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Danga's Statistics Gatherer
							 | 
						||
| 
								 | 
							
								# Gathers statistics using plugins.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Command line options:
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#    --conf STRING    set what config file to use for options
							 | 
						||
| 
								 | 
							
								#    --server STRING  set what server to point at
							 | 
						||
| 
								 | 
							
								#    --port INT       the port of the server to use
							 | 
						||
| 
								 | 
							
								#    --foreground     if present, tell the server to force to the foreground
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Configuration file format:
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#    server = STRING          location of statistics server
							 | 
						||
| 
								 | 
							
								#    port = INT               port number server listens on
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#    job: plugin(parameter, parameter)
							 | 
						||
| 
								 | 
							
								#    job2: plugin
							 | 
						||
| 
								 | 
							
								#    helperplugin(parameter, parameter)
							 | 
						||
| 
								 | 
							
								#    ...
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Copyright 2004, Danga Interactive
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Authors:
							 | 
						||
| 
								 | 
							
								#   Mark Smith <marksmith@danga.com>
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# License:
							 | 
						||
| 
								 | 
							
								#   undecided.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use strict;
							 | 
						||
| 
								 | 
							
								use lib "$ENV{LJHOME}/cgi-bin";
							 | 
						||
| 
								 | 
							
								use Getopt::Long;
							 | 
						||
| 
								 | 
							
								use POSIX qw(:sys_wait_h);
							 | 
						||
| 
								 | 
							
								use Danga::Daemon;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# config
							 | 
						||
| 
								 | 
							
								my $path = "$ENV{LJHOME}/bin/spud";
							 | 
						||
| 
								 | 
							
								my $plugins_path = "$path/plugins";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# command line config
							 | 
						||
| 
								 | 
							
								my %opts;
							 | 
						||
| 
								 | 
							
								GetOptions \%opts, qw/ conf=s server=s port=i /;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# reverse mapping; ( pid => job-name )
							 | 
						||
| 
								 | 
							
								my %pids;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# mapping of what we expect to die soon
							 | 
						||
| 
								 | 
							
								my %todie;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# called if we die
							 | 
						||
| 
								 | 
							
								sub fail {
							 | 
						||
| 
								 | 
							
								    foreach my $pid (keys %pids) {
							 | 
						||
| 
								 | 
							
								        kill 15, $pid;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    $_[0] ||= 'no reason provided';
							 | 
						||
| 
								 | 
							
								    die "shutdown detected: $_[0]\n";
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# now daemonize
							 | 
						||
| 
								 | 
							
								Danga::Daemon::daemonize(
							 | 
						||
| 
								 | 
							
								    \&worker,
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        interval => 1,
							 | 
						||
| 
								 | 
							
								        listenport => 13500,
							 | 
						||
| 
								 | 
							
								        chdir => $path,
							 | 
						||
| 
								 | 
							
								        shutdowncode => \&fail,
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# ( plugin-name =>
							 | 
						||
| 
								 | 
							
								#    {
							 | 
						||
| 
								 | 
							
								#       modtime => int, # last modified time of plugin file (for reload detection)
							 | 
						||
| 
								 | 
							
								#       children => [ pid, pid, pid, ... ], # pids of workers doing this plugin
							 | 
						||
| 
								 | 
							
								#       file => str, # filename
							 | 
						||
| 
								 | 
							
								#       reload => bool, # if on, reload_plugins() will reload this plugin
							 | 
						||
| 
								 | 
							
								#       registered => bool, # if on, this plugin has registered correctly and is good to go
							 | 
						||
| 
								 | 
							
								#       code => {
							 | 
						||
| 
								 | 
							
								#           register => sub { ... },
							 | 
						||
| 
								 | 
							
								#           worker => sub { ... },
							 | 
						||
| 
								 | 
							
								#       },
							 | 
						||
| 
								 | 
							
								#    }
							 | 
						||
| 
								 | 
							
								# );
							 | 
						||
| 
								 | 
							
								my %plugins;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# ( job-name =>
							 | 
						||
| 
								 | 
							
								#    {
							 | 
						||
| 
								 | 
							
								#       plugin => plugin-name,
							 | 
						||
| 
								 | 
							
								#       respawn_with => plugin-name,
							 | 
						||
| 
								 | 
							
								#       pid => pid,
							 | 
						||
| 
								 | 
							
								#       options => [ option, option, option, ... ], # list of options from config file
							 | 
						||
| 
								 | 
							
								#       reload => bool, # if on, reload_plugins() will reload this job
							 | 
						||
| 
								 | 
							
								#       active => bool, # if on, this is active per the config file
							 | 
						||
| 
								 | 
							
								#    }
							 | 
						||
| 
								 | 
							
								# );
							 | 
						||
| 
								 | 
							
								my %jobs;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# array of plugins that want to help us out; array of [ pluginname, [ opts ] ]
							 | 
						||
| 
								 | 
							
								my @helpers;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# cached socket to our stat server
							 | 
						||
| 
								 | 
							
								my $sock;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								sub set {
							 | 
						||
| 
								 | 
							
								    # sends a statistic up to the stat server
							 | 
						||
| 
								 | 
							
								    $sock ||= IO::Socket::INET->new(PeerAddr => $opts{server},
							 | 
						||
| 
								 | 
							
								                                    PeerPort => $opts{port},);
							 | 
						||
| 
								 | 
							
								    fail("can't create socket: $!")
							 | 
						||
| 
								 | 
							
								        unless $sock;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # send this on up to the server
							 | 
						||
| 
								 | 
							
								    while (scalar(@_) >= 2) {
							 | 
						||
| 
								 | 
							
								        $sock->print("QSET $_[0] $_[1]\r\n");
							 | 
						||
| 
								 | 
							
								        shift @_; shift @_;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# keeps track of whether or not this is the first worker run
							 | 
						||
| 
								 | 
							
								sub worker {
							 | 
						||
| 
								 | 
							
								    # step 1: check through plugins directory to see what plugins have changed (or are new)
							 | 
						||
| 
								 | 
							
								    check_plugin_modtimes();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # step 2: check for a changed config file and load/reload as necessary
							 | 
						||
| 
								 | 
							
								    check_config();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # step 3: now that we know exactly what's changed, call master reloader
							 | 
						||
| 
								 | 
							
								    reload();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # step 4: run through any defined helpers so they can, well, help
							 | 
						||
| 
								 | 
							
								    foreach my $helpref (@helpers) {
							 | 
						||
| 
								 | 
							
								        my ($plugin, $opts) = @$helpref;
							 | 
						||
| 
								 | 
							
								        next unless $plugins{$plugin} && $plugins{$plugin}->{registered} &&
							 | 
						||
| 
								 | 
							
								                    $plugins{$plugin}->{code}->{helper};
							 | 
						||
| 
								 | 
							
								        eval {
							 | 
						||
| 
								 | 
							
								            $plugins{$plugin}->{code}->{helper}->($opts);
							 | 
						||
| 
								 | 
							
								        };
							 | 
						||
| 
								 | 
							
								        debug($@) if $@;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								sub check_plugin_modtimes {
							 | 
						||
| 
								 | 
							
								    opendir DIR, $plugins_path
							 | 
						||
| 
								 | 
							
								        or fail("Unable to open plugins directory for reading");
							 | 
						||
| 
								 | 
							
								    foreach my $file (readdir(DIR)) {
							 | 
						||
| 
								 | 
							
								        next if $file =~ /^\./;
							 | 
						||
| 
								 | 
							
								        next unless $file =~ /^(.+)\.pl$/;
							 | 
						||
| 
								 | 
							
								        my $plugin = $1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # create an empty record if this is the first time we've found this plugin
							 | 
						||
| 
								 | 
							
								        $plugins{$plugin} ||= {
							 | 
						||
| 
								 | 
							
								            modtime => 0,
							 | 
						||
| 
								 | 
							
								            reload => 1,
							 | 
						||
| 
								 | 
							
								            registered => 0,
							 | 
						||
| 
								 | 
							
								            children => [],
							 | 
						||
| 
								 | 
							
								            file => "$plugins_path/$file",
							 | 
						||
| 
								 | 
							
								        };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # compare modtime and mark for reload if necessary
							 | 
						||
| 
								 | 
							
								        my $mt = (stat($plugins{$plugin}->{file}))[9];
							 | 
						||
| 
								 | 
							
								        $plugins{$plugin}->{reload} = 1
							 | 
						||
| 
								 | 
							
								            if $mt > $plugins{$plugin}->{modtime};
							 | 
						||
| 
								 | 
							
								        $plugins{$plugin}->{modtime} = $mt;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    closedir DIR;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								sub check_config {
							 | 
						||
| 
								 | 
							
								    fail("Config file not found")
							 | 
						||
| 
								 | 
							
								        unless -e $opts{conf};
							 | 
						||
| 
								 | 
							
								    my $mt = (stat($opts{conf}))[9];
							 | 
						||
| 
								 | 
							
								    $opts{config_modtime} ||= 0;
							 | 
						||
| 
								 | 
							
								    reload_config()
							 | 
						||
| 
								 | 
							
								        if $mt > $opts{config_modtime};
							 | 
						||
| 
								 | 
							
								    $opts{config_modtime} = $mt;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								sub reload_config {
							 | 
						||
| 
								 | 
							
								    debug("");
							 | 
						||
| 
								 | 
							
								    debug("configuration file reloading");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # we mark all jobs as inactive, so they get marked as active below
							 | 
						||
| 
								 | 
							
								    foreach (keys %jobs) {
							 | 
						||
| 
								 | 
							
								        $jobs{$_}->{active} = 0
							 | 
						||
| 
								 | 
							
								            unless $jobs{$_}->{respawn_with};
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # clear out all helpers, as they should get readded.  they aren't in
							 | 
						||
| 
								 | 
							
								    # separate threads so it doesn't matter if we readd them every time.
							 | 
						||
| 
								 | 
							
								    @helpers = ();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    open FILE, "<$opts{conf}"
							 | 
						||
| 
								 | 
							
								        or fail("Unable to open config file: $!");
							 | 
						||
| 
								 | 
							
								    foreach my $line (<FILE>) {
							 | 
						||
| 
								 | 
							
								        # ignore comments and clean surrounding whitespace
							 | 
						||
| 
								 | 
							
								        next if $line =~ /^\s*#/;
							 | 
						||
| 
								 | 
							
								        $line =~ s/^\s+//;
							 | 
						||
| 
								 | 
							
								        $line =~ s/[\s\r\n]+$//;
							 | 
						||
| 
								 | 
							
								        next unless length $line;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # shortcut; set some options
							 | 
						||
| 
								 | 
							
								        if ($line =~ /^(\w+)\s*=\s*(.+)$/) {
							 | 
						||
| 
								 | 
							
								            $opts{$1} = $2;
							 | 
						||
| 
								 | 
							
								            next;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # extract any options contained in (...) and going to the end of the line
							 | 
						||
| 
								 | 
							
								        my $optionstr;
							 | 
						||
| 
								 | 
							
								        if ($line =~ s/(?:\((.*)\))$//) {
							 | 
						||
| 
								 | 
							
								            $optionstr = $1;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        my @options =
							 | 
						||
| 
								 | 
							
								            map { (s/^\s+//, s/\s$//, 1) ? $_ : undef } # now trim whitespace front and back
							 | 
						||
| 
								 | 
							
								            split(/,/, $optionstr);                     # split option string on commas
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # now see if it has a job + plugin left, or just a plugin
							 | 
						||
| 
								 | 
							
								        if ($line =~ /^([-\w:.]+)\s*:\s*(\w+)$/) {
							 | 
						||
| 
								 | 
							
								            # this is a job definition
							 | 
						||
| 
								 | 
							
								            my ($job, $plugin) = ($1, $2);
							 | 
						||
| 
								 | 
							
								            fail("Error adding $job to job list")
							 | 
						||
| 
								 | 
							
								                unless add_job($job, $plugin, \@options);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        } elsif ($line =~ /^(\w+)$/) {
							 | 
						||
| 
								 | 
							
								            # this is just a helper plugin
							 | 
						||
| 
								 | 
							
								            fail("Plugin $1 not defined")
							 | 
						||
| 
								 | 
							
								                unless $plugins{$1};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # push name of plugin on helper list
							 | 
						||
| 
								 | 
							
								            debug("helper from plugin $1 added");
							 | 
						||
| 
								 | 
							
								            push @helpers, [ $1, \@options ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								            fail("Unknown format: $line($optionstr)");
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    close FILE;
							 | 
						||
| 
								 | 
							
								    debug("configuration file reloaded");
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# main processor that goes through everything we have and reloads as necessary.  this
							 | 
						||
| 
								 | 
							
								# also handles reaping our children.
							 | 
						||
| 
								 | 
							
								sub reload {
							 | 
						||
| 
								 | 
							
								    # iterate over any dead children we might have picked up
							 | 
						||
| 
								 | 
							
								    while ((my $pid = waitpid(-1, WNOHANG)) > 0) {
							 | 
						||
| 
								 | 
							
								        if (my $job = delete($pids{$pid})) {
							 | 
						||
| 
								 | 
							
								            if ($jobs{$job}->{active}) {
							 | 
						||
| 
								 | 
							
								                debug("[$job] dead; pid = $pid; marking to reload; unexpected!");
							 | 
						||
| 
								 | 
							
								                $jobs{$job}->{reload} = 1;
							 | 
						||
| 
								 | 
							
								                $jobs{$job}->{pid} = 0;
							 | 
						||
| 
								 | 
							
								            } else {
							 | 
						||
| 
								 | 
							
								                debug("[$job] dead; pid = $pid; inactive job, NOT reloading");
							 | 
						||
| 
								 | 
							
								                delete $jobs{$job};
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								            if (delete $todie{$pid}) {
							 | 
						||
| 
								 | 
							
								                debug("child death; pid = $pid; expected death, already reloaded");
							 | 
						||
| 
								 | 
							
								            } else {
							 | 
						||
| 
								 | 
							
								                debug("ERROR: $pid died but we have no record of it");
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # iterate over plugins and reload as necessary
							 | 
						||
| 
								 | 
							
								    foreach my $plugin (sort keys %plugins) {
							 | 
						||
| 
								 | 
							
								        next unless $plugins{$plugin}->{reload};
							 | 
						||
| 
								 | 
							
								        debug("reloading plugin: $plugin");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # now require the file
							 | 
						||
| 
								 | 
							
								        my $file = $plugins{$plugin}->{file};
							 | 
						||
| 
								 | 
							
								        unless (my $ret = do $file) {
							 | 
						||
| 
								 | 
							
								            if ($@) {
							 | 
						||
| 
								 | 
							
								                warn "couldn't parse $file: $@\n";
							 | 
						||
| 
								 | 
							
								            } elsif (! defined $ret) {
							 | 
						||
| 
								 | 
							
								                warn "couldn't do $file: $!\n";
							 | 
						||
| 
								 | 
							
								            } else {
							 | 
						||
| 
								 | 
							
								                warn "couldn't run $file\n";
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            next;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # now mark any jobs with this plugin to reload
							 | 
						||
| 
								 | 
							
								        foreach my $job (keys %jobs) {
							 | 
						||
| 
								 | 
							
								            $jobs{$job}->{reload} = 1
							 | 
						||
| 
								 | 
							
								                if $jobs{$job}->{plugin} eq $plugin ||
							 | 
						||
| 
								 | 
							
								                   $jobs{$job}->{respawn_with} eq $plugin;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # now that we know all the plugins are loaded, iterate over jobs so we can get
							 | 
						||
| 
								 | 
							
								    # the plugins spawned and doing something
							 | 
						||
| 
								 | 
							
								    foreach my $job (sort keys %jobs) {
							 | 
						||
| 
								 | 
							
								        my $plugin = $plugins{$jobs{$job}->{plugin}};
							 | 
						||
| 
								 | 
							
								        fail("can't add job for plugin with no worker code: job = $job; plugin = $jobs{$job}->{plugin}")
							 | 
						||
| 
								 | 
							
								            unless ref $plugin->{code}->{worker};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # see if we need to kill off this job
							 | 
						||
| 
								 | 
							
								        unless ($jobs{$job}->{active}) {
							 | 
						||
| 
								 | 
							
								            debug("killing job: $job");
							 | 
						||
| 
								 | 
							
								            if ($jobs{$job}->{pid}) {
							 | 
						||
| 
								 | 
							
								                kill 15, $jobs{$job}->{pid};
							 | 
						||
| 
								 | 
							
								            } else {
							 | 
						||
| 
								 | 
							
								                delete $pids{$jobs{$job}->{pid}};
							 | 
						||
| 
								 | 
							
								                delete $jobs{$job};
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            next;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # now, the following path does a reload of this job if necessary
							 | 
						||
| 
								 | 
							
								        next unless $jobs{$job}->{reload} && $plugin->{registered};
							 | 
						||
| 
								 | 
							
								        debug("reloading job: $job");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # kill off this child if we had one
							 | 
						||
| 
								 | 
							
								        if ($jobs{$job}->{pid}) {
							 | 
						||
| 
								 | 
							
								            kill 15, $jobs{$job}->{pid};
							 | 
						||
| 
								 | 
							
								            delete $pids{$jobs{$job}->{pid}};
							 | 
						||
| 
								 | 
							
								            $todie{$jobs{$job}->{pid}} = 1;
							 | 
						||
| 
								 | 
							
								            debug("[$job] killing child; pid = $jobs{$job}->{pid}");
							 | 
						||
| 
								 | 
							
								            $jobs{$job}->{pid} = 0;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # bust out a child for this job
							 | 
						||
| 
								 | 
							
								        my $pid = fork;
							 | 
						||
| 
								 | 
							
								        fail("can't fork child: $!") if !defined $pid;
							 | 
						||
| 
								 | 
							
								        unless ($pid) {
							 | 
						||
| 
								 | 
							
								            # child path; do some basic setup and then call the worker
							 | 
						||
| 
								 | 
							
								            $0 .= " [$jobs{$job}->{plugin}: $job]";
							 | 
						||
| 
								 | 
							
								            $SIG{INT} = undef; # in case parent is in the foreground
							 | 
						||
| 
								 | 
							
								            $SIG{TERM} = undef; # no special handling for this
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # call the child which should do all the work and return when it's done
							 | 
						||
| 
								 | 
							
								            $plugin->{code}->{worker}->($job, $jobs{$job}->{options});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # when the above returns, the worker is done, so we exit
							 | 
						||
| 
								 | 
							
								            exit 0;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # if we get here we're a parent, which means we need to mark this child as
							 | 
						||
| 
								 | 
							
								        # run and that we don't need to do anything more
							 | 
						||
| 
								 | 
							
								        $jobs{$job}->{pid} = $pid;
							 | 
						||
| 
								 | 
							
								        $jobs{$job}->{reload} = 0;
							 | 
						||
| 
								 | 
							
								        $pids{$pid} = $job;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# called by plugins to let us know that they want to be active.  they have to provide a
							 | 
						||
| 
								 | 
							
								# certain set of minimum functionality which we use.  we also import some things into
							 | 
						||
| 
								 | 
							
								# their namespace.
							 | 
						||
| 
								 | 
							
								sub register_plugin {
							 | 
						||
| 
								 | 
							
								    my ($plugin, $package, $opts) = @_;
							 | 
						||
| 
								 | 
							
								    return unless $plugin && $package && $plugins{$plugin} && $opts;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # make sure they gave us enough functions
							 | 
						||
| 
								 | 
							
								    unless (ref $opts->{register} && (ref $opts->{worker} || ref $opts->{helper})) {
							 | 
						||
| 
								 | 
							
								        debug("${plugin} did not provide minimum functionality: register and either worker or helper");
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # now create some aliases in their package so they can get to debug and set
							 | 
						||
| 
								 | 
							
								    eval "*${package}::debug = *main::debug;";
							 | 
						||
| 
								 | 
							
								    eval "*${package}::set = *main::set;";
							 | 
						||
| 
								 | 
							
								    eval "*${package}::add_job = *main::add_job;";
							 | 
						||
| 
								 | 
							
								    eval "*${package}::get_var = *main::get_var;";
							 | 
						||
| 
								 | 
							
								    eval "*${package}::mark_inactive_by_plugin = *main::mark_inactive_by_plugin;";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # call the plugin's register function so that it knows we've acknowledged its presence
							 | 
						||
| 
								 | 
							
								    unless ($opts->{register}->()) {
							 | 
						||
| 
								 | 
							
								        debug("${plugin}::register() didn't return true");
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # done reloading, mark as reloaded (so we don't reload next time)
							 | 
						||
| 
								 | 
							
								    $plugins{$plugin}->{code} = $opts;
							 | 
						||
| 
								 | 
							
								    $plugins{$plugin}->{reload} = 0;
							 | 
						||
| 
								 | 
							
								    $plugins{$plugin}->{registered} = 1;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# called by us and by helpers to add jobs to the list.  if called by a plugin, $respawn_with
							 | 
						||
| 
								 | 
							
								# must be specified and should be set to the name of the plugin.  otherwise, this job will
							 | 
						||
| 
								 | 
							
								# die the next time the config file is changed.
							 | 
						||
| 
								 | 
							
								sub add_job {
							 | 
						||
| 
								 | 
							
								    my ($job, $plugin, $options, $respawn_with) = @_;
							 | 
						||
| 
								 | 
							
								    fail("Bad input to add_job: job = $job, plugin = $plugin")
							 | 
						||
| 
								 | 
							
								        unless $job && $plugin;
							 | 
						||
| 
								 | 
							
								    $options ||= [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # now print out debugging info
							 | 
						||
| 
								 | 
							
								    #debug("found config: $job: $plugin(" . join(', ', @$options) . ")");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # make sure this plugin exists
							 | 
						||
| 
								 | 
							
								    fail("Plugin $plugin not defined")
							 | 
						||
| 
								 | 
							
								        unless $plugins{$plugin};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # default %jobs setup
							 | 
						||
| 
								 | 
							
								    $jobs{$job} ||= {
							 | 
						||
| 
								 | 
							
								        plugin => $plugin,
							 | 
						||
| 
								 | 
							
								        pid => 0,
							 | 
						||
| 
								 | 
							
								        reload => 1,
							 | 
						||
| 
								 | 
							
								        options => $options,
							 | 
						||
| 
								 | 
							
								        respawn_with => $respawn_with,
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								    $jobs{$job}->{active} = 1; # on unconditionally
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # now determine if this job needs reloading
							 | 
						||
| 
								 | 
							
								    $jobs{$job}->{reload} = 1 unless $jobs{$job}->{pid};
							 | 
						||
| 
								 | 
							
								    if (scalar(@$options) == scalar(@{$jobs{$job}->{options}})) {
							 | 
						||
| 
								 | 
							
								        # compare options one by one, reload if any have changed
							 | 
						||
| 
								 | 
							
								        for (my $i = 0; $i < scalar(@$options); $i++) {
							 | 
						||
| 
								 | 
							
								            $jobs{$job}->{reload} = 1
							 | 
						||
| 
								 | 
							
								                if $options->[$i] ne $jobs{$job}->{options}->[$i];
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								        # number of options changed, reload them all
							 | 
						||
| 
								 | 
							
								        $jobs{$job}->{reload} = 1;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # if reload, copy in new options just in case
							 | 
						||
| 
								 | 
							
								    if ($jobs{$job}->{reload}) {
							 | 
						||
| 
								 | 
							
								        @{$jobs{$job}->{options}} = @$options;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return 1;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# called by helpers to mark everything they've spawned as inactive before
							 | 
						||
| 
								 | 
							
								# they begin another round of adding jobs.  this is basically a way to say
							 | 
						||
| 
								 | 
							
								# to the gatherer that a process is dead.  if it's not re-added immediately
							 | 
						||
| 
								 | 
							
								# by the helper, it gets killed off in the next round of reaping.
							 | 
						||
| 
								 | 
							
								sub mark_inactive_by_plugin {
							 | 
						||
| 
								 | 
							
								    my $plugin = shift;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    foreach my $job (keys %jobs) {
							 | 
						||
| 
								 | 
							
								        $jobs{$job}->{active} = 0
							 | 
						||
| 
								 | 
							
								            if $jobs{$job}->{respawn_with} eq $plugin;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# used by plugins to get access to variables set in the config file
							 | 
						||
| 
								 | 
							
								sub get_var {
							 | 
						||
| 
								 | 
							
								    return $opts{$_[0] || ''};
							 | 
						||
| 
								 | 
							
								}
							 |