#!/usr/bin/perl
#
# sunray Xserver handler for gdm
#

use strict;
use Sys::Syslog;
use Data::Dumper;
use Time::HiRes qw(sleep);
use POSIX ":sys_wait_h";

my $XserversFile = '/var/opt/SUNWut/xconfig/Xservers';
my $UTsession = '/opt/SUNWut/lib/utdmsession';
my $XserversFileMtime = 0;
my $xserversArgs = ' -dpi 75 -terminate -query localhost';
my %xservers;


##
# log events to syslog
sub logger
{
	my $message = shift;

	openlog('utxd', 'cons,pid', 'user');
	syslog('info', $message);
	closelog;
}

##
#
sub sig_chld_handler 
{
	my $child;
        while (($child = waitpid(-1,WNOHANG)) > 0) {
		my $child_display = get_display_from_pid ($child);
		if ($child_display) {
			remove_xserver( $child_display );
		}
	}
	$SIG{CHLD} = \&sig_chld_handler;  # still loathe sysV
} 

##
#
sub cleanup_and_die
{
	logger("terminating all Xservers");
	remove_xserver('all');
	exit(0);
}


##
#
sub parse_xserver_file
{
	my @confTab;
	
	open(FH, $XserversFile);
	while (<FH>) {
		if (/^#/) { next; }
		chomp;

		my (@tab) = split(/ /);
		my ($display, $name, $type) = (shift @tab, shift @tab, shift @tab);
		my $command = join(' ', @tab) . $xserversArgs;
		push (@confTab, { 'display' => $display, 'command' => $command} );
	}
	close(FH);


	# add not existing displays
	foreach my $conf (@confTab) {
		my $display = $conf->{'display'};
		unless ( exists($xservers{$display})) {
			$xservers{ $display }->{'command'} = $conf->{'command'};
			$xservers{ $display }->{'pid'} = undef;
		}

	}

	# schedule server to be removed
	foreach my $display ( keys( %xservers  )) {
		
		my $foundInConfFlag = 0;

		foreach my $conf (@confTab) {
			if ($conf->{'display'} eq $display) { 
				$foundInConfFlag = 1;
			}
		}

		if ($foundInConfFlag == 0) {
			$xservers{$display}->{'removeDisplay'} = 1;
		}
	}
}

##
# find $display using pid
#
sub get_display_from_pid
{
	my $pid = shift;

	foreach my $display ( keys( %xservers )) {
		if ($xservers{$display}->{'pid'} == $pid) {
			return $display;
		}
	}

	return undef;
}


##
# kills xserver
# @param $pid	If 'all' kill all servers
#
sub remove_xserver
{
	my $display = shift;

	# kill all servers
	if ($display eq 'all') {
		foreach my $display ( keys( %xservers )) {
			my $pid = $xservers{$display}->{'pid'};
			xserver_stop($pid);
		}

		%xservers = undef;
		return;
	}


	# kill only one server
	my $pid = $xservers{$display}->{'pid'};
	xserver_stop($pid);
	$xservers{$display}->{'pid'} = undef;
}




##
# start xserver
sub xserver_start
{
	my $display = shift;

	unless ( exists ($xservers{$display} ) and
		 $xservers{$display}->{'pid'} == undef ) {
		# error
		return;
	}

	my $command = $xservers{$display}->{'command'};

	my $child_pid;
	if (!defined( $child_pid = fork())) {
		    die "cannot fork: $!";
	} elsif ($child_pid) {
		$xservers{$display}->{'pid'} = $child_pid;	
	} else {
		# TODO close all fd, logging
		 exec $command;
		 sleep(1);
		 logger("error: exec $command failed");
		 exit(-1);
	} 
}


##
# kill an xserver
sub xserver_stop
{
	my $pid = shift;
	unless ($pid) { return; }
	kill('KILL', $pid);
}








# SIGNAL handling
$SIG{CHLD} = \&sig_chld_handler;
$SIG{INT} = \&cleanup_and_die;
$SIG{TERM} = \&cleanup_and_die;
$SIG{QUIT} = \&cleanup_and_die;


##
# main loop
while (1)
{
	sleep(0.1);

	my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
	       $atime,$mtime,$ctime,$blksize,$blocks) = stat($XserversFile);
	##
	# parse config file if changed
	if ( $XserversFileMtime != $mtime ) {
		logger("Xservers files changes, parsing $XserversFile");
		parse_xserver_file();
		$XserversFileMtime = $mtime;
	}

	foreach my $display ( keys( %xservers ) ) {
		#FIXME: perl fucking weirdness: delete creates an empty element in the hash 
		($display eq '') and next;

		if ($xservers{$display}->{'pid'} == undef) {
			logger("starting Xserver for display: $display");
			xserver_start($display);
		}

		if ( exists ($xservers{$display}->{'removeDisplay'} )) {
			#logger("removing Xserver for display: $display");
			#remove_xserver($display);
			#delete $xservers{$display};
		}
	}
}


exit 0;
