#!/usr/bin/perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html

package DSH;
use base xCAT::DSHContext;
use xCAT::MsgUtils;
use File::Path;


# Configure Node group path from environment

our $nodegroup_path = $ENV{'DSH_NODEGROUP_PATH'};

=head3
        context_defaults

        Assign default properties for the DSH context.  A default
        property for a context will be used if the property is
        not user configured in any other way.

        Arguments:
        	None

        Returns:
        	A reference to a hash table with the configured
        	default properties for the DSH context
                
        Globals:
        	None
    
        Error:
        	None
    
        Example:
        	$default_properties = DSH->config_defaults;

        Comments:
        	$defaults hash table contents:
        	
        		$defaults{'NodeRemoteShell'} - default remote shell to use for node targets

=cut

sub context_defaults {
	my %defaults = ();

	if ( $ENV{'DSH_NODE_RSH'} ) {
		my @remoteshell_list = split ',', $ENV{'DSH_NODE_RSH'};

		foreach $context_remoteshell (@remoteshell_list) {
			my ( $context, $remoteshell ) = split ':', $context_remoteshell;

			if ( !$remoteshell ) {
				$remoteshell = $context;
				!$defaults{'NodeRemoteShell'}
				  && ( $defaults{'NodeRemoteShell'} = $remoteshell );
			}

			elsif ( $context eq 'DSH' ) {
				$defaults{'NodeRemoteShell'} = $remoteshell;
			}
		}
	}

	if ( !$defaults{'NodeRemoteShell'} ) {
		my $dsh_context_defaults = xCAT::DSHContext->context_defaults;
		$defaults{'NodeRemoteShell'} =
		  $$dsh_context_defaults{'NodeRemoteShell'};
	}

	return \%defaults;
}

=head3
        context_properties

        Configure the user specified context properties for the DSH context.
        These properties are configured by the user through environment
        variables or external configuration files.

        Arguments:
        	None

        Returns:
        	A reference to a hash table of user-configured properties for
        	the DSH context.
                
        Globals:
        	None
    
        Error:
        	None
    
        Example:
        	$properties = DSH->config_properties

        Comments:

=cut

sub context_properties {
	my %properties = ();

	$properties{'DCP_DEVICE_OPTS'} = $ENV{'DCP_DEVICE_OPTS'};
	$properties{'DCP_DEVICE_RCP'}  = $ENV{'DCP_DEVICE_RCP'}
	  || $ENV{'DCP_DEVICE_COPY_CMD'};
	$properties{'DCP_NODE_OPTS'} = $ENV{'DCP_NODE_OPTS'};
	$properties{'DCP_NODE_RCP'}  = $ENV{'DCP_NODE_RCP'} || $ENV{'DCP_COPY_CMD'};
	$properties{'DSH_CONTEXT'}   = $ENV{'DSH_CONTEXT'};
	$properties{'DSH_DEVICE_LIST'} = $ENV{'DSH_DEVICE_LIST'};
	$properties{'DSH_DEVICE_OPTS'} = $ENV{'DSH_DEVICE_OPTS'}
	  || $ENV{'DSH_DEVICE_REMOTE_OPTS'};
	$properties{'DSH_DEVICE_RCP'} = $ENV{'DSH_DEVICE_RCP'};
	$properties{'DSH_DEVICE_RSH'} = $ENV{'DSH_DEVICE_RSH'}
	  || $ENV{'DSH_DEVICE_REMOTE_CMD'};
	$properties{'DSH_ENVIRONMENT'}    = $ENV{'DSH_ENVIRONMENT'};
	$properties{'DSH_FANOUT'}         = $ENV{'DSH_FANOUT'};
	$properties{'DSH_LOG'}            = $ENV{'DSH_LOG'};
	$properties{'DSH_NODEGROUP_PATH'} = $ENV{'DSH_NODEGROUP_PATH'};
	$properties{'DSH_NODE_LIST'}      = $ENV{'DSH_NODE_LIST'}
	  || $ENV{'DSH_LIST'}
	  || $ENV{'WCOLL'};
	$properties{'DSH_NODE_OPTS'} = $ENV{'DSH_NODE_OPTS'}
	  || $ENV{'DSH_REMOTE_OPTS'};
	$properties{'DSH_NODE_RCP'} = $ENV{'DSH_NODE_RCP'};
	$properties{'DSH_NODE_RSH'} = $ENV{'DSH_NODE_RSH'}
	  || $ENV{'DSH_REMOTE_SHELL'}
	  || $ENV{'DSH_REMOTE_CMD'};
	$properties{'DSH_OUTPUT'} = $ENV{'DSH_OUTPUT'};
	$properties{'DSH_PATH'}   = $ENV{'DSH_PATH'};
	$properties{'DSH_REPORT'} = $ENV{'DSH_REPORT'}
	  || $ENV{'DSH_REPORTS_DIRECTORY'};
	$properties{'DSH_SYNTAX'}  = $ENV{'DSH_SYNTAX'};
	$properties{'DSH_TIMEOUT'} = $ENV{'DSH_TIMEOUT'};
	$properties{'RSYNC_RSH'}   = $ENV{'RSYNC_RSH'};

	if($ENV{'DSH_ON_HMC'}){
		$properties{'DSH_NODE_RCP'} = '/usr/hmcrbin/scp';
		$properties{'DSH_NODE_RSH'} = '/usr/hmcrbin/ssh';
	}
	return \%properties;
}

=head3
        all_nodegroups

        Returns an array of all node group names in the DSH context

        Arguments:
        	None

        Returns:
        	An array of node group names
                
        Globals:
        	$nodegroup_path
    
        Error:
        	None
    
        Example:
        	@nodegroups = DSH->all_nodegroups;

        Comments:

=cut

sub all_nodegroups {
	my @nodegroups = ();

	if ($nodegroup_path) {
		opendir( DIR, $nodegroup_path );

		while ( my $nodegroup = readdir(DIR) ) {
			( $nodegroup !~ /^\./ ) && push @nodegroups, $nodegroup;
		}

		closedir DIR;
	}

	return @nodegroups;
}

=head3
        nodegroup_members

        Given a node group in the DSH context, this routine expands the
        membership of the node group and returns a list of its members.

        Arguments:
        	$nodegroup - node group name

        Returns:
        	An array of node group members
                
        Globals:
        	$nodegroup_path
    
        Error:
        	None
    
        Example:
        	$members = DSH->nodegroup_members('MyGroup1');

        Comments:

=cut

sub nodegroup_members {
	my ( $class, $nodegroup ) = @_;

	my %resolved_nodes   = ();
	my %unresolved_nodes = ();

	my $nodes = DSH->read_target_file("$nodegroup_path/$nodegroup");

	!$nodes && return undef;

	my @members = ();

	foreach $node (@$nodes) {
		if ( $node =~ /@/ ) {
			xCAT::MsgUtils->message("E",
				"$node is not a valid name for group $nodegroup");
		}

		else {
			push @members, $node;
		}
	}

	DSHContext->resolve_hostnames( \%resolved_nodes, \%unresolved_nodes,
		@members );

	@members = keys(%resolved_nodes);
	return \@members;
}

=head3
        all_nodes

        Returns an array of all node names in the DSH context

        Arguments:
        	None

        Returns:
        	An array of node names
                
        Globals:
        	$nodegroup_path
    
        Error:
        	None
    
        Example:
        	@nodes = DSH->all_nodes;

        Comments:

=cut

sub all_nodes {
	my $build_cache = undef;

	if ( -e "$ENV{'HOME'}/.dsh/$nodegroup_path/AllNodes" ) {
		my @stat_path     = stat $nodegroup_path;
		my @stat_allnodes =
		  stat "$ENV{'HOME'}/.dsh/$nodegroup_path/AllNodes.dsh";

		if ( $stat_path[9] > $stat_allnodes[9] ) {
			$build_cache = 1;
		}

		else {
			if ($nodegroup_path) {
				opendir( DIR, $nodegroup_path );

				while ( my $nodegroup = readdir(DIR) ) {

					if ( $nodegroup !~ /^\./ ) {
						my @stat_file = stat $nodegroup;
						( $stat_file[9] > $stat_allnodes[9] )
						  && ( $build_cache = 1 );
					}

					last if $build_cache;
				}

				closedir DIR;
			}
		}
	}

	else {
		$build_cache = 1;
	}

	if ($build_cache) {
		my @nodegroups = DSH->all_nodegroups;

		my @nodes = ();

		foreach $nodegroup (@nodegroups) {
			push @nodes, @{ DSH->nodegroup_members($nodegroup) };
		}

		if ( !( -d "$ENV{'HOME'}/.dsh/$nodegroup_path" ) ) {
			eval { mkpath( "$ENV{'HOME'}/.dsh/$nodegroup_path") };
			if ($@) {
				 xCAT::MsgUtils->message(
					"E",
				" Cannot make directory: $ENV{'HOME'}/.dsh/$nodegroup_path\n");
				return undef;
			}
		}

		DSH->write_target_file( "$ENV{'HOME'}/.dsh/$nodegroup_path/AllNodes",
			@nodes );
		return @nodes;
	}

	else {
		my $nodes =
		  DSH->read_target_file("$ENV{'HOME'}/.dsh/$nodegroup_path/AllNodes");
		return @$nodes;
	}
}

=head3
        read_target_file

        Processes the given filename and stores all targets in the file in an
        array

        Arguments:
        	$filename - file to read target names from

        Returns:
        	A reference to an array of target names
                
        Globals:
        	None
    
        Error:
        	None
    
        Example:
        	DSH->read_target_file('/tmp/target_file');

        Comments:

=cut

sub read_target_file {
	my ( $class, $filename ) = @_;

	my %targets = ();

	if ( open( FILE, $filename ) ) {

		while ( my $target = <FILE> ) {
			$target =~ /^\s*#/     && next;
			$target =~ /^\s*$/     && next;
			$target =~ /;/         && next;
			$target =~ /\S+\s+\S+/ && next;
			$target =~ s/\s+$//;
			chomp($target);
			$targets{$target}++;
		}

		close FILE;

		my @target_list = keys(%targets);
		return \@target_list;
	}

	else {
		xCAT::MsgUtils->message( "E", "Cannot open file: $filename\n"); 
		return undef;
	}
}

=head3
        write_target_file

        Writes a list of supplied targets to a specified file.  Each target name
        is written to one line

        Arguments:
        	$filename - file to write target names
        	@targets - array of target names to write

        Returns:
        	None
                
        Globals:
        	None
    
        Error:
        	None
    
        Example:
        	DSH->read_target_file('/tmp/target_file');

        Comments:

=cut

sub write_target_file {
	my ( $class, $filename, @targets ) = @_;

	if ( open( FILE, ">$filename" ) ) {

		print FILE "#\n";
		print FILE "# DSH Utilities Target File\n";
		print FILE "#\n";

		foreach $target (@targets) {
			print FILE "$target\n";
		}

		close FILE;
	}

	else {
		xCAT::MsgUtils->message("E", "Error writing file $filename");
	}
}

sub query_node {
	my ( $class, $node ) = @_;
	my $res = 0;

	$~ = "NODES";
	NetworkUtils->tryHost( $node, \$res );
	if ($res) {
		print("$node : Valid\n");
	}
	else {
		print("$node : Invalid\n");
	}

	format NODES =
@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<	@<<<<<<<<<<<<<<<<<
$node, $status
.

}

sub query_group {
	my ( $class, $group ) = @_;
	my @dsh_groups = all_nodegroups();

	$~ = "GROUPS";
	if ( grep( /^$group$/, @dsh_groups ) ) {
		print("$group : Valid\n");
	}
	else {
		print("$group : Invalid\n");
	}
}

1;    #end