#!/usr/bin/perl
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
# bos720 src/bos/usr/lib/nim/methods/c_loadiso.pl 1.10 
#  
# 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 
# @(#)44        1.10  src/bos/usr/lib/nim/methods/c_loadiso.pl, cmdnim, bos720 11/25/08 07:53:10

use strict;
use warnings;
use Getopt::Long;
use Carp;
use IPC::Open3;
use IO::Select;
use File::Path;     # Used for mkpath rmtree
use File::Basename; # Used for basename
use File::Temp qw / tempfile tempdir /;

use lib '/usr/lpp/bos.sysmgt/nim/perl';
use NIMUtils;
use NIMUtils ':rc_csts'; # KO_CODE OK_CODE

# The following line is dynamically replaced at compiled time
# with the list of all IDs needed to display i18n messages.
# See the Makefile for more details.
# === START
use constant {
   ERR_OVF_REFERS	=> [1,449],
   ERR_OVF_RUNCMD	=> [1,450],
   ERR_OVF_BAD_OPTION	=> [1,451],
   ERR_OVF_MISSING_PARAM	=> [1,452],
   ERR_OVF_UNKN_OBJECT	=> [1,453],
   ERR_OVF_OPEN	=> [1,454],
   ERR_OVF_FILE_NOTFOUND	=> [1,455],
   ERR_OVF_LOCATION_NOTFOUND	=> [1,456],
   ERR_OVF_RESTORE	=> [1,457],
   ERR_OVF_UNMOUNT	=> [1,458],
   ERR_OVF_FS_EXISTS	=> [1,459],
   ERR_OVF_COLLECT_INFO	=> [1,460],
   ERR_OVF_INVALID_ENVELOPE	=> [1,461],
   ERR_OVF_DEFAULT_VALUES	=> [1,462],
   ERR_OVF_NOSPACE_FOR_REPOSITORY	=> [1,468],
   MSG_M_MKOVF_VM_SYNTAX	=> [479,492],
   MSG_M_RMOVF_VM_SYNTAX	=> [479,493],
   ATTR_OVF_VM	=> [5,820],
   H_ATTR_OVF_VM	=> [5,821],
   ATTR_OVF_ENVELOPE	=> [5,824],
   H_ATTR_OVF_ENVELOPE	=> [5,825],
   ATTR_OVF_SOURCE	=> [5,826],
   H_ATTR_OVF_SOURCE	=> [5,827],
   ATTR_OVF_ENVELOPE_UPDATE	=> [5,834],
   H_ATTR_OVF_ENVELOPE_UPDATE	=> [5,835],
   VERBOSE_ATTR_OVF_VM	=> [11,380],
   VERBOSE_ATTR_OVF_ENVELOPE	=> [11,381],
   VERBOSE_ATTR_OVF_SOURCE	=> [11,382],
   VERBOSE_ATTR_OVF_ENVELOPE_UPDATE	=> [11,384],
};
# === END

# Global variables
use vars qw($resources_mounted
	    %lsnim_info
	    $image_name
	    $device_name
	    $tmpdir
);


# return code values
use constant ERR_RC_RUNCMD	=> 2;
use constant ERR_RC_MOUNT	=> 3;
use constant ERR_RC_UNMOUNT	=> 4;
use constant ERR_RC_BAD_OPTION	=> 5;
use constant ERR_RC_MISSING_PARAM	=> 6;
use constant ERR_RC_COLLECT_IMAGE_INFO	=> 7;
use constant ERR_RC_CANT_CREATE_ISO	=> 8;
use constant ERR_RC_CANT_CREATE_TMPDIR	=> 9;
use constant ERR_RC_NOSPACE_FOR_REPOSITORY	=> 10;

# List of the AIX commands used
my $LSSLOT	= "/usr/sbin/lsslot";
my $LSREP	= "/usr/ios/cli/ioscli lsrep";
my $MKREP	= "/usr/ios/cli/ioscli mkrep";
my $CHREP	= "/usr/ios/cli/ioscli chrep";
my $LSVOPT	= "/usr/ios/cli/ioscli lsvopt";
my $LSSP	= "/usr/ios/cli/ioscli lssp";
my $MKVOPT	= "/usr/ios/cli/ioscli mkvopt";
my $RMVOPT	= "/usr/ios/cli/ioscli rmvopt";
my $MKVDEV	= "/usr/ios/cli/ioscli mkvdev";
my $RMVDEV	= "/usr/ios/cli/ioscli rmvdev";
my $LOADOPT	= "/usr/ios/cli/ioscli loadopt";

# ----------------------------------------------------------------------------
# SUBROUTINE_NAME: cleanup_exit
# ABSTRACT: This function cleanups all the resources used by this script and exit
# GLOBAL_MODIFIED: none
# INPUT: none
# OUTPUT: none
# RETURN: KO_CODE
# ----------------------------------------------------------------------------
sub cleanup_exit()
{

	&NIMUtils::restore_default_signals_handlers();
	cleanup();
	if (-d $tmpdir) {
		chdir('/'); # avoid the warning on stderr
		rmtree($tmpdir);
	}
	exit(KO_CODE);
}

# ----------------------------------------------------------------------------
# SUBROUTINE_NAME: remove_vopt
# ABSTRACT: This function checks that the virtual optical media is created
#		and remove it.
# GLOBAL_MODIFIED: none
# INPUT: none
# OUTPUT: none
# RETURN: OK_CODE
# ----------------------------------------------------------------------------
sub remove_vopt()
{
	my ($rc, $stdout, $cmd);
	
	if ($image_name ne "") {
		$cmd = "$RMVOPT -name $image_name";
		# run the command but don't check the returned values
		($rc, $stdout) = &NIMUtils::run_command($cmd);
		$image_name = "";
	}
	
	return(OK_CODE);
}

# ----------------------------------------------------------------------------
# SUBROUTINE_NAME: remove_vadapt
# ABSTRACT: This function checks that the virtual drive exists
#		and remove it.
# GLOBAL_MODIFIED: none
# INPUT: none
# OUTPUT: none
# RETURN: OK_CODE
# ----------------------------------------------------------------------------
sub remove_vadapt()
{
	my ($rc, $stdout, $cmd);
	
	if (defined($device_name) && ($device_name !~ /^\s*$/)) {
		$cmd = "$RMVDEV -vtd $device_name";
		# run the command but don't check the returned values
		($rc, $stdout) = &NIMUtils::run_command($cmd);
		$device_name = "";
	}
	
	return(OK_CODE);
}

# ----------------------------------------------------------------------------
# SUBROUTINE_NAME: cleanup
# ABSTRACT: This function cleanup all the resources used by this script
# GLOBAL_MODIFIED: none
# INPUT: name of mount point to cleanup
# INPUT: Did the resource use -o allocate at the time of allocation
# INPUT: Did the resource is a mounted filesystem
# OUTPUT: none
# RETURN: OK_CODE if ok, ERR_RC_RUNCMD else
# ----------------------------------------------------------------------------
sub cleanup()
{

	my ($rc);

	# 1 - umount the resource
	&NIMUtils::nim_umount($resources_mounted, 1);
	
	# 2 - remove the virtual optical drive if necessary
	&remove_vadapt($device_name);
	
	# 3 - remove the virtual optical device if necessary
	&remove_vopt($image_name);
	
	return(OK_CODE);
}

# ----------------------------------------------------------------------------
# SUBROUTINE_NAME: mount_iso_in_vopt_drive
# ABSTRACT: This function does all the real work to mount the VOPT device.
# GLOBAL_MODIFIED:
# INPUT: slot_id
# INPUT: iso_location
# OUTPUT: none
# RETURN: OK_CODE if OK
# ----------------------------------------------------------------------------
sub mount_iso_in_vopt_drive ($$)
{
	my($slot_num, $iso_location, $vopt_obj_name)=@_;
	
	my $base_name		= basename($iso_location);
	my $server_nim_object	= "master";
	my $mount_pnt 		= $tmpdir . "/$base_name";
	my ($rc, $cmd, $stdout, $image_location);
	my ($fn_imgname, $tmpl_imgname, $tmpl_imgsuffix);
	
	#-1- Mount the resource allocated by the server method
	# The ISO is always on the master
	$rc = &NIMUtils::lsnim_command($server_nim_object, \%lsnim_info);
	if ($rc) {
		translate(ERR_OVF_RUNCMD, "%s: The command \"%s\" failed (rc=%s).\n", "mount_iso_in_vopt_drive", "/usr/sbin/nimclient -l -l $server_nim_object", $rc);
		&cleanup();
		return (ERR_RC_RUNCMD);
	}
	
	my %server_info = %{$lsnim_info{$server_nim_object}};
	my $server_hostname = (split(/ /, $server_info{"if1"}))[1];
	($rc, $resources_mounted) =&NIMUtils::nim_mount($server_nim_object, "$server_hostname:$iso_location", "$mount_pnt", $vopt_obj_name);
	if ($rc == OK_CODE) {
		$image_location = $mount_pnt;
	} else {
		&cleanup();
		translate(ERR_OVF_RUNCMD, "%s: The command \"%s\" failed (rc=%s).\n", "mount_iso_in_vopt_drive", "mount $server_hostname:$iso_location on $mount_pnt", $rc);
		return (ERR_RC_COLLECT_IMAGE_INFO);
	}
	
	#-2- Get the iso file size
	my @sb = stat($image_location);
	# Note: put this size in mb to be able to compare it to the other outputs
	my $image_size = int(($sb[7] / (1024 * 1024)) + 1);
	
	#-3- Check the repository exists and has enough free space
	$cmd = "$LSREP 2>&1";
	($rc, $stdout) = &NIMUtils::run_command($cmd);
	my $repository_found = 0;
	if ($rc ==0 and $stdout !~ /The DVD repository has not been created yet/) {
	
		# the repository exists
		my @tmp = split(/\n/, $stdout);
		
		# search for it in the lsrep output and check its size
		foreach my $line (@tmp) {
			#$ lsrep
			#Size(mb) Free(mb) Parent Pool         Parent Size      Parent Free
			#      62       62 rootvg_clients            69888            31424
			if ($line =~ /^\s+(\d+)\s+(\d+)\s+(\S+)\s+(\d+)\s+(\d+)$/) {
				# a repository was found => check its size
				my $rep_free = $2;
				if ($rep_free < $image_size) {
					my $extend_size = $image_size - $rep_free;
					$cmd = "$CHREP -size " . $extend_size . "M";
					($rc, $stdout) = &NIMUtils::run_command($cmd);
					if ($rc) {
						# this repository can not be extended, only one can be created => failed
						&NIMUtils::translate(ERR_OVF_RUNCMD, "%s: The command \"%s\" failed (rc=%s).\n", "mount_iso_in_vopt_drive", $cmd, $rc);
						&cleanup();
						return (ERR_RC_RUNCMD);
					}
				}
				$repository_found = 1;
				last;
			}
		}
	}	
	if (!$repository_found) {
	
		#-4- Create a repository -> check the free space in the storage pools
		$cmd = "$LSSP";
		($rc, $stdout) = &NIMUtils::run_command($cmd);
		if ($rc) {
			&NIMUtils::translate(ERR_OVF_RUNCMD, "%s: The command \"%s\" failed (rc=%s).\n", "mount_iso_in_vopt_drive", $cmd, $rc);
			&cleanup();
			return (ERR_RC_RUNCMD);
		}
		my $candidate_pool = undef;
		my @tmp = split(/\n/, $stdout);
		foreach my $l (@tmp) {
			if ($l =~ /^(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)/) {
				if ($3 >= $image_size) {
					$candidate_pool = $1;
					$cmd = "$MKREP -sp " . $candidate_pool . " -size " . $image_size . "M";
					($rc, $stdout) = &NIMUtils::run_command($cmd);
					if ($rc) {
						&NIMUtils::translate(ERR_OVF_RUNCMD, "%s: The command \"%s\" failed (rc=%s).\n", "mount_iso_in_vopt_drive", $cmd, $rc);
						&cleanup();
						return (ERR_RC_RUNCMD);
					}
					last;
				}
			}
		}
		
		if (!defined($candidate_pool)) {
			&NIMUtils::translate(ERR_OVF_NOSPACE_FOR_REPOSITORY, "%s: There is not enough space left to create a virtual optical disk repository.\n", "mount_iso_in_vopt_drive");
			&cleanup();
			return (ERR_RC_NOSPACE_FOR_REPOSITORY);
		}
	}
	
	#-5- generate a unique name for the iso_file based on the base_name
	#            "nim_vopt_iso_XXXXX_<basename>.iso
	$tmpl_imgname = "nim_vopt_iso_XXXXX";
	$tmpl_imgsuffix = "_" . $base_name;
	if ($tmpl_imgsuffix !~ /.iso$/) {
		$tmpl_imgsuffix .= ".iso";
	}
	# the lenght of the iso can not exceed 38 characters
	if (length($tmpl_imgsuffix) > 19) {
		my $start = substr($tmpl_imgsuffix, 0, 14);
		$tmpl_imgsuffix = $start . ".iso";
	}
	(undef, $fn_imgname) = tempfile($tmpl_imgname,
						SUFFIX => "$tmpl_imgsuffix",
						DIR => "/var/vio/VMLibrary",
						OPEN => 0);
	if ($fn_imgname eq "") {
		&NIMUtils::translate(ERR_OVF_RUNCMD,
			  "%s: The command \"%s\" failed (rc=%s).\n",
			  "mount_iso_in_vopt_drive", "tempfile", ERR_RC_CANT_CREATE_ISO);
		&cleanup();
		return (ERR_RC_RUNCMD);
	}
	$image_name = basename($fn_imgname);
	
	#-6- Create the Virtual optical device
	$cmd = "$MKVOPT -name $image_name -file $image_location -ro";
	($rc, $stdout) = &NIMUtils::run_command($cmd);
	if ($rc) {
		&NIMUtils::translate(ERR_OVF_RUNCMD, "%s: The command \"%s\" failed (rc=%s).\n", "mount_iso_in_vopt_drive", $cmd, $rc);
		&cleanup();
		unlink($fn_imgname);
		return (ERR_RC_RUNCMD);
	}
	#-7- umount the resource
	&NIMUtils::nim_umount($resources_mounted, 1);
	
	#-8- Grep the vhost
	$cmd = "$LSSLOT -c slot | grep -- -C$slot_num";
	($rc, $stdout) = &NIMUtils::run_command($cmd);
	if ($rc) {
		&NIMUtils::translate(ERR_OVF_RUNCMD, "%s: The command \"%s\" failed (rc=%s).\n", "mount_iso_in_vopt_drive", $cmd, $rc);
		&remove_vopt();
		return (ERR_RC_RUNCMD);
	}
	my $vhost_id = (split(/\s+/, $stdout))[-1];
	if (! defined($vhost_id) || ($vhost_id !~ /vhost/)) {
		&NIMUtils::translate(ERR_OVF_RUNCMD, "%s: The command \"%s\" failed (rc=%s).\n", "mount_iso_in_vopt_drive", $cmd, $rc);
		&remove_vopt();
		return (ERR_RC_RUNCMD);
	}
	#-9- Create the Virtual Adapter
	# root equiv "mkdev -t fbopt -s vtdev -c virtual_target -p $vhost_id"
	$cmd = "$MKVDEV -fbo -vadapter $vhost_id";
	($rc, $stdout) = &NIMUtils::run_command($cmd);
	if ($rc) {
		&NIMUtils::translate(ERR_OVF_RUNCMD, "%s: The command \"%s\" failed (rc=%s).\n", "mount_iso_in_vopt_drive", $cmd, $rc);
		&remove_vopt();
		return (ERR_RC_RUNCMD);
	}
	if ($stdout =~ /^(\S+)\s+Available/) {
		$device_name = $1;
	}
	if (! defined($device_name) || ($device_name =~ /^\s*$/)) {
		&NIMUtils::translate(ERR_OVF_RUNCMD, "%s: The command \"%s\" failed (rc=%s).\n", "mount_iso_in_vopt_drive", $cmd, $rc);
		&remove_vopt();
		return (ERR_RC_RUNCMD);
	}
	
	#-10- Load the Virtual Optical Device
	$cmd = "$LOADOPT -vtd $device_name -disk $image_name";
	($rc, $stdout) = &NIMUtils::run_command($cmd);
	if ($rc) {
		&NIMUtils::translate(ERR_OVF_RUNCMD, "%s: The command \"%s\" failed (rc=%s).\n", "mount_iso_in_vopt_drive", $cmd, $rc);
		&remove_vopt();
		&remove_vadapt();
		return (ERR_RC_RUNCMD);
	}
	
	# All is OK, the device name must be printed on stdout
	print("$device_name\n");
	
	return OK_CODE;
}


# ----------------------------------------------------------------------------
## BEGIN MAIN ##
# ----------------------------------------------------------------------------

# options: -a slot_num=<remote_slot_num> -a location=<path_to_iso_file>
# Parse the arguments
my %options;
GetOptions("a=s" => \%options)
  or do {
	# invalid option parameter
	&NIMUtils::translate(ERR_OVF_BAD_OPTION, "%s: Invalid option.\n", "main");
	exit(ERR_RC_BAD_OPTION);
  };

my $slot_num		= $options{'slot_num'};
my $iso_location	= $options{'location'};
my $vopt_obj_name	= $options{'vopt_iso'};

$image_name	= "";

# Check that mandatory arguments are here
&NIMUtils::set_signals_handler(\&cleanup_exit);

if ((!defined($slot_num) or ($slot_num eq ""))
	|| (!defined($iso_location)  or ($iso_location  eq ""))
	|| (!defined($vopt_obj_name)  or ($vopt_obj_name  eq ""))
	)
{
	&NIMUtils::translate(ERR_OVF_MISSING_PARAM, "%s: A mandatory command parameter is missing on the command line.\n", "main");
	exit(ERR_RC_MISSING_PARAM);
}

$resources_mounted = "";

# Generate and create a unique directory
$tmpdir	= &NIMUtils::create_tmpdir("nim_tmp");
# Change the working directory to $tmpdir
chdir($tmpdir);
my $rc = &mount_iso_in_vopt_drive($slot_num, $iso_location, $vopt_obj_name);
#Change again before deleting.
if ($tmpdir) {
	chdir('/'); # avoid the warning on stderr
	rmtree($tmpdir);
}

exit($rc);

