The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#  File: Stem/Boot.pm

#  This file is part of Stem.
#  Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.

#  Stem is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.

#  Stem is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.

#  You should have received a copy of the GNU General Public License
#  along with Stem; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#  For a license to use the Stem under conditions other than those
#  described here, to purchase support for this software, or to purchase a
#  commercial warranty contract, please contact Stem Systems at:

#       Stem Systems, Inc.		781-643-7504
#  	79 Everett St.			info@stemsystems.com
#  	Arlington, MA 02474
#  	USA

package Stem::Boot ;

use strict ;
use Carp ;
use Symbol ;

my $attr_spec = [

	{
		'name'		=> 'reg_name',
		'help'		=> <<HELP,
This is the name under which this Cell was registered.
HELP
	},
	{
		'name'		=> 'boot_file',
		'required'	=> 1,
		'help'		=> <<HELP,
This is the file that describes the processes to bootstrap
HELP
	},
	{
		'name'		=> 'name',
		'help'		=> <<HELP,
Name of this boot entry
HELP
	},
	{
		'name'		=> 'cmd',
		'help'		=> <<HELP,
Path to command that will be booted
HELP
	},
	{
		'name'		=> 'log',
		'help'		=> <<HELP,
Default Name of logical log to send all status and process output
HELP
	},
	{
		'name'		=> 'delay',
		'help'		=> <<HELP,
Default delay (in seconds) between spawning processes
HELP
	},
	{
		'name'		=> 'user',
		'help'		=> <<HELP,
Default user id to run the processes
HELP
	},
	{
		'name'		=> 'wrap',
		'default'	=> '/bin/sh -c',
		'help'		=> <<HELP,
Default command wrapper for each process
HELP
	},
	{
		'name'		=> 'chdir',
		'help'		=> <<HELP,
Default dir to chdir to before running each process
HELP
	},
	{
		'name'		=> 'boot_now',
		'type'		=> 'boolean',
		'default'	=> 1,
		'help'		=> <<HELP,
Boot this program when this object is created
HELP
	},
	{
		'name'		=> 'restart',
		'help'		=> <<HELP,
Restart this program when it exits
HELP
	},
] ;

my %name2boot ;


sub new {

	my( $class ) = shift ;

	my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
	return $self unless ref $self ;

	my $boot_info = Stem::Util::load_file( $self->{'boot_file'} ) ;
	return $boot_info unless ref $boot_info ;

	foreach my $boot ( @{$boot_info} ) {

		die "boot entry is not a hash\n" unless ref $boot eq 'HASH' ;

		if ( my $skip = $boot->{'skip'} ) {

			next if lc $skip eq 'yes' ;
		}

		my $boot_obj = Stem::Class::parse_args( $attr_spec,
					%{$self},
					%{$boot}
		) ;

		die "boot entry error: $boot_obj\n" unless ref $boot_obj ;

		my $cmd = $boot_obj->{'cmd'} ;
		die "boot entry is missing 'cmd'\n" unless $cmd ;

		my $name = $boot_obj->{'name'} ;
		die "boot entry is missing 'name'\n" unless $name ;

		$name2boot{ $name } = $boot_obj ;

		if ( $boot_obj->{'boot_now'} ) {

			$boot_obj->run_cmd() ;
		}
	}

	return ;
}


sub run_cmd {

	my( $self ) = @_ ;

#print Store $self ;

	my $cmd ;

	if ( my $user = $self->{'user'} ) {

		if ( getpwuid($<) ne $user ) {

			$cmd .= "su - $user ; " ;
		}
	}

	if ( my $wrap = $self->{'wrap'} ) {

		$cmd .= qq{$wrap "} ;
		$self->{'wrap_end'} ||= '"' ;
	}

	if ( my $chdir = $self->{'chdir'} ) {

		$cmd .= "cd $chdir ; " ;
	}

	if ( my $stem_env = $self->{'stem_env'} ) {

		my $cmd_env = join ' ', map(
				"$_='$stem_env->{$_}'", keys %{$stem_env} ) ;

		$cmd =~ s/run_stem/run_stem $cmd_env/ ;
	}

	$cmd .= $self->{'cmd'} ;

	$cmd .= $self->{'wrap_end'} if $self->{'wrap_end'} ;

	my $handle = gensym ;

#print "$cmd\n" ;

	if ( my $pid = open( $handle, '-|' ) ) {

#print "pid $pid\n" ;
		$self->{'pid'} = $pid ;
		$self->{'handle'} = $handle ;
	}
	elsif ( defined( $pid ) ) {

		local( %ENV ) = ( %ENV, %{ $self->{'env'} || {} } ) ;

		open( STDERR, '>&STDOUT' ) ;

		exec $cmd ;
		die "Couldn't exec [$cmd]\n" ;
	}
	else {

		die "couldn't fork\n" ;
	}

	my $aio = Stem::AsyncIO->new(

			'object'	=> $self,
			'read_fh'	=> $handle,
			'read_method'	=> 'boot_read',
			'closed_method'	=> 'boot_closed',
	) ;

	$self->{'aio'} = $aio ;

	if ( my $log = $self->{'log'} ) {

		Stem::Log::Entry->new(
			'logs'	=> $log,
			'label'	=> 'boot',
			'text'	=>
			"Booting $self->{'name'} PID = $self->{'pid'}: $cmd\n",
		) ;
	}

	return ;
}

sub boot_read {

	my( $self, $data ) = @_ ;

#print "BOOT READ [$$data]\n" ;

	if ( my $log = $self->{'log'} ) {

		Stem::Log::Entry->new(
			'logs'	=> $log,
			'label'	=> 'boot',
			'text'	=> "Output for $self->{'name'}\n[${$data}]\n",
		) ;
	}

	return ;
}

sub boot_closed {

	my( $self ) = @_ ;

#print "BOOT closed\n" ;

	$self->{'aio'}->shut_down() ;
	delete $self->{'aio'} ;

	my $boot_pid = $self->{'pid'} ;
	my $pid = waitpid( $boot_pid, 0 ) ;

#print "WAIT [$pid]\n" ;

	if ( my $log = $self->{'log'} ) {

		Stem::Log::Entry->new(
			'logs'	=> $log,
			'label'	=> 'boot',
			'text'	=> "Boot $self->{'name'} exited PID = $pid",
		) ;
	}

# do restart if needed




	return ;
}

1 ;