#!/usr/bin/perl -Tw
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
# bos720 src/bos/usr/sbin/install/suma/lib/SUMA/FixInventory.pm 1.4 
#  
# Licensed Materials - Property of IBM 
#  
# Restricted Materials of IBM 
#  
# COPYRIGHT International Business Machines Corp. 2004 
# 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 SUMA::FixInventory;

# code starts after '=cut'

=head1 NAME

SUMA::FixInventory - A module to display a system's 
fix inventory information in multiple formats.

=head1 SYNOPSIS

   use SUMA::FixInventory qw/get_inv_sys /;

   #
   # Fix inventory
   #
   
   $format="instfix";
   $instfix_flags="Tci";
   $clients="localhost";
   
   ($fix_list_fh,$pid)=get_inv_sys($format,$instfix_flags,"localhost");
   # ($fix_list_fh,$pid)=get_inv_sys("localhost");
   print (<$fix_list_fh>);
   close $fix_list_fh;

   waitpid($pid,0);
   my $rc=$? >> 8;

   # exit $rc;

   # or

   $fix_list_fh=get_inv_sys($format,$instfix_flags,@clients);
   # $fix_list_fh=get_inv_sys(@clients);
   print (<$fix_list_fh>);
   close $fix_list_fh;

   # exit 0;

=cut


use strict;

use Getopt::Std;
use File::Spec;
use HTML::Entities qw/encode_entities/;

# SUMA modules
use lib qw(/usr/suma/lib);
use SUMA::GConfig;
use SUMA::Util qw(ckPrivate writeXml);
use SUMA::Messenger qw(
                        MSG_CANT_FORK
                        MSG_GENERIC
                        MSG_NOT_MASTER
                        MSG_NOT_ROOT
                        MSG_NO_HOSTNAME
                         );


require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw( get_inv_sys );




# Default Values
my $user="";
my $nimMaster=0;                        # Whether this is a nim master
my $hostname="localhost";

# Globals
my $format;
my $instfix_flags;
my @properties;
my $xml_arrayRef;
my @INSTFIX;

# Error Codes
my $ecFailure=1;                        # General error including usage
my $ecPartialFailure=2;                 # Some clients returned information others did not





=head1 METHODS

=over 4

=item get_inv_sys CLIENT_STR

=item get_inv_sys FORMAT_STR, FLAGS_STR, CLIENT_STR

Outputs the software inventory of a system. The CLIENT_STR must be
"localhost".  FORMAT_STR is the format the output should be in; this is either
"instfix" or "xml".  FLAGS_STR is the instfix flags to pass to the instfix command.  
The FLAGS_STR's value is ignored if the format is xml, though a value must
still be passed in.  In the case where only CLIENT_STR is passed in then 
the output will be the same as if FLAGS_STR="Tci" and FORMAT_STR="instfix".
The value returned is either a file handle to the output or an array holding
the file handle and then the pid of the process writing the output.
The pid is returned so you may get a return code (see synopsis for example).
The type of return value is determined by looking at what you
are expecting as a return value either a scalar or an array.  

=cut
sub get_inv_sys
   {
   my @clients;
   my $client;
   my $writer;
   my $reader;
   
   if (scalar(@_) == 1)
      {
      $format="lslpp";
      $instfix_flags="Tci";
      }
   else
      {
      $format=shift; if (! defined($format)) {$format="instfix";}
      $instfix_flags=shift; if (! defined($instfix_flags)) {$instfix_flags="Tci";}
      }
   @clients=@_; if (! defined($clients[0])) {@clients=("localhost")}
   
   if ((scalar(@clients) > 1) && ($clients[0] ne "localhost")) {exit $ecFailure;}
   
   # Get current user
   $user=`/usr/bin/whoami`;
   chomp($user);
   
   # Check for nim master
   system("/usr/bin/grep NIM_CONFIGURATION=master /etc/niminfo > /dev/null 2>&1 ");
   if ($? == 0) {$nimMaster=1;}

   # Get hostname
   $hostname=`/usr/bin/hostname`;
   if (($? != 0) && ($format eq "xml")) 
      {
      mesg(LVL_ERROR, MSG_NO_HOSTNAME);
      exit $ecFailure;
      }
   chomp($hostname);

   my $returnCode=0;                                # set return code
   my($wroteXmlHead)=0;

   # Pipe the content out.  Caller deals with it.
   pipe($reader, $writer);
   if(my $pid = fork) 
      {
      # Parent
      close $writer;
      if (wantarray())
         {
         return ($reader,$pid);
         }      
      else
         {
         return $reader;
         }
      }
   else 
      {
      # Child - write content into the pipe and then exit.
      unless(defined($pid)) 
         {
         mesg(LVL_ERROR, MSG_CANT_FORK);
         mesg(LVL_DEBUG, MSG_GENERIC, "Fork failed piping lslpp content out of " . (caller(0))[3]);
         return undef;
         }
      close $reader;

      foreach $client (@clients)
         {
         $client=_clean($client);
         my $rc=_getFixInfo($client);
         if (($rc == 1) && ($returnCode == 0)) {$returnCode=$ecFailure;}
         if (($rc == 0) && ($returnCode == $ecFailure)) {$returnCode=$ecPartialFailure;}    

         # Output Client Information
         if ($rc == 0) 
            {
            GET_CASE1:
               {
               ($format eq "instfix") && 
                  do
                  {
                  _writeInstfix($writer);
                  last GET_CASE1;
                  };
 
               ($format eq "xml") && 
                  do
                  {
                  if(! $wroteXmlHead)
                     {
                     _writeXmlHead($writer);
                     $wroteXmlHead=1;
                     }
                  my $xml_arrayRef=_createXmlStructure($client);   
                  writeXml($writer,$xml_arrayRef, 2);
                  last GET_CASE1;
                  };         
               } # end GET_CASE1
            } # end if $rc == 0 
         } # end foreach $client 

      # Write the end text if the output was in xml.
      if (($format eq "xml") && ($returnCode != 1)) {_writeXmlTail($writer);}

      close $writer;
      exit($returnCode);
      }
   } # end get_inv_sys




# Function: _getFixInfo(client_str)
# Purpose: To acquire the instfix information needed for fix inventory.
# Side effects: Variable INSTFIX contains the output from the 
#               instfix commands.
# Returns: 0 success, $ecFailure
sub _getFixInfo
   {
   ckPrivate();
   my $client=shift;
   if (! defined($client)) {return $ecFailure};
   $instfix_flags=_clean($instfix_flags);
   if (! defined($instfix_flags)) {return $ecFailure};
   if (($client eq "localhost") || ($client eq $hostname))
      {
      mesg(LVL_DEBUG, MSG_GENERIC, "Getting instfix output for the localhost.");      
      @INSTFIX=`/usr/sbin/instfix -$instfix_flags 2>&1 `;
      if ( $? != 0) {return $ecFailure;}
      }
   else
      { 
      mesg(LVL_DEBUG, MSG_GENERIC, "Getting instfix output for nim client: ",$client,".");
      if ($user ne "root")
         {
         mesg(LVL_ERROR, MSG_NOT_ROOT);
         return $ecFailure;
         }
      if (! $nimMaster)
         {
         mesg(LVL_ERROR, MSG_NOT_MASTER);
         return $ecFailure;
         }
      if ($format eq "xml")
         {
         $instfix_flags="Tci";
         }
      @INSTFIX=`/usr/sbin/nim -o instfix -a instfix_flags=$instfix_flags $client 2>&1 `; 
      if ( $? != 0) {return $ecFailure;}
      }
   if ($format eq "xml")
      {
      @INSTFIX=map {$_->[0]}	# does a sort of instfix lines by the 2nd field (fileset)
         sort {$a->[1] cmp $b->[1]}
	 map { [$_, (split /:/)[1] ]}
	 @INSTFIX;
      }
   return 0
   } # end _getFixInfo





# Function: _writeInstfix(writer_fh)
# Purpose: To write the contents of the variable INSTFIX to the filehandle passed in.
# Side effects: Writes to stream.
# Returns: 0 success
sub _writeInstfix
   {  
   ckPrivate();
   my $writer=shift;    
   print ($writer @INSTFIX);
   } # end _writeInstfix





# Function: _writeXmlHead(writer_fh)
# Purpose: To write the xml header and the first two tags to the filehandle passed in.
# Side effects: Writes to filehandle.
# Returns: 0 success
sub _writeXmlHead
   {
   ckPrivate();
   my $writer=shift;    
   print($writer "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>  \n");
   print($writer "\n");

   print($writer "<com.ibm.inventory version=\"1.0\">  \n");
    
   print($writer "   <ResourceSet class=\"com.ibm.inventory.Fixes\" ");
   print($writer "type=\"Fixes\" version=\"1.1\" timestamp=\"");
   print($writer time(),"000\">  \n\n");
   } # end _writeXmlHead





# Function: _createXmlStructure(client_str)
# Purpose: To create the array reference that will hold the filled in xml structure
#          for the xml document that is created from reading the INSTFIX variable.
# Side effects: -
# Returns: The tagArrayRef for an xml structure for the client passed in.
#          See SUMA::Util->writeXml for more details on structure. 
sub _createXmlStructure
   {
   ckPrivate();
   my $client=shift;
   my $clientHostname=$client;
   my $clientOSLevel;
   
   if ($clientHostname eq "localhost")                 # localhost
      {
      $client=$hostname;
      $clientHostname=$hostname;
      $clientOSLevel=`/usr/bin/oslevel`;
      chomp($clientOSLevel);
      }
   else                                                # nim client
      {
      $clientHostname=(`/usr/sbin/lsnim -Za if1 $client`)[1];
      chomp($clientHostname);
      $clientHostname=(split(":",$clientHostname))[2];
      $clientOSLevel="";
      }

  
   my($keyword,$fileset,$requiredLevel,$installedLevel,$status,$abstract,$extras);
   
   my $last_fileset="";
   my $filesets_array=[];
   my ($fixProp_array,$filesetProp_array,$child_array,$fixes_array);
   my ($child_tagArrayRef,$property_tagArrayRef,$fixes_tagArrayRef);
   foreach my $line (@INSTFIX)   
      {
      chomp($line);

      $line=encode_entities($line);	# html encode entries

      # field:1    2           3                4          5        6 
      ($keyword,$fileset,$requiredLevel,$installedLevel,$status,$abstract,
      # field: 7+
      $extras)=split(/:/,$line);

      $filesetProp_array=[];
      if ($fileset ne $last_fileset)
         {

         # PCP
         $child_tagArrayRef=['Value',$fileset,"", 'type', '"string"'];
         $child_array=[$child_tagArrayRef];
         $property_tagArrayRef=['Property','PCP',$child_array, 'name', '"PCP"', 'displayName', '"Component"'];
         push(@{$filesetProp_array},$property_tagArrayRef);

         # PVK
         $child_tagArrayRef=['Value',$installedLevel,"", 'type', '"string"'];
         $child_array=[$child_tagArrayRef];
         $property_tagArrayRef=['Property','PVK',$child_array, 'name', '"PVK"', 'displayName', '"Version"'];
         push(@{$filesetProp_array},$property_tagArrayRef);

         # Use the V in VRMF of bos.rte for oslevel if it is a client
         if (($clientOSLevel eq "") && ($fileset eq "bos.rte"))
            {
            $clientOSLevel=substr($installedLevel,0,1);
            $clientOSLevel.=".0.0.0";
            }

         $fixes_array=[];
	 }

      $fixProp_array=[];

      # FFI
      $child_tagArrayRef=['Value',$keyword,"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','FFI',$child_array, 'name', '"FFI"', 'displayName', '"Fix Id"'];
      push(@{$fixProp_array},$property_tagArrayRef);

      # DS
      $child_tagArrayRef=['Value',$abstract,"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','DS',$child_array, 'name', '"DS"', 'displayName', '"Description"'];
      push(@{$fixProp_array},$property_tagArrayRef);

      # PVK
      $child_tagArrayRef=['Value',$requiredLevel,"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','PVK',$child_array, 'name', '"PVK"', 'displayName', '"Version"'];
      push(@{$fixProp_array},$property_tagArrayRef);

      # FSC
      $child_tagArrayRef=['Value',$status,"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','FSC',$child_array, 'name', '"FSC"', 'displayName', '"Fix State Code"'];
      push(@{$fixProp_array},$property_tagArrayRef);


      my $uniqueID=$fileset . "-" . $keyword;
      my $fix_tagArrayRef=['Resource',$fileset,$fixProp_array, 'displayName', '"'.$fileset.'"', 'uniqueId', '"'.$uniqueID.'"'];
      push(@{$fixes_array},$fix_tagArrayRef);

      if ($fileset ne $last_fileset)
         {
         $fixes_tagArrayRef=['ResourceList','',$fixes_array, 'displayName', '"Fixes"'];
         push(@{$filesetProp_array},$fixes_tagArrayRef);
         my $fileset_tagArrayRef=['Resource',$fileset,$filesetProp_array, 'displayName', '"'.$fileset.'"', 'uniqueId', '"'.$fileset.'"'];
         push(@{$filesets_array},$fileset_tagArrayRef);
	 }
      $last_fileset=$fileset;
      } # end foreach $line
   


   my $filesets_tagArrayRef=['ResourceList','',$filesets_array, 'displayName', '"Components"'];
   my $sysProp_array=[];
   
   # HN
   $child_tagArrayRef=['Value',$clientHostname,"", 'type', '"string"'];
   $child_array=[$child_tagArrayRef];
   $property_tagArrayRef=['Property','HN',$child_array, 'name', '"HN"', 'displayName', '"HostName"'];
   push(@{$sysProp_array},$property_tagArrayRef);

   # OS
   my $OSLevel="AIX ".$clientOSLevel;
   $child_tagArrayRef=['Value',$OSLevel,"", 'type', '"string"'];
   $child_array=[$child_tagArrayRef];
   $property_tagArrayRef=['Property','OS',$child_array, 'name', '"OS"', 'displayName', '"Operating System"'];
   push(@{$sysProp_array},$property_tagArrayRef);
   
   push(@{$sysProp_array},$filesets_tagArrayRef);
   my $client_tagArrayRef=['Resource',$client,$sysProp_array, 'displayName', '"'.$client.'"', 'uniqueId', '"'.$client.'"'];
   
   my $xml_arrayRef=$client_tagArrayRef;
   return $xml_arrayRef;
   } # end _createXmlStructure

  






# Function: _writeXmlTail(writer_fh)
# Purpose: To write the last two end tags to the filehandle passed in.
# Side effects: Writes to filehandle.
# Returns: 0 success
sub _writeXmlTail
   {
   ckPrivate();
   my $writer=shift;    
   print($writer "   </ResourceSet>  \n");
   print($writer "</com.ibm.inventory>  \n");
   } # end _writeXmlTail





# Function: _clean(variable_str)
# Purpose: To clean the taint from a variable passed in.
#          A clean variable in this function will contain only 
#          alphanumerics, underscore, hyphen, at-sign, or a period.   
# Side effects: -
# Returns: The variable passed in untainted if it was clean, or
#          "" if it was not a valid value.
sub _clean
   {
   ckPrivate();
   my $value=shift;
   if ($value =~ /^([-\@\w.]+)$/) 
      {
      $value=$1;
      }
   else
      {
      mesg(LVL_DEBUG, MSG_GENERIC, "Invalid value: $value");
      $value="";
      }
   } # end _clean












1;
