# @(#)33        1.4  src/bos/usr/lib/nim/methods/NIMUtils.pm, cmdnim, bos720 11/25/08 04:05:14
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
# bos720 src/bos/usr/lib/nim/methods/NIMUtils.pm 1.4 
#  
# Licensed Materials - Property of IBM 
#  
# COPYRIGHT International Business Machines Corp. 2008 
# All Rights Reserved 
#  
# US Government Users Restricted Rights - Use, duplication or 
# disclosure restricted by GSA ADP Schedule Contract with IBM Corp. 
#  
# IBM_PROLOG_END_TAG 
package NIMUtils;

use strict;
use warnings;
use Carp;
use IPC::Open3;
use IO::Select;
use File::Temp qw / tempdir /;
use File::Path;

require Exporter;

our @ISA = qw(Exporter);
our @EXPORT = qw(set_signals_handler restore_default_signals_handlers run_command
nim_umount nim_mount translate lsnim_command compare_releases create_tmpdir 
KO_CODE OK_CODE MASTER TMPDIR NIM NIMCLIENT LSNIM SYNC MOUNT UNMOUNT HOST LN);


# Module return code constants
use constant KO_CODE => -1;
use constant OK_CODE => 0;
# Other constants
use constant MASTER   => 'master';
use constant CLIENT   => 'client';
use constant TMPDIR   => '/tmp';

# List of the AIX commands used
use constant NIM			=> '/usr/sbin/nim';
use constant NIMCLIENT 		=> '/usr/sbin/nimclient';
use constant LSNIM			=> '/usr/sbin/lsnim';
use constant SYNC			=> '/usr/sbin/sync';
use constant MOUNT			=> '/usr/sbin/mount';
use constant UNMOUNT		=> '/usr/sbin/umount';
use constant HOST			=> '/usr/bin/host';
use constant LN				=> '/usr/bin/ln';
use constant C_ERRMSG		=> '/usr/lpp/bos.sysmgt/nim/methods/c_errmsg';

our %EXPORT_TAGS = ( 
rc_csts => [ 'KO_CODE', 'OK_CODE' ], 
nim_csts => [ 'MASTER', 'TMPDIR' ], 
aix_csts => [ 'NIM', 'NIMCLIENT', 'LSNIM', 'SYNC', 'MOUNT', 'UNMOUNT', 'HOST', 'LN'] 
);
our @EXPORT_OK = qw($NIM_CMD $LSNIM_CMD $EXEC_NAME);

# These global variables have their values changed dynamically inside 
# the function which use them.
# exported package globals go here
our $NIM_CMD;
our $LSNIM_CMD;
our $EXEC_NAME;

$NIM_CMD="";
$LSNIM_CMD="";
$EXEC_NAME="";
&set_nim_environment();

# ----------------------------------------------------------------------------
# SUBROUTINE_NAME: set_nim_environment
# ABSTRACT: This function sets the nim binaries to nim or nimclient...
# GLOBAL_MODIFIED: NIM_CMD LSNIM_CMD
# INPUT: none
# OUTPUT: none
# RETURN: OK_CODE
# ----------------------------------------------------------------------------
sub set_nim_environment() {
	
	if (-e NIM) {
		$NIM_CMD = NIM;
		$LSNIM_CMD = LSNIM." -l";
		$EXEC_NAME = MASTER;
	} else {
		$NIM_CMD = NIMCLIENT;
		$LSNIM_CMD = NIMCLIENT." -l -l";
		$EXEC_NAME = CLIENT;
	}
	return (OK_CODE);
}

# ----------------------------------------------------------------------------
# SUBROUTINE_NAME: set_signals_handler
# ABSTRACT: This function sets the signal handler
# GLOBAL_MODIFIED: none
# INPUT: a reference to the function which cleanup before exiting
# OUTPUT: none
# RETURN: OK_CODE
# ----------------------------------------------------------------------------
sub set_signals_handler($)
{
	my ($function_ref) = @_;
	$SIG{INT} = $SIG{KILL} = $SIG{ABRT} = $SIG{ALRM} = $SIG{HUP} = $SIG{QUIT} = $function_ref; # = $SIG{TERM}
	return (OK_CODE);
}

# ----------------------------------------------------------------------------
# SUBROUTINE_NAME: restore_default_signals_handlers
# ABSTRACT: This function restores the SIG Handlers
# GLOBAL_MODIFIED: %SIG
# INPUT: none
# OUTPUT: none
# RETURN: OK_CODE
# ----------------------------------------------------------------------------
sub restore_default_signals_handlers()
{
	delete($SIG{INT});
	delete($SIG{KILL});
	delete($SIG{TERM});
	delete($SIG{ABRT});
	delete($SIG{ALRM});
	delete($SIG{HUP});
	delete($SIG{QUIT});
	return (OK_CODE);
}


# ----------------------------------------------------------------------------
# SUBROUTINE_NAME: run_command
# ABSTRACT: This function runs a command locally on the system.
# GLOBAL_MODIFIED: none
# INPUT: name of the command to run
# OPTIONAL INPUT: set to 1 if the output of the command is needed
# OUTPUT: return code of the command
# OUTPUT: if an array is expected for the result, then the stdout is also returned
# RETURN: OUTPUT if ok, KO_CODE else
# ----------------------------------------------------------------------------
sub run_command($)
{
	my ($command) = @_;

	# remove potential ; at the end of the command
	$command =~ s/([^\\]);+$/$1/;

	# force LANG to C and add a return code
	$command = "LANG=C ${command};echo \"RETURN CODE=\$?\"";

	# open a pipe to send input to the command and get output from it
	my $pid = open3(*CMD_IN, *CMD_OUT, *CMD_ERR, $command);
	if ($pid < 0) {
		return (KO_CODE);
	}
	close(CMD_IN);

	# Retrive the outputs
	my $selector = IO::Select->new();
	$selector->add(*CMD_ERR, *CMD_OUT);

	# get command ouput
	my $stdout_string = "";
	my $stderr_string = "";
	while (my @ready = $selector->can_read) {
		foreach my $fh (@ready) {
			if (fileno($fh) == fileno(CMD_ERR)) {
				$stderr_string .= scalar <CMD_ERR>;
			} else {
				$stdout_string .= scalar <CMD_OUT>;
			}
			$selector->remove($fh) if eof($fh);
		}
	}
	close(CMD_ERR);
	close(CMD_OUT);

	# Convert output and errors strings to arrays -> do not skip ending \n lines in case there are several of them.
	# This is why we have to pass -1 as split parameter.
	my @output = split(/\n/, $stdout_string, -1);
	pop(@output); # remove last "\n" entry
	my @errors = split(/\n/, $stderr_string, -1);
	pop(@errors); # remove last "\n" entry

	# wait for shell termination
	my $child_pid    = wait();
	my $child_status = $?;
	if ($child_pid < 0) {
		print("WARNING, Could not get child pid !!!!\n");
	} elsif ($child_pid != $pid) {
		print("WARNING, got sigchild from another child !!!!\n");
	}

	my $rc = 0;

	# retrieve the execution return code value
	if ($output[-1] =~ /^RETURN CODE=(\d+)$/) {
		$rc = $1;

		# remove the last RETURN CODE line at the end of the output
		pop(@output);
		$stdout_string = substr($stdout_string, 0, rindex($stdout_string, "RETURN CODE="));
	} elsif ($output[-1] =~ s/RETURN CODE=(\d+)$//) {
		$rc = $1;

		# remove the last RETURN CODE line at the end of the output
		$stdout_string = substr($stdout_string, 0, rindex($stdout_string, "RETURN CODE="));
	}

	if (scalar(@errors) > 0) {
		printf(STDERR join("\n", @errors) . "\n");
	}
	return ($rc, $stdout_string) if (wantarray);
	return ($rc);
}

# ----------------------------------------------------------------------------
# SUBROUTINE_NAME: nim_umount
# ABSTRACT: This function umount a resource on the local system
# GLOBAL_MODIFIED: none
# INPUT: directory to umount
# OPTIONAL INPUT: set to 1 if the mount must be forced
# OUTPUT: none
# RETURN: OK_CODE if ok, KO_CODE else
# ----------------------------------------------------------------------------
sub nim_umount($$)
{
	my ($resources_mounted, $force) = @_;

	my ($i, $mnt_pnt, $tmp, $cmd, $object, @tmp_mount, $rc);
	$rc = 0;
	@tmp_mount = split(/ /, $resources_mounted);
	if (scalar(@tmp_mount) == 0) {
		return (OK_CODE);
	}

	# look for the specified <dir>
	foreach $i (@tmp_mount) {

		# separate stanza
		$mnt_pnt = (split(/\|/, $i))[1];
		$object  = (split(/\|/, $i))[0];
		if (($mnt_pnt =~ /\s+/) or ($object =~ /\s+/)) {
			return (KO_CODE);
		}

		# umount it, but first perform a sync to make
		# sure that nothing in the mount is still thought to be busy.
		if ($rc = &run_command(SYNC)) {
			return (KO_CODE);
		}
		if ($resources_mounted) {
			if (defined($force)) {

				# We were told to force umount this directory.
				# Try to umount without the force first.
				$cmd = UNMOUNT." $mnt_pnt";
				if ($rc = &run_command($cmd)) {
					$cmd = UNMOUNT." -f $mnt_pnt";
					if ($rc = &run_command($cmd)) {
						return (KO_CODE);
					}
				}

				# process mounted dir - normal
			} else {
				$cmd = UNMOUNT." $mnt_pnt";
				if ($rc = &run_command($cmd)) {
					return (KO_CODE);
				}
			}
		}
		if (defined($mnt_pnt) && ($mnt_pnt !~ /\*/) && ($mnt_pnt !~ /\s+/)) {
			rmdir($mnt_pnt);
			$resources_mounted =~ s/$i\s*//;
			$rc = 0;
		}

	}
	if ($rc) {
		return (KO_CODE);
	}

	# new list reflects the mounts that are still active

	return (OK_CODE);
}

# ----------------------------------------------------------------------------
# SUBROUTINE_NAME: nim_mount
# ABSTRACT: This function mount a NIM resource on the local system.
#	It uses the NFS version defined in NIM
# GLOBAL_MODIFIED: none
# INPUT: $server_nim_object = image server NIM object
# INPUT: $objet = <server_hostname>:<path to mount>
# INPUT: $access_pnt = name of the resources once mounted
# OUTPUT: $resources_mounted
# RETURN: OK_CODE if ok, KO_CODE else
# ----------------------------------------------------------------------------
sub nim_mount($$$$$)
{

	# $objet = <server>:<path a monter>
	# $access_pnt = <mksysb name once mounted>
	my ($server_nim_object, $object, $access_pnt, $object_name) = @_;

	my $i = 0;
	my ($mnt_params, $automount_flag, $nfs4_access, $nfs4_mount, $nfs4_link, $cmd, $rc, @tmp, $link_dir, $link_file);
	my $resources_mounted="";
	
	my $NFS4_MNTDIR  = "/tmp/_.nim_mounts._";
	my $NFS4_LINKDIR = "$NFS4_MNTDIR/hostlinks";
	
	
	# check for unnecessary mounts
	if (-e $access_pnt) {
		return (KO_CODE);
	}

	# Detect if the resource must be mounted using NFSv4
	if ($EXEC_NAME ne MASTER) {
		if ($server_nim_object eq MASTER) {
			$cmd = "$NIM_CMD -l -M $object_name";
		} else {
			$cmd = "$NIM_CMD -m $object";
		}
	} else {
		$cmd = LSNIM." -M $object_name";
	}
	($rc, $nfs4_access) = &run_command($cmd);

	if ($nfs4_access ne "") {

		#=================================
		# NFSv4 mount
		#=================================
		@tmp        = split(/ /, $nfs4_access);
		$nfs4_mount = $tmp[0];
		$nfs4_link  = $tmp[1];
		$access_pnt = $nfs4_mount . $access_pnt;
	}

	if (!-r $access_pnt) {
		if ($nfs4_access eq "") {
			mkdir($access_pnt);
		} else {
			$cmd = LN." -sf \"" . $nfs4_mount . $nfs4_link . "\" " . $access_pnt;
			if ($rc = &run_command($cmd)) {
				return ($rc);
			}
		}
	} else {

		# This access point does already exist and we can read it ... just use it but this should never happen!
		return (OK_CODE);
	}

	# mount the "object" (either local CDROM, remote dir, or local dir)
	if ($object =~ /.*:.*/) {
		$mnt_params = "-ohard,intr -vnfs";
	}

	if ($nfs4_access eq "") {

		# NFSv3
		$cmd = MOUNT." $mnt_params $object $access_pnt";
		if ($rc = &run_command($cmd)) {
			return ($rc);
		}

		if ($resources_mounted eq "") {
			$resources_mounted = "$object|$access_pnt";
		} else {
			$resources_mounted = "$object|$access_pnt $resources_mounted";
		}
	} else {

		# NFSv4
		$link_dir  = "$NFS4_LINKDIR/$nfs4_mount";
		$link_file = "$link_dir/linkfile";
		mkpath($link_dir);
		$cmd = "/usr/bin/touch $link_file; ".LN." $link_file \"${NFS4_LINKDIR}/$access_pnt\"";
		if ($rc = &run_command($cmd)) {
			return ($rc);
		}

		# create exit script for umount
		if ($resources_mounted eq "") {
			$resources_mounted = "$object|$nfs4_mount";
		} else {
			$resources_mounted = "$object|$nfs4_mount $resources_mounted";
		}
	}

	return (OK_CODE, $resources_mounted);
}

# ----------------------------------------------------------------------------
# SUBROUTINE_NAME: translate
# ABSTRACT: This function printf an error message
# 	using c_errmsg NIM method. If it fails, return
# 	the default message provided.
# GLOBAL_MODIFIED:
# INPUT: $message
# INPUT: Default string to display in case of error
# INPUT: args for all the %s in the message
# OUTPUT: Print an error string on stderr
# RETURN: OK_CODE if OK
# ----------------------------------------------------------------------------
sub translate($$@)
{
	my ($message, $default, @args) = @_;
	my ($arg, $tmp, $rc, $string);
	my $set = $message->[0];
	my $id  = $message->[1];
	foreach $tmp (@args) {
		$arg .= "\"$tmp\" ";
	}
	my $cmd = C_ERRMSG." $id 1 $arg";
	($rc, $string) = &run_command($cmd);
	if (($string eq "") or ($string =~ /error on system call;/)) {
		$arg =~ s/\" \"/\",\"/g;
		printf STDERR $default, eval($arg);
		return (OK_CODE);
	} else {
		$string =~ s/\n$//;
		printf STDERR $string;
	}
	return (OK_CODE);
}

# ----------------------------------------------------------------------------
# SUBROUTINE_NAME: lsnim_command
# ABSTRACT: lsnim the object
# GLOBAL_MODIFIED: none
# INPUT: $object_name: A name of the NIM object.
# OUTPUT: $lsnim_info: hash reference to the result.
# RETURN: OK_CODE if ok, KO_CODE else
# ----------------------------------------------------------------------------
sub lsnim_command($$)
{
	my ($object_name, $lsnim_info) = @_; # Always master ...
	my ($cmd, $rc, $cmdHandle, $name, $val, $elem, $stdout, $line, $key);
	
	# all fieds :
	$cmd = "$LSNIM_CMD $object_name ";

	# Execute the lsnim command generated above
	($rc, $stdout) = &run_command($cmd);
	if ($rc) {
		return ($rc);
	}
	if ($stdout !~ /$object_name/) {
		return (KO_CODE);
	}
	foreach $line (split(/\n/, $stdout)) {
		if ($line =~ /:/) {
			$line =~ s/[: ]//g;
			$key = $line;
		}
		($name, $val) = split(/=/, $line);
		if (defined($val)) {
			$name =~ s/^\s*(.*?)\s*$/$1/;
			$val  =~ s/^\s*(.*?)\s*$/$1/;
			$lsnim_info->{$key}->{$name} = $val;
		}
	}

	return (OK_CODE);

}

# ----------------------------------------------------------------------------
# SUBROUTINE_NAME: compare_releases
# ABSTRACT: This function compares 2 aix releases
# GLOBAL_MODIFIED: none
# INPUT: $new = release 1
# INPUT: $old = release 2
# OUTPUT: none
# RETURN: 0 if matches
#			> 0 if bigger
#			< 0 else
# ----------------------------------------------------------------------------
sub compare_releases($$)
{
	my ($new, $old) = @_;
	if ($new !~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/) {
		return (-2);
	}
	my $a1 = $1;
	my $b1 = $2;
	my $c1 = $3;
	my $d1 = $4;
	if ($old !~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/) {
		return (-2);
	}
	my $a2 = $1;
	my $b2 = $2;
	my $c2 = $3;
	my $d2 = $4;
	if ($a1 <=> $a2) { return ($a1 <=> $a2) }
	if ($b1 <=> $b2) { return ($b1 <=> $b2) }
	if ($c1 <=> $c2) { return ($c1 <=> $c2) }
	if ($d1 <=> $d2) { return ($d1 <=> $d2) }
	return (0);
}

sub create_tmpdir($)
{
	my ($basedir) = @_;
	
	my $tmpl_tempdir 	= $basedir."_$$" . "_XXXX";
	my $tmpdir	= tempdir($tmpl_tempdir, DIR => TMPDIR);
	if ($tmpdir eq "") {
		return (undef);
	}
	mkpath($tmpdir);
	return $tmpdir;
}


# DO not touch this line !!
1;
__END__
