#!/usr/bin/perl
#
# conmux-registry -- console name registry server
#
# Main registry server.  This server holds host/port assignments for
# conmux daemons registering with it.  This allows users to specify
# human names for their consoles and find the relevant conmux daemon.
#
# (C) Copyright IBM Corp. 2004, 2005, 2006
# Author: Andy Whitcroft <andyw@uk.ibm.com>
#
# The Console Multiplexor is released under the GNU Public License V2
#
use strict;

use FindBin;
use Symbol qw(gensym);

use IO::Socket;
use IO::Multiplex;
use URI::Escape;

# Find our internal libraries.
use lib $FindBin::Bin;
use Conmux;

our $P = 'conmux-registry';
our $debug = 0;

#
# LISTENER SOCKET: creates an intenet listener for new clients and
# connects them to the junction provided.
#
package ListenerSocket;

sub new {
	my ($class, $mux, $port, $registry) = @_;
	my $self = bless { 'mux' => $mux, 'registry' => $registry }, $class;

	print "ListenerSocket::new [$self] mux<$mux> port<$port> " .
		"registry<$registry>\n" if ($main::debug);

	$self->initialise($mux, $port, $registry);

	$self;
}

sub initialise {
	my ($self, $mux, $port, $registry) = @_;
	my ($sock);

	print "ListenerSocket::initialise [$self] mux<$mux> port<$port> " .
		"registry<$registry>\n" if ($main::debug);

	# Create a listening socket and add it to the multiplexor.
	my $sock = new IO::Socket::INET(Proto     => 'tcp',
					LocalPort => $port,
					Listen    => 4,
					ReuseAddr => 1)
		or die "socket: $@";

	print "  adding $self $sock\n" if ($main::debug);
	$mux->listen($sock);
	$mux->set_callback_object($self, $sock);
	$self->{'listener'} = $sock;
}

# Handle new connections by instantiating a new client class.
sub mux_connection {
	my ($self, $mux, $fh) = @_;
	my ($client);

	print "ListenerSocket::mux_connection [$self] mux<$mux> fh<$fh>\n"
		if ($main::debug);

	# Make a new client connection.
	$client = Client->new($mux, $fh, $self->{'registry'});
	print "  new connection $self $client\n" if ($main::debug);
}

sub DESTROY {
	my ($self) = @_;

	print "ListenerSocket::DESTROY [$self]\n" if ($main::debug);

	close($self->{'listener'});
}

#
# CLIENT: general client object, represents a remote client channel
#
package Client;

sub new {
	my ($class, $mux, $fh, $registry) = @_;
	my $self = bless { 'mux' => $mux,
			   'fh'  => $fh,
			   'registry' => $registry }, $class;

	print "Client::new [$self] mux<$mux> fh<$fh> registry<$registry>\n"
		if ($main::debug);

	$self->initialise($mux, $fh, $registry);

	$self;
}

sub initialise {
	my ($self, $mux, $fh, $registry) = @_;

	print "Client::initialise [$self] mux<$mux> fh<$fh> " .
		"registry<$registry>\n" if ($main::debug);

	$mux->set_callback_object($self, $fh);
}

sub mux_input {
	my ($self, $mux, $fh, $input) = @_;

	print "Client::mux_input [$self] mux<$mux> fh<$fh> input<$$input>\n"
		if ($main::debug);

        while ($$input =~ s/^(.*?)\n//) {
		my ($cmd, $args) = split(' ', $1, 2);
		my (%args) = Conmux::decodeArgs($args);

		my $reply = {
			'status' => 'ENOSYS',
		};

		# Fill in the common results.
		$reply->{'title'} = 'registry';

		# Handle this command.
		if ($cmd eq "LOOKUP") {
			my $r = $self->{'registry'}->lookup($args{'service'});

			if (defined $r) {
				$reply->{'result'} = $r;
				$reply->{'status'} = 'OK';

			} else {
				$reply->{'status'} = 'ENOENT entry not found';
			}

		} elsif ($cmd eq "ADD") {
			$self->{'registry'}->add($args{'service'},
				$args{'location'});
			$reply->{'status'} = 'OK';

		} elsif ($cmd eq "LIST") {
			$reply->{'result'} = $self->{'registry'}->list();
			$reply->{'status'} = 'OK';
		}

		$fh->write(Conmux::encodeArgs($reply) .  "\n");
	}
}
sub mux_eof {
	my ($self, $mux, $fh, $input) = @_;

	print "Client::mux_eof [$self] mux<$mux> fh<$fh> input<$input>\n"
		if ($main::debug);

	# Handle any pending input, then remove myself.
	$self->mux_input($mux, $fh, $input);

	# Tell the multiplexor we no longer are using this channel.
	$mux->shutdown($fh, 1);
}
sub mux_close {
	my ($self, $mux, $fn) = @_;

	print "Client::close [$self]\n" if ($main::debug);
}

sub DESTROY {
	my ($self) = @_;

	print "Client::DESTROY [$self]\n" if ($main::debug);
}

#
# REGISTRY: registry elements.
#
package Registry;

sub new {
	my ($class, $store) = @_;
	my $self = bless { 'store'  => $store }, $class;

	my ($key, $val);

	print "Registry::new [$self] store<$store>\n" if ($main::debug);

	# Open the store and populate the keys.
	open(S, '<', $store) || die "Registry::new: $store: open failed - $!\n";
	while (<S>) {
		chomp;

		($key, $val) = split(' ', $_);

		$self->{'key'}->{$key} = $val;
	}
	close(S);

	$self;
}

sub add {
	my ($self, $what, $where) = @_;

	my ($key);

	print "Registry::add [$self] what<$what> where<$where>\n"
		if ($main::debug);

	$self->{'key'}->{$what} = $where;

	print "$what at $where\n";

	if (open(S, '>', $self->{'store'} . '.new')) {
		foreach $key (sort keys %{$self->{'key'}}) {
			print S "$key $self->{'key'}->{$key}\n";
		}
		close(S);
		rename $self->{'store'} . '.new', $self->{'store'};

	} else {
		warn "$P: $self->{'store'}.new: open failed - $!";
	}
}

sub lookup {
	my ($self, $what) = @_;

	print "Registry::lookup [$self] what<$what>\n" if ($main::debug);

	$self->{'key'}->{$what};
}

sub list {
	my ($self) = @_;
	my ($r, $key);

	print "Registry::list [$self]\n" if ($main::debug);

	foreach $key (sort keys %{$self->{'key'}}) {
		$r .= "$key $self->{'key'}->{$key}\n";
	}

	$r;
}

#
# MAIN: makes the IO multiplexor, listener and registry and stitches
# them all together.
#
package main;

# Usage checks.
if ($#ARGV != 1) {
	print STDERR "Usage: $P <local port> <store>\n";
	exit 1
}
my ($lport, $store) = @ARGV;

# Make a new multiplexer.
my $mux  = new IO::Multiplex;

# Make the registry object.
my $registry = Registry->new($store);

# Create the client listener socket.
my $listener = ListenerSocket->new($mux, $lport, $registry);

# Hand over to the multiplexor.
$mux->loop;
