# @(#)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 ; } else { $stdout_string .= scalar ; } $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 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 = : # INPUT: $access_pnt = name of the resources once mounted # OUTPUT: $resources_mounted # RETURN: OK_CODE if ok, KO_CODE else # ---------------------------------------------------------------------------- sub nim_mount($$$$$) { # $objet = : # $access_pnt = 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__