313 lines
7.6 KiB
Perl
313 lines
7.6 KiB
Perl
#! /usr/bin/perl
|
|
#
|
|
# Monitors dgamelaunch inprogress-dirs to detect active games, and then
|
|
# monitors those active games to find .where updates and converts them to
|
|
# .dglwhere files that dgamelaunch can consume.
|
|
#
|
|
# Assumption: inprogress dirs are directly under DGLDIR/inprogress*
|
|
# Assumption: player .where files are under TTYRECDIR/<playername>/
|
|
# Assumption: the script runs with permissions to create .dglwhere alongside
|
|
# the player .where
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Linux::Inotify2;
|
|
use POSIX;
|
|
use Fcntl qw/:flock/;
|
|
|
|
use Getopt::Long;
|
|
|
|
my $DAEMON = 1;
|
|
GetOptions("daemon!" => \$DAEMON)
|
|
or die "Bad command line: @ARGV\n";
|
|
|
|
my $DGLDIR = $ARGV[0] || '';
|
|
my $TTYRECDIR = $ARGV[1] || '';
|
|
|
|
$ENV{DGLDIR} = $DGLDIR;
|
|
$ENV{TTYRECDIR} = $TTYRECDIR;
|
|
|
|
my $LOCKFILE = "$DGLDIR/.crawl-inotify.lock";
|
|
my $LOGFILE = "$DGLDIR/crawl-inotify-where.log";
|
|
|
|
my $inotify;
|
|
my %MONITORED_PLAYERS;
|
|
|
|
sub say($) {
|
|
print STDERR "@_\n";
|
|
}
|
|
|
|
sub assert_dir_exists($) {
|
|
my $dir = shift;
|
|
die "$dir not set\n" unless $ENV{$dir};
|
|
die "Can't find $dir ($ENV{$dir})\n" unless -d $ENV{$dir};
|
|
}
|
|
|
|
sub assert_environment_exists() {
|
|
assert_dir_exists('DGLDIR');
|
|
assert_dir_exists('TTYRECDIR');
|
|
}
|
|
|
|
sub player_where_dir($$) {
|
|
my ($player, $dir) = @_;
|
|
for my $candidate ("$TTYRECDIR/$dir/morgue/$player") {
|
|
return $candidate if -d $candidate;
|
|
}
|
|
"$TTYRECDIR/$player"
|
|
}
|
|
|
|
sub player_where_file($$) {
|
|
my ($player, $where_dir) = @_;
|
|
player_where_dir($player, $where_dir) . "/$player.where"
|
|
}
|
|
|
|
sub player_dglwhere_file($$) {
|
|
my ($player, $morgue_dir) = @_;
|
|
player_where_dir($player, $morgue_dir) . "/$player.dglwhere"
|
|
}
|
|
|
|
sub whereis_read($) {
|
|
my $whereis_file = shift;
|
|
open my $inf, '<', $whereis_file or do {
|
|
warn "Could not read whereis file: $whereis_file: $!\n";
|
|
return;
|
|
};
|
|
chomp(my $text = <$inf>);
|
|
close $inf;
|
|
$text =~ s/::/\n/g;
|
|
my %hash = map {
|
|
my ($key, $value) = /(\w+)=(.*)/s;
|
|
$value =~ s/\n/:/gs;
|
|
($key, $value)
|
|
} split(/:/s, $text);
|
|
\%hash
|
|
}
|
|
|
|
sub whereis_human_readable($) {
|
|
my $w = shift;
|
|
# No more than 18 characters, empty string for saved/dead characters:
|
|
return '' if $$w{status} ne 'active';
|
|
my $weight = $$w{xl} * 100 + $$w{lvl};
|
|
sprintf("$weight|%-3s $$w{char}, $$w{place}",
|
|
"L$$w{xl}")
|
|
}
|
|
|
|
sub write_dglwhere_file($$) {
|
|
my ($player, $where_dir) = @_;
|
|
my $where_dict = whereis_read(player_where_file($player, $where_dir));
|
|
|
|
if ($where_dict) {
|
|
my $human_readable_where = whereis_human_readable($where_dict);
|
|
my $dglwhere_file = player_dglwhere_file($player, $where_dir);
|
|
open my $outf, '>', $dglwhere_file or do {
|
|
say "Could not write $dglwhere_file: $!";
|
|
return;
|
|
};
|
|
print $outf "$human_readable_where\n";
|
|
close $outf;
|
|
say "Wrote $dglwhere_file: $human_readable_where";
|
|
}
|
|
}
|
|
|
|
sub inprog_player($) {
|
|
my $ttyrec = shift;
|
|
($ttyrec) = $ttyrec =~ m{.*/(.*)} if $ttyrec =~ m{/};
|
|
my ($player) = $ttyrec =~ /(.+?):/;
|
|
$player
|
|
}
|
|
|
|
sub inprog_morgue_dir($) {
|
|
my $dir = shift;
|
|
$dir =~ s{/$}{};
|
|
($dir) = $dir =~ m{.*/(.*)};
|
|
# Strip alt qualifiers:
|
|
$dir =~ s/\b(?:spr|zd|tut)-//;
|
|
$dir
|
|
}
|
|
|
|
## BEGIN inotify callbacks ##
|
|
|
|
sub inotify_player_where_file_changed {
|
|
my ($player, $where_dir, $event) = @_;
|
|
return unless $$event{name} =~ /$player\.where$/;
|
|
write_dglwhere_file($player, $where_dir);
|
|
}
|
|
|
|
sub inotify_inprogress_change {
|
|
my ($inprog_dir, $event) = @_;
|
|
my $file = $$event{name};
|
|
return unless $file;
|
|
|
|
my $player = inprog_player($file);
|
|
my $morgue_dir = inprog_morgue_dir($inprog_dir);
|
|
my $gone_away = $$event{mask} & IN_DELETE;
|
|
if ($player) {
|
|
if ($gone_away) {
|
|
say "$player went away, unmonitoring.";
|
|
unmonitor_player($player);
|
|
}
|
|
else {
|
|
say "$player started game, monitoring";
|
|
write_dglwhere_file($player, $morgue_dir);
|
|
monitor_player($inotify, $player, $morgue_dir);
|
|
}
|
|
}
|
|
}
|
|
|
|
## END inotify callbacks ##
|
|
|
|
sub monitor_player($$$) {
|
|
my ($inotify, $player, $morgue_dir) = @_;
|
|
return if $MONITORED_PLAYERS{$player};
|
|
say "++ MONITOR: $player";
|
|
|
|
my $watch;
|
|
for my $i (1..3) {
|
|
my $where_dir = player_where_dir($player, $morgue_dir);
|
|
$watch = $inotify->watch($where_dir,
|
|
IN_CLOSE_WRITE,
|
|
sub {
|
|
inotify_player_where_file_changed($player,
|
|
$morgue_dir,
|
|
@_)
|
|
});
|
|
last if $watch;
|
|
|
|
say " xx RETRY: $i";
|
|
sleep 1;
|
|
}
|
|
if ($watch) {
|
|
$MONITORED_PLAYERS{$player} = $watch;
|
|
} else {
|
|
say "[ERR] Watch object is false for $player: $!"
|
|
}
|
|
}
|
|
|
|
sub unmonitor_player($) {
|
|
my $player = shift;
|
|
my $watch = $MONITORED_PLAYERS{$player};
|
|
if (defined $watch) {
|
|
say "-- MONITOR: $player";
|
|
$watch->cancel;
|
|
delete $MONITORED_PLAYERS{$player};
|
|
}
|
|
else {
|
|
say "$player was not being monitored, ignoring unmonitor request"
|
|
}
|
|
}
|
|
|
|
sub inprogress_dirs() {
|
|
my $inprog_glob = $DGLDIR;
|
|
if (-d "$DGLDIR/inprogress") {
|
|
$inprog_glob = "$DGLDIR/inprogress/*";
|
|
}
|
|
else {
|
|
$inprog_glob = "$DGLDIR/inprogress*";
|
|
}
|
|
my @dirs = grep(-d, glob($inprog_glob));
|
|
die "No inprogress dirs under $DGLDIR!\n" unless @dirs;
|
|
@dirs
|
|
}
|
|
|
|
sub monitor_active_player_where($) {
|
|
my $inotify = shift;
|
|
|
|
my %monitorees;
|
|
my @inprog_dirs = inprogress_dirs();
|
|
for my $dir (@inprog_dirs) {
|
|
my @ttyrecs = glob("$dir/*.ttyrec");
|
|
|
|
my $morgue_dir = inprog_morgue_dir($dir);
|
|
for my $ttyrec (@ttyrecs) {
|
|
my $player = inprog_player($ttyrec);
|
|
write_dglwhere_file($player, $morgue_dir);
|
|
monitor_player($inotify, $player, $morgue_dir);
|
|
}
|
|
}
|
|
}
|
|
|
|
sub monitor_inprogress_dirs($) {
|
|
my $inotify = shift;
|
|
my @inprog_dirs = inprogress_dirs();
|
|
for my $inprog (@inprog_dirs) {
|
|
$inotify->watch($inprog, IN_CREATE | IN_DELETE,
|
|
sub {
|
|
my $event = shift;
|
|
inotify_inprogress_change($inprog, $event)
|
|
});
|
|
}
|
|
}
|
|
|
|
sub lock_or_exit {
|
|
my ($exitcode, $lockf) = @_;
|
|
$exitcode ||= 0;
|
|
open LOCKFILE, '>', $lockf or die "Couldn't open $lockf: $!\n";
|
|
flock(LOCKFILE, LOCK_EX | LOCK_NB)
|
|
or do {
|
|
warn "Cannot start: $lockf is held by another process\n";
|
|
exit($exitcode);
|
|
};
|
|
}
|
|
|
|
sub daemonify {
|
|
my $log = shift;
|
|
defined(my $pid = fork) or die "Unable to fork: $!";
|
|
exit if $pid;
|
|
setsid or die "Unable to start a new session: $!";
|
|
print "Started daemon.\n";
|
|
|
|
open STDOUT, '>', $log or die "Can't write $log: $!\n";
|
|
open STDERR, '>&', \*STDOUT;
|
|
# Done daemonifying.
|
|
}
|
|
|
|
sub ps_list($) {
|
|
my $process_name = shift;
|
|
my @processes = map([split],
|
|
qx/ps -ef | grep \Q$process_name\E | grep -v grep/);
|
|
grep($_->[1] != $$, @processes);
|
|
}
|
|
|
|
sub ps_pid($) {
|
|
my $process_name = shift;
|
|
my @processes = ps_list($process_name);
|
|
return unless @processes == 1;
|
|
return $processes[0][1];
|
|
}
|
|
|
|
sub pid_describe($) {
|
|
my $pid = shift;
|
|
my @lines = qx/ps -f -p \Q$pid\E/;
|
|
join('', @lines)
|
|
}
|
|
|
|
sub supersede_existing_daemon() {
|
|
my $name = $0;
|
|
($name) = $name =~ m{.*/(.*)} if $name =~ m{/};
|
|
my $existing_daemon_pid = ps_pid($name);
|
|
if ($existing_daemon_pid) {
|
|
print "Killing existing daemon ($existing_daemon_pid):\n",
|
|
pid_describe($existing_daemon_pid);
|
|
kill TERM => $existing_daemon_pid;
|
|
sleep 1;
|
|
}
|
|
}
|
|
|
|
sub main() {
|
|
assert_environment_exists();
|
|
|
|
supersede_existing_daemon();
|
|
lock_or_exit(1, $LOCKFILE);
|
|
daemonify($LOGFILE) if $DAEMON;
|
|
|
|
$inotify = Linux::Inotify2->new;
|
|
monitor_active_player_where($inotify);
|
|
monitor_inprogress_dirs($inotify);
|
|
|
|
say "Monitoring and updating where info";
|
|
$inotify->poll while 1;
|
|
warn "Exiting, WTF?\n";
|
|
}
|
|
|
|
main();
|