ljr/wcmtools/spud/bin/replicator

225 lines
6.2 KiB
Plaintext
Raw Permalink Normal View History

2019-02-05 21:49:12 +00:00
#!/usr/bin/perl
#
# Danga's Statistics Server Replicator
# Very lightweight program that replicates data from one statistic server (subscribe *)
# to another one. Very, very simple and lightweight. Uses open3() to use the ssh command
# to connect to a remote statistics server.
#
# Command line options:
#
# -c STRING set what config file to use for options
# -D if present, tell the server to daemonize
#
# Configuration file format:
#
# server = STRING location of statistics server
# port = INT port number server listens on
# ssh_host = STRING host of remote SSH to tunnel through
# ssh_port = INT port SSH daemon is on
# ssh_key = STRING filename to use for our private key
# ssh_user = STRING username to identify as to SSH server
#
# Copyright 2004, Danga Interactive
#
# Authors:
# Mark Smith <marksmith@danga.com>
#
# License:
# undecided.
#
# uses
use strict;
use IO::Socket;
use IO::Select;
use IPC::Open3;
use Getopt::Long;
use Carp;
use POSIX ":sys_wait_h";
use Socket qw(PF_INET IPPROTO_TCP SOCK_STREAM);
use Danga::Socket;
# Command-line options will override
my ($daemonize, $conf_file);
Getopt::Long::Configure( "bundling" );
Getopt::Long::GetOptions(
'D|daemon' => \$daemonize,
'c|config=s' => \$conf_file,
);
die "You must at least specify --config=FILENAME for me to work.\n" unless $conf_file;
die "File '$conf_file' doesn't exist for configuration.\n" unless -e $conf_file;
# parse the config file
my %config;
open FILE, "<$conf_file"
or die "Unable to open config file: $!\n";
while (my $line = <FILE>) {
if ($line =~ /^\s*([^#].*)\s*=\s*(.*)\s*/) {
my ($l, $r) = (trim($1), trim($2));
if ($l eq 'server') { $config{to}{host} = $r; }
elsif ($l eq 'port') { $config{to}{port} = $r+0; }
elsif ($l eq 'ssh_key') { $config{from}{key} = $r; }
elsif ($l eq 'ssh_host') { $config{from}{host} = $r; }
elsif ($l eq 'ssh_port') { $config{from}{port} = $r+0; }
elsif ($l eq 'ssh_user') { $config{from}{username} = $r; };
}
}
close FILE;
# daemonize?
daemonize() if $daemonize;
# connect to stats server we're replicating TO. messy.
my $dsock = new_connection()
or die "Can't get initial socket... exiting.\n";
# now setup our ssh open3 sockets
my ($reader, $writer);
my $sshpid = open3($writer, $reader, $reader, 'ssh', '-C', '-tt', '-i', $config{from}{key},
'-p', $config{from}{port}, '-l', $config{from}{username}, $config{from}{host});
print $writer "sub *\n";
# always kill off our SSH connection
$SIG{TERM} = sub { kill 15, $sshpid; exit 0; };
$SIG{INT} = sub { kill 15, $sshpid; exit 0; };
# variables used later
my $readbuf;
# this is our main reading loop
my $sobj = new IO::Select;
$sobj->add($reader);
# post event loop
my $postloop = sub {
# check if somehow ssh died on us? :-/
my $pid = waitpid -1, WNOHANG;
if ($pid == $sshpid) {
# sleep a few seconds and try to spawn a new one
$sshpid = 0;
while (!$sshpid) {
print "Lost SSH connection... sleeping 5 seconds.\n";
sleep 5;
$sshpid = open3($writer, $reader, $reader, 'ssh', '-C', '-tt', '-i', $config{from}{key},
'-p', $config{from}{port}, '-l', $config{from}{username}, $config{from}{host});
print $writer "sub *\n";
}
}
# if our parent socket is closed...
unless ($dsock && !$dsock->{closed}) {
# create a new one if we can
print "Lost SPUD connection... reconnecting...\n";
$dsock = new_connection();
unless ($dsock) {
print "\tUnable to connect... pausing a second.\n";
sleep 1;
return 1;
}
}
# see if we can read from our socket yet
my @ready = $sobj->can_read(0.1);
return 1 unless @ready;
# must be ready to read
my $bytes = sysread $reader, $readbuf, 1024, length $readbuf;
while ($readbuf =~ s/(.+?)\r?\n//) {
my $line = $1;
next unless $line =~ /^set/i;
$dsock->write("Q$line\r\n");
}
return 1;
};
# now configure the client
Client->SetLoopTimeout(100); # 100 milliseconds timeout
Client->SetPostLoopCallback($postloop); # have it call us
# now run the event loop
Client->EventLoop();
# kill off our child too
kill 15, $sshpid;
print "replicator terminating\n";
# daemonizer routine
sub daemonize {
my($pid, $sess_id, $i);
## Fork and exit parent
if ($pid = fork) { exit 0; }
## Detach ourselves from the terminal
croak "Cannot detach from controlling terminal"
unless $sess_id = POSIX::setsid();
## Prevent possibility of acquiring a controling terminal
$SIG{'HUP'} = 'IGNORE';
if ($pid = fork) { exit 0; }
## Change working directory
chdir "/";
## Clear file creation mask
umask 0;
## Close open file descriptors
close(STDIN);
close(STDOUT);
close(STDERR);
## Reopen stderr, stdout, stdin to /dev/null
open(STDIN, "+>/dev/null");
open(STDOUT, "+>&STDIN");
open(STDERR, "+>&STDIN");
}
# little trimming sub
sub trim {
my $res = shift;
$res =~ s/^\s+//;
$res =~ s/\s+$//;
return $res;
}
# connect anew to the SPUD server we're replicating to
# NOTE: can return undef if we can't get to the server!
sub new_connection {
my $sock;
socket $sock, PF_INET, SOCK_STREAM, IPPROTO_TCP;
die "error: can't make socket\n"
unless $sock && defined fileno($sock);
IO::Handle::blocking($sock, 0);
connect $sock, Socket::sockaddr_in($config{to}{port}, Socket::inet_aton($config{to}{host}));
my $dsock = Client->new($sock)
or return undef;
$dsock->watch_write(1);
$dsock->watch_read(1);
return $dsock;
}
###########################################################################
### Client class for use in processing input/output
package Client;
use base "Danga::Socket";
sub event_read {
# read and toss, we don't care about input from the user here
my Client $self = $_[0];
my $bref = $self->read;
}
sub event_err {
# connection died?
my Client $self = $_[0];
$self->close('event_err');
}
sub event_hup {
# connection to server died...
my Client $self = $_[0];
$self->close('event_hup');
}