#!/usr/bin/perl
# 
# program to query the DB relay
# written by Thomas.Bergauer@cern.ch
#
# version history:
# 1.0 first try
# 1.1 includes parsing of "status" flag for debugging
# 1.2 includes "verbose" flag, filereading and new table output
# 2.0 completly rewritten parser, comments in input file possible
# 2.1 new flags: -h, -s
# 2.2 fixed bug with -h flag
# 2.3 added timeout for connecting and slow answer
# 2.4 added flag -t for timeout
# 2.5 added fallback host (*by Marko Dragicevic maks@gmx.at)
# 2.6 added parsing of #HEADER 
# 2.7 corrected bug with SIG_IGN signal (Ctrl-C) to terminate 

use XML::Parser;
use Getopt::Std;

#
# some variables

$version = 2.7;
$host    = 'cmstrkdb.in2p3.fr';
$hostFB  = 'lyopc72.in2p3.fr';
$port    = 3615;
$timeout = 60;  # sec standard unless set by option -t

$AF_INET = 2;
$SOCK_STREAM = 1;
    
#
# handle Ctrl-C to stop program
$SIG{'SIG_IGN'} = 'dokill';
sub dokill {
    kill 9,$child if $child;
}


# check options
my %opts = ();
getopts('vsht:');

$verbose = defined($opt_v);
$readfromstdin = defined($opt_s);
$noheader = defined($opt_h);

# read timeout from -t switch
$opt_t = "$timeout" unless defined($opt_t); 
$timeout_answer = $opt_t;


# read filename from argumentlist
$file = $ARGV[0];


#
# read query
if ($readfromstdin) {
  # read query from stdin
  @filelines = <STDIN>
  }
else
  {
  # print usage if no filename given
  if ($file eq "") {
    print "\nTrackerDB relay application query program V",$version,"\n";
    print "by Thomas.Bergauer\@cern.ch\n\n";
    print "usage:\n";
    print "  relay.pl [OPTIONS] filename.sql\n\n";
    print "options:\n";
    print "  -v   verbose. print query and db status.\n";
    print "  -h   no header. suppress column name in answer.\n";
    print "  -s   read query from stdin and not from file.\n";
    print "       (example: cat file.sql |relay.pl -s)\n";
    print "  -t   set timeout for answer in seconds.\n";
    print "\n";
    exit 1;
  }
  # read query from file
  open(QUERY, $file) || die "file not found" ;
  @filelines = <QUERY>;             # read file to arry
  close(QUERY);
  }

# grep header lines from file and strip #HEADER
@header = grep(/^\ *#HEADER/, @filelines);

# remove lines beginning with '#' (remarks)
@lines = grep(!/^\ *#/, @filelines);

#
# connect to relay server at Lyon

if (CONNECT($host) != 0)
   { if ($verbose) {print STDERR "Trying fallback host: $hostFB\n";}
    if (CONNECT($hostFB) != 0)
       {print STDERR "ERROR: Connection to all relay-server refused.\n";
	exit 1;
       }
   }
   

# print xml file to stdout if verbose
if ($verbose) {  print @lines,"\n"; }

# print xml file to socket
print S @lines,"\n";

# print sql file header to stdout
foreach $headerline (@header)
{
	$headerline =~ s/#HEADER//;
	$headerline =~ s/^\s*//;
	print $headerline;
}

# define parser
$parser = new XML::Parser( Handlers => {
                Start => \&handle_start, # starttag
                End => \&handle_end,     # endtag
                Char => \&handle_char,   # value between tags
                Final => \&final_handler}); # after last tag


# read text from socket to answer
# parse the answer from socket
# use 'alarm' for timeout
eval {
  local $SIG{__DIE__} = undef;
  local $SIG{ALRM} = sub {die};
  alarm($timeout_answer);
  $parser->parse(*S); 
  alarm(0);
};

# print timeout if occured
if ($@) {
   print STDERR "ERROR: timeout reading answer.\n";
   exit 1;
} 

# close the socket
close (S);

exit 0;

#
# program end
#



# *************************************************************
# handler for start tags
sub handle_start {
  # read local variables
  my($p,$el) = @_;
  #make tag global
  $tag=$el;
  $state="starttag";
}

# *************************************************************
# handler for start tags
sub handle_end {
  # read local variables
  my($p,$el) = @_;
  #make tag global
  $tag=$el;
  if ($tag eq "row") { 
    if ((! $noheader) or !($linecount == 0)) { print $line,"\n"; } 
    $line = ""; 
    if ($linecount == 0) { print $line2,"\n"; $line2=""; }
    $linecount++; 
    }
  if ($tag eq "value") { 
    $line  = $line  . "\t";  
    $line2 = $line2 . "\t"; 
}
  $state="endtag";
}

# *************************************************************
# handler for data between tags
sub handle_char {
  my($p,$val) = @_;


  # TAG "status"
  if    ($tag eq "status")  { 
    if (($verbose or $val =~ 400 ) and $state eq "starttag" ) { 
        print STDERR "Status: ",$val,"\n\n";
    } 
  }

 # first line
 if ($linecount == 0) { 

  if ($tag eq "column") { $line = $line . $val; }
  if ($tag eq "value")  { $line2 = $line2 . $val; }

 }
 else { 

 # second, third,... line
  if ($tag eq "value") { $line = $line . $val; }


 }

 $state="textbetween";
}


# *************************************************************
# handler after last tag
sub final_handler {
  my($p,$val) = @_;
#  print $val;
}

# *************************************************************
# connect
sub CONNECT
  {
  eval{
  local $SIG{__DIE__} = undef;
  local $SIG{ALRM} = sub {die}; 
  alarm(30);

  $sockaddr = 'S n a4 x8';
  ($name,$aliases,$proto) = getprotobyname('tcp');
  ($name,$aliases,$port) = getservbyname($port,'tcp')
      unless $port =~ /^\d+$/;;
  ($name,$aliases,$type,$len,$thataddr) = gethostbyname($_[0]);

  $this = pack($sockaddr, $AF_INET, 0, $thisaddr);
  $that = pack($sockaddr, $AF_INET, $port, $thataddr);
  $errorcode = 0;
  {
   if (socket(S, $AF_INET, $SOCK_STREAM, $proto)) {
    }
    else {
        if ($verbose) {print STDERR "ERROR: socket error\n";}
        $errorcode = 1;
        last;
    }
    if (bind(S, $this)) {
    }
    else {
        if ($verbose) {print STDERR "ERROR: bind error\n";}
        $errorcode = 2;
        last;
    }
    if (connect(S,$that)) {
    }
    else {
        if ($verbose) {print STDERR "ERROR: connection to $_[0]:$port refused\n";}
        $errorcode = 3;
        last;
    }
  }    
  select(S); $| = 1; select(STDOUT);  
  alarm 0;
  };
  
# print timeout if occured
  if ($@) {
  if ($verbose) {print STDERR "ERROR: timeout while connecting to $_[0]:$port.\n";}
  $errorcode = 4;
  }
  return $errorcode;
};

