eval 'exec perl -x $0 ${1+"$@"}' # -*-perl-*-
  if 0;
#!perl -w
#
# ======================================================================
# This file is Copyright 1998,1999 by the Purdue Research Foundation and
# may only be used under license.  For terms of the license, see the
# file named COPYRIGHT included with this software release.
# AAFID is a trademark of the Purdue Research Foundation.
# All rights reserved.
# ======================================================================
#
# AAFID::Starter package.
#
# AAFID project, COAST Laboratory, CERIAS, 1998-1999.
# 
# Diego Zamboni, Mar 4, 1998.
#
# $Id: Starter.pm,v 1.11 1999/09/03 17:08:54 zamboni Exp $
#
# NOTE: This file is in Perl's POD format. For more information, see the 
#       manual page for perlpod(1).
#

package AAFID::Starter;

# The following keeps up with the RCS version number. The self-assignment
# keeps the -w switch from complaining (because $VERSION may not be used
# here, but it is used in our base class).
$VERSION = do { my @r = (q$Revision: 1.11 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; $VERSION = $VERSION;

use vars qw(
	    $entity
	    $host
	    $port
	    $stdin
	    %opt
	   );
use strict;
use AAFID::Log;
use AAFID::Common;
use AAFID::Constants;
use AAFID::Config;
use IO::Socket;
use Getopt::Long;

=pod

This package implements an "entity starter" object that we use to bootstrap
AAFID across a network (for example, to start a Transceiver that will
communicate to a Monitor in another host). First comes the description of
how this works, then the implementation.

=head1 What do we want?

We would like to have a simple way of starting entities (particularly
C<ControllerEntities>) on a host, and have them be able to communicate
with another C<ControllerEntity> in another host. The communication,
for now, can be done through TCP connections. We have two main ways
of starting up: the entity is started remotely by the entity to which
it will communicate (for example, a Monitor automatically starts
Transceivers in all the hosts that it will be monitoring), or the entity
is started separately and it contacts its controller (in the previous
example, a new Transceiver may be started in a new host, and we want it
to communicate to the same Monitor without having to restart the
Monitor).

=head1 How do we do it?

All the initiative comes from the "subentity" side. This is, it is
the subentity the one who initiates the communication to the "upper"
entity.

=begin text

  Host A                           Host B
  --------------                   -------------
 |              |                 |             |
 |     Monitor<---------------------Transceiver |
 |              |                 |             |
  --------------                   -------------
                                   ^^ "Subentity"

=end text

When the starter program runs, it does the following:

=over 4

=item 1.

Gets (somehow, maybe from a command-line argument or a configuration
file) the address of the host that it has to contact.

=item 2.

Contacts the controller entity in that host to a well-known port.

=item 3.

Once the connection is established, it sets its standard input and
output to that channel.

=item 4.

The starter may also receive an indication of what type of entity it
has to run locally (for example, whether to run a Transceiver or a
Monitor). If not specified, it defaults to some reasonable value (maybe
some instance of a Transceiver).

=item 5. 

If the entity to start is found in the local file system, it is loaded,
instantiated and run. From then on, the starter is no more --- the new
entity has taken over, and will communicate to its "upper" entity through
standard input and output (as it should), but which are connected to a
network channel.

=item 6.

If the entity to start is not found in the local file system, the Starter
fails. In previous versions, it asked for the code using the NEEDMODULE
command, and that capability may come back in the future, but for now it
just terminates.

=back 4

=head1 On the server side

On the server side, we have to add code to the C<ControllerEntity> and
C<Comm> packages to set up and listen on a server socket. When a new
connection is received, it is added to the select object of the
C<ControllerEntity>, and when the first standard AAFID message (which
should be a CONNECT; this does not include the NEEDMODULE message) is
received through it, it is associated with the corresponding ID.

If the server wants to start the remote entity, then it can simply
start it using any remote-execution mechanism it wants (such as rsh
or ssh), giving it the appropriate initialization parameters, and
wait for it to come back through the network.

Enough talking. To the code.

=cut

  sub main {
    &initialize;
    while(!&connect_host) { 
      # If the connection fails, wait and retry.
      sleep(10);
    }
    if (!&load_entity) {
      exit(1);
    }
    &instantiate_entity;
  }

  sub initialize {
    
    my %c=AAFID::Config::configure;
    # General default parameters
    $entity="AAFID::PlainTransceiver";
    $host="nohost";
    $port=$c{listenport};
    $stdin=0;
    
    # linkage info for GetOptions
    %opt=(stdin		=> \$stdin,
	  host  	=> \$host,
	  port		=> \$port,
	  entity	=> \$entity,
	 );

    &GetOptions(\%opt,    # Linkage info.
		"stdin|s",  # "Connect" to STDIN/OUT (for testing)
		            # This is the default if no host is given.
		"host|h=s", # Host to connect to.
		"port|p=i", # Port to connect to.
		"entity|e=s" # Classname to instantiate and run.
	       );

    # If no host is given, use STDIN/OUT, as in testing mode.
    unless ($host and ($host ne "nohost") ) {
      $stdin=1;
    }

    Log_register("debug", $c{logfile});
    foreach (@{$c{logcategories}}) {
      Log_activate($_);
    }
    #    Log_activate("debug");
    Log("debug", "Starter starting\n");
  }

  sub connect_host {
    # If we are in testing mode, do nothing.
    $|=1, return 1 if $stdin;
      # Open the socket
    Log("debug", "Creating socket to $host:$port\n");
    my $s=IO::Socket::INET->new(PeerAddr	=> $host,
				PeerPort	=> $port,
				Proto		=> 'tcp',
				Reuse		=> 1
			       );
    if (!$s) {
      Log("debug", "Could not open socket: $!\n");
      warn "Could no open socket: $!\n";
      return undef;
    }
    # Redirect STDOUT/STDIN to that socket. Make them all nonbuffering.
    $s->autoflush(1);
    Log("debug", "Redirecting STDOUT/STDIN\n");
    open(STDOUT, ">&".$s->fileno)
      or die "Could not redirect STDOUT: $!";
    open(STDIN, "<&".$s->fileno)
      or die "Could not redirect STDIN: $!";
    $|=1;
    return 1;
  }

  sub load_entity {
    # First, try to simply use it.
    Log("debug", "Trying to load $entity\n");
    eval "use $entity";
    return 1 if !$@;
    # We only do further if the use fails.
    if ($@) {
      $@ =~ s/\s+/ /g;
      &stop("Error when trying to load class $entity: $@", 1);
    }
  }

  sub instantiate_entity {
    # This should be simple.
    # Now we got the class loaded, and the package name in $classname.
    # Check if it is a valid entity.
    unless ($entity->isa("AAFID::Entity")) {
      &stop("$entity is not a derivative of AAFID::Entity", 1);
    }
    # Before actually creating the entity, we fork a new process. Then
    # the parent dies. This way, the process that was used for running
    # the starter can terminate, freeing some resources.
    my $pid;
    if (defined($pid=fork)) {
      if (!$pid) {		# We are the children
	# live... Live.... LIVE!!!!!!!! muahahahahahaha!
	Log("debug", "Instantiating new $entity\n");
	my $newentity=$entity->new;
	if (!$newentity) {
	  &stop("Error instantiating $entity, got null instance", 1);
	}
	# Run for your life. From now on, our children takes over. If he
	# ever returns, we die.
	Log("debug", "Running new entity\n");
	$newentity->run;
      }
      # If we are the parent, or after "run" terminates, we exit.
      exit(0);
    }
    else {
      &stop("Error in fork: $!");
    }
  }

  sub stop {
    my $msg=shift;
    my $code=shift || 0;
    # Stop, sending a message.
    print "NONEEDMODULE STOP - - - $msg\n";
    exit($code);
  }

# We have to run the thing.
&main;

#
# $Log: Starter.pm,v $
# Revision 1.11  1999/09/03 17:08:54  zamboni
# Changed the start line to something that is path-independent, and
# updated the copyright notice.
#
# Revision 1.10  1999/03/29 22:33:27  zamboni
# Merged branch a05-new-comm-module, which updates it to make use of the new event-based communication mechanism.
#
# Revision 1.9.4.1  1999/03/29 16:45:17  zamboni
# - Removed capability of requesting the code of the transceiver if it is
#   not found locally. This may be re-added later, but for now the complexity
#   of the needed code to do this with the new event mechanism seems to
#   outweight the benefits. If the transceiver is not found locally,
#   Starter now fails.
# - Removed get_code subroutine, no longer needed due to the above.
#
# Revision 1.9  1998/09/02 16:00:03  zamboni
# - Made it default to STDIN/OUT if no host is given, even if --stdin is
#   not given.
# - Made it fork before creating the new entity, so that the parent
#   process exits and the process which was used to run the Starter
#   can terminate and does not consume resources.
# - Moved Log keyword to the end.
#
# Revision 1.8  1998/06/29 20:11:23  zamboni
# Added copyright message
#
# Revision 1.7  1998/06/26 21:33:04  zamboni
# - Added use of AAFID::Config to get the monitor listening port.
# - Moved assignment of default option values to the inside of initialize()
#   (the code was previously in top level) to have it execute after the
#   configuration parameters are read. Also, made the default host an
#   invalid one, instead of narnia.
# - Made use of AAFID::Config to activate log categories.
#
# Revision 1.6  1998/05/02 19:51:56  zamboni
# POD correction.
#
# Revision 1.5  1998/03/14 05:54:03  zamboni
# - Removed the use of AAFID::Comm, it is not necessary.
# - Modified the code to the new format of the NEEDMODULE, NONEEDMODULE
#   and NEWMODULE messages (instead of NEEDCODE, NONEEDCODE, and the
#   Code-Begin: line, respectively).
#
# Revision 1.4  1998/03/13 16:59:35  zamboni
# - Added $VERSION declaration.
#
# Revision 1.3  1998/03/06 07:11:43  zamboni
# - Added "use lib" to add proper directories to the search path.
# - Added "use strict"
#
# Revision 1.2  1998/03/05 07:15:34  zamboni
# Removed dummy stuff I had put in $host for testing purposes.
#
# Revision 1.1  1998/03/05 06:30:25  zamboni
# Initial revision
#
