###
###  Copyright 2000-2007 University of Illinois Board of Trustees
###  All rights reserved. 
###
###  InitScripts.pm - init script module for psgconf
###
###  Campus Information Technologies and Educational Services
###  University of Illinois at Urbana-Champaign
###


package PSGConf::Control::InitScripts;

use strict;

use PSGConf::Action::GenerateFile::RC_Script;
use PSGConf::Action::Remove;
use PSGConf::Action::Symlink;
use PSGConf::Action::RunCommand;

use PSGConf::Data::Hash;
use PSGConf::Data::String;

use PSGConf::Util;

use File::Basename;


###############################################################################
###  Wrapper function for use with RunCommand
###############################################################################
sub _check_info
{
	my ($self, $psgconf) = @_;
	my ($cmd, $action, $script, $fullname);

	$action = $psgconf->get_action($self->{filename});

	### First see if we have an action to update the script itself.
	return 1 
		if (defined($action) && $action->changed());

	### If not, then we need to run our test.
	$cmd = &PSGConf::Util::_expand_tokens( $psgconf, $self->{test} );
	$fullname = $self->{filename};
	$script = $self->{script};
	$cmd =~ s/%f/$fullname/g;
	$cmd =~ s/%s/$script/g;

	return (!PSGConf::Util::RunCommand($cmd, 1))? 0:1;
}


###############################################################################
###  decide() method
###############################################################################

sub decide
{
	my ($self, $psgconf) = @_;
	my ($script, $cmd, $fullname, $func, $link);

	### Process the rc_scripts hash.
	foreach $script ( keys %{$psgconf->data_obj('rc_scripts')->get()} ) {

		### Verify that we have a state defined for this hash entry
		if ( ! exists $psgconf->data_obj('rc_scripts')->find($script)->{state} ) {
			warn "\t!!!WARNING: rc_script ($script) does not have a state defined\n";
			next;
		}

		$fullname = $psgconf->data_obj('rc_scripts')->find($script)->{fullname};

		# Create any RC scripts we need to create.
		if ( exists $psgconf->data_obj('rc_scripts')->find($script)->{create_rc_script} &&
			$psgconf->data_obj('rc_scripts')->find($script)->{create_rc_script} eq 'yes' )
		{
			if ( $psgconf->data_obj('rc_scripts')->find($script)->{state} eq 'enable' ) 
			{ 
				$psgconf->register_actions (

					### Create the startup script
					PSGConf::Action::GenerateFile::RC_Script->new(
						name => $fullname,
						command_interp => "#!/bin/sh",

						mode => 0755,
						rc_echo => $psgconf->data_obj('rc_echo')->get(),
						description => $psgconf->data_obj('rc_scripts')->find($script)->{description},
						top_cmds => $psgconf->data_obj('rc_scripts')->find($script)->{top_cmds},
						start => $psgconf->data_obj('rc_scripts')->find($script)->{start_cmd},
						stop => $psgconf->data_obj('rc_scripts')->find($script)->{stop_cmd},
						restart => $psgconf->data_obj('rc_scripts')->find($script)->{restart_cmd},
						links => $psgconf->data_obj('rc_scripts')->find($script)->{run_levels},
					)
				);
			}
			elsif ( $psgconf->data_obj('rc_scripts')->find($script)->{state} eq 'disable' ) 
			{
				$psgconf->register_actions (

					### Remove the rc script itself.
					PSGConf::Action::Remove->new(
						name => $fullname
					)
				);
			}
		}

		# Go through and enable or disable all the scripts we were 
		# told to.
		if ( $psgconf->data_obj('rc_scripts')->find($script)->{state} eq 'enable' &&
			exists $psgconf->data_obj('rc_scripts')->find($script)->{enable_cmd} )
		{
			$cmd = &PSGConf::Util::_expand_tokens(
				$psgconf,
				$psgconf->data_obj('rc_scripts')->find($script)->{enable_cmd}
			);
			$cmd =~ s/%f/$fullname/g;
			$cmd =~ s/%s/$script/g;

			$func =
				(defined $psgconf->data_obj('rc_scripts')->find($script)->{enable_test})?
				\&_check_info: undef;

			$psgconf->register_actions (
				PSGConf::Action::RunCommand->new (
					name 		=> "Enabling RC Script $script",
					command		=> $cmd,
					filename		=> $fullname,
					script		=> $script,
					test			=> $psgconf->data_obj('rc_scripts')->find($script)->{enable_test},
					check_func	=> $func
				)
			);
		} 
		elsif ( $psgconf->data_obj('rc_scripts')->find($script)->{state} eq 'disable' &&
			defined $psgconf->data_obj('rc_scripts')->find($script)->{disable_cmd} )
		{
			$cmd = &PSGConf::Util::_expand_tokens(
				$psgconf,
				$psgconf->data_obj('rc_scripts')->find($script)->{disable_cmd}
			);
			$cmd =~ s/%f/$fullname/g;
			$cmd =~ s/%s/$script/g;

			$func =
				(defined $psgconf->data_obj('rc_scripts')->find($script)->{disable_test})?
				\&_check_info: undef;

			$psgconf->register_actions (
				PSGConf::Action::RunCommand->new (
					name 		=> "Disabling RC Script $script",
					command		=> $cmd,
					filename		=> $fullname,
					script		=> $script,
					test			=> $psgconf->data_obj('rc_scripts')->find($script)->{disable_test},
					check_func	=> $func
				)
			);
		}

		# Go through all services we need to manage the RC links for.
		if ( exists $psgconf->data_obj('rc_scripts')->find($script)->{manage_links} &&
			$psgconf->data_obj('rc_scripts')->find($script)->{manage_links} eq 'yes' ) {
			my ($run_levels) = $psgconf->data_obj('rc_scripts')->find($script)->{run_levels};

			# Find the base of the rcX.d directories from the fullname
			# of the script (and remove the init.d/name from the path.
			my ($basedir) = dirname $fullname;
			$basedir = dirname $basedir;

			foreach $link ( keys %{$run_levels} ) {
				my ($linkname) = $basedir . "/rc" . $link . ".d/" . 
					$run_levels->{$link} .  ${script};

				if ( $psgconf->data_obj('rc_scripts')->find($script)->{state} eq 'enable' &&
					! -l $linkname ) {
					$psgconf->register_actions (
						PSGConf::Action::Symlink->new (
							name => $linkname,
							link_to => $fullname
						)
					);
				} elsif ( $psgconf->data_obj('rc_scripts')->find($script)->{state} eq 'disable' &&
					-l $linkname ) {
					$psgconf->register_actions (
						PSGConf::Action::Remove->new (
							name => $linkname,
							backup => 0
						)
					);
				}
			}
		}
	}
}


###############################################################################
###  policy methods
###############################################################################

sub _policy_update_rc_vars
{
	my ($self, $psgconf) = @_;
	my ($script, $var);

	foreach $script ( keys %{$psgconf->data_obj('rc_scripts')->get()} ) {
		if ( defined $psgconf->data_obj('rc_scripts')->find($script)->{rc_var} &&
			defined $psgconf->data_obj('rc_scripts')->find($script)->{state} &&
			$psgconf->data_obj('rc_scripts')->find($script)->{state} ne 'ignore' ) {
				$var = $psgconf->data_obj('rc_scripts')->find($script)->{rc_var};
				$psgconf->data_obj('rc_vars')->insert(
					{ $var =>
						($psgconf->data_obj('rc_scripts')->find($script)->{state} eq 'enable')? 'YES': 'NO'
					});
		}
	}
}

sub _policy_add_all_scripts
{
	my ($self, $psgconf) = @_;
	my ($cmd, $script, $fullname);
	local *FP;

	return
		if ( !defined $psgconf->data_obj('list_rc_scripts_cmd')->get() );

	### Collect all the know startup scripts
	$cmd = $psgconf->data_obj('list_rc_scripts_cmd')->get();

	if (open (FP,  "$cmd |" )) {
		while (<FP>) {

			chomp $_;

			### If the string starts with /, then assume it is
			### a full path and use the basename of the string
			### as the key (and store the full path in the
			### structure.
			$script = $_;


			if ( $script =~ m,^/, ) {
				$fullname = $script;
				$script = basename $script;
				$psgconf->data_obj('rc_scripts')->insert({
					$script => { fullname => $fullname }
				}) if ( ! defined $psgconf->data_obj('rc_scripts')->find($script)->{fullname} );
			}

			$psgconf->data_obj('rc_scripts')->insert(
				{ $script => { 'state' => 
					$psgconf->data_obj('default_script_behavior')->get() }}
			) if ( ! defined $psgconf->data_obj('rc_scripts')->find($script)
				|| ! defined $psgconf->data_obj('rc_scripts')->find($script)->{state} );
		}
		close FP;
	} else {
		warn "Could not run ($cmd)\n";
	}
}

###############################################################################
###  Constructor
###############################################################################

sub new
{
	my ($class, $psgconf) = @_;
	my ($self);

	$self = {};
	bless($self, $class);

	$self->{name} = 'InitScripts';

	$psgconf->register_data(
		rc_scripts			=> PSGConf::Data::Hash->new(
								value_type => 'HASH'
							),
		default_script_behavior	=> PSGConf::Data::String->new(
								value => 'ignore'
							),
		rc_echo				=> PSGConf::Data::String->new(
								value => 'print'
							),
		list_rc_scripts_cmd		=> PSGConf::Data::String->new()
	);

	$psgconf->register_policy($self,
		init_add_all_scripts	=> '_policy_add_all_scripts',
		update_rc_vars			=> '_policy_update_rc_vars'
	);

	return $self;
}


###############################################################################
###  documentation
###############################################################################

1;

__END__

=head1 NAME

PSGConf::Control::InitScripts - psgconf control class for Startup scripts

=head1 SYNOPSIS

In F<psgconf_modules>:

  Control PSGConf::Control::InitScripts

=head1 DESCRIPTION

The B<PSGConf::Control::InitScripts> module provides a B<psgconf>
control object for configuring the system's Startup scripts.  It supports
the following methods:

=over 4

=item new()

The constructor.  Its parameter is a reference to the B<PSGConf>
object.  It registers the following data objects:

=over 4

=item I<rc_scripts>

A B<PSGConf::Data::Hash> of Hashes, where the primary key is the name
of the RC script to process.  Sub keys include:

=over 4

=item I<state>

A B<PSGConf::Data::Enum> object taking the values F<enable>, F<disable>
or F<ignore>.  

=item I<fullname>

A B<PSGConf::Data::String> which contains the fully qualfied file name
for the script (or the script to disable in the case of F</etc/rc.tcpip>
on AIX systems).

=item I<description>

A B<PSGConf::Data::String> object describing what the script does.  This
will be used in the F<chkconfig> formatting information put into the
script itself.

=item I<rc_var>

A B<PSGConf::Data::String> object containing the variable to put into the
F</etc/rc.conf> file on FreeBSD.  Values will be set to C<YES> or C<NO>,
determined by what I<state> is set to (C<enable> or C<disable>).

=item I<manage_links>

A B<PSGConf::Data::Boolean> object telling this Control module whether or
not to maintain the sym links in the C<rcX.d> directories.  Gets F<X> from
the I<run_levels> hash.

=item I<run_levels>

A B<PSGConf::Data::Hash> object containing what run levels we need to run
this script at and what to do.  The key is what run level to run at (usually
is S,0-9) and the value is in the format of F<[SK]XX> where XX is 00 to 99.

=item I<create_rc_scripts>

A B<PSGConf::Data::Boolean> object telling this Control module to create the
RC script, based on what the I<start_cmd> and I<stop_cmd> are set to.

=item I<start_cmd>

A B<PSGConf::Data::String> object with the command to execute when this
RC script is run at boot time.

=item I<stop_cmd>

A B<PSGConf::Data::String> object with the command to execute when this
RC script is run at shutdown time.

=item I<restart_cmd>

A B<PSGConf::Data::String> object with the command to execute when this
service needs to be restarted.  Defaults to running the I<stop_cmd>
followed by the I<start_cmd>.

=item I<enable_cmd>

A B<PSGConf::Data::String> object to enable the RC script in the native
format supported by the OS.  This command supports the special tokens
I<%s> and I<%f>, which get expanded to the hash key and I<fullname>,
respectively.  Also it supports expansion of any B<PSGConf::Data::String>
directive via I<%{directive}> syntax.

=item I<enable_test>

A B<PSGConf::Data::String> object to test to see if the RC script has
been enabled. This command supports the special tokens
I<%s> and I<%f>, which get expanded to the hash key and I<fullname>,
respectively.  Also it supports expansion of any B<PSGConf::Data::String>
directive via I<%{directive}> syntax.

=item I<disable_cmd>

A B<PSGConf::Data::String> object to disable the RC script in the native
format supported by the OS. This command supports the special tokens
I<%s> and I<%f>, which get expanded to the hash key and I<fullname>,
respectively.  Also it supports expansion of any B<PSGConf::Data::String>
directive via I<%{directive}> syntax.

=item I<disable_test>

A B<PSGConf::Data::String> object to test to see if the RC script has
been disabled. This command supports the special tokens
I<%s> and I<%f>, which get expanded to the hash key and I<fullname>,
respectively.  Also it supports expansion of any B<PSGConf::Data::String>
directive via I<%{directive}> syntax.

=back

=item I<default_script_behavior>

A B<PSGConf::Data::String> object to determine what to do with RC
scripts that have been found on the system but are not in the
I<rc_scripts> hash.  Valid values are I<enable>, I<disable> and
I<ignore>.  The default is I<ignore>.

=item I<rc_echo>

A B<PSGConf::Data::String> object for a command that we can use in the
RC script to echo to the string.  It needs to support the I<\c> syntax
to not print a newline.  The default is I<print>.

=item I<list_rc_scripts_cmd>

A B<PSGConf::Data::String> object for a command to list all the RC
scripts on a system.  There should be one column of output, and if
the first character is I</>, then assume the object is a fully 
qualified file name.

=back

The constructor also registers the following policy methods:

=over 4

=item I<init_add_all_scripts>

Updates the I<rc_scripts> hash to have all RC scripts that were
found by I<list_rc_scripts_cmd> command and sets their I<state>
to the value if I<default_script_behavior>, if it is not set
to C<ignore>.

=item I<update_rc_vars>

Updates the I<rc_vars> hash (B<PSGConf::Control::FreeBSD>) with
the values using the I<rc_var> and I<state> values in the I<rc_scripts>
hash.

=back

=item decide()

Instantiates and registers action objects, as follows:

=over 4

=item *

A B<PSGConf::Action::GenerateFile::RC_Script> or B<PSGConf::Action::Remove>
Action object to generate the RC script for each script B<psgconf> is
going to maintain. 

=item *

A B<PSGConf::Action::RunCommand> Action to enable or disable the RC script,
depending on the result from the I<enable_test> or I<disable_test> command.

=item *

A B<PSGConf::Action::Symlink> or B<PSGConf::Action::Remove> Action object
to maintain the I<run_levels> sym links.

=back

=back

=head1 SEE ALSO

L<perl>

L<PSGConf>

L<PSGConf::Action::GenerateFile::RC_Script>

L<PSGConf::Action::Remove>

L<PSGConf::Action::Symlink>

L<PSGConf::Action::RunCommand>

L<PSGConf::Data::Hash>

L<PSGConf::Data::String>

L<psgconf-intro>

L<File::Basename>

=cut



syntax highlighted by Code2HTML, v. 0.9.1