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

# code starts after '=cut'

=head1 NAME

SUMA::SoftwareInventory - A module to display a system's
or nim client's software inventory information in multiple formats.

=head1 SYNOPSIS

   use SUMA::SoftwareInventory qw/get_inv_sys  get_inv_repos/;

   #
   # Software inventory
   #
   
   $format="lslpp";
   $lslpp_flags="Lcq";
   @clients=("localhost","alpha","beta");
   $path="/usr/sys/inst.images";
   
   ($software_list_fh,$pid)=get_inv_sys($format,$lslpp_flags,"localhost");
   # ($software_list_fh,$pid)=get_inv_sys("localhost");
   print (<$software_list_fh>);
   close $software_list_fh;

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

   # exit $rc;

   # or

   $software_list_fh=get_inv_sys($format,$lslpp_flags,@clients);
   # $software_list_fh=get_inv_sys(@clients);
   print (<$software_list_fh>);
   close $software_list_fh;

   # exit 0;

   #
   # Software repository
   #
   ($software_list_fh,$pid)=get_inv_repos($path);
   print (<$software_list_fh>);
   close $software_list_fh;

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

   # exit $rc;

   # or
   $software_list_fh=get_inv_repos($path);
   print (<$software_list_fh>);
   close $software_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 POSIX qw(strftime);

use SUMA::GConfig;
use SUMA::Util qw(ckPrivate writeXml RE_PATH);
use SUMA::Messenger qw(
                        MSG_CANT_FORK
                        MSG_GENERIC
                        MSG_NOT_MASTER
                        MSG_NOT_ROOT
                        MSG_NO_HOSTNAME
                        MSG_USAGE_SUMA_SWINV2
                        );


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




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

# Globals
my $format;
my $lslpp_flags;
my @properties;
my $xml_arrayRef;
my @LSLPP;
my @LSLPP_PROD_INFO;
my @REPOS;

# 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_repos PATH_STR

Outputs the software inventory of a repository. The PATH_STR can be
either relative or absolute.  The inventory output is formatted 
exactly the same as a call to geninstall -Ld PATH_STR.  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_repos
   {
   my $path=shift;
   my $writer;
   my $reader;
   
   $path=File::Spec->rel2abs($path);
   
   # Get current user
   $user=`/usr/bin/whoami`;
   chomp($user);
   
   # 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;

      my $rc=_getReposInfo($path);

      # Output Client Information
      if ($rc == 0) 
         {
         print ($writer @REPOS);
         } # end if $rc == 0 

      close $writer;
      exit($rc);
      }
   } # end get_inv_repos




=item get_inv_sys CLIENT_STR

=item get_inv_sys FORMAT_STR, FLAGS_STR, CLIENT_STR

=item get_inv_sys FORMAT_STR, FLAGS_STR, CLIENT_STR1, CLIENT_STR2 ...

Outputs the software inventory of a system. The CLIENT_STR must be
"localhost" if you wish the inventory of the local system.  You may also
specify a valid nim client as a CLIENT_STR if the local system is the nim
master.  FORMAT_STR is the format the output should be in; this is either
"lslpp" or "xml".  FLAGS_STR is the lslpp flags to pass to the lslpp 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="Lcq" and FORMAT_STR="lslpp".
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";
      $lslpp_flags="Lcq";
      }
   else
      {
      $format=shift; if (! defined($format)) {$format="lslpp";}
      $lslpp_flags=shift; if (! defined($lslpp_flags)) {$lslpp_flags="Lcq";}
      }
   @clients=@_; if (! defined($clients[0])) {@clients=("localhost")}
   
   # 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;
   my($wroteXmlRCHead)=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=_getSoftwareInfo($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 "lslpp") && 
                  do
                  {
                  _writeLslpp($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;
                  };         
 
               ($format eq "xmlrc") && 
                  do
                  {
                  if(! $wroteXmlRCHead)
                     {
                     _writeXmlRCHead($writer);
                     $wroteXmlRCHead=1;
                     }
                  my $xml_arrayRef=_createXmlRCStructure($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);}
      if (($format eq "xmlrc") && ($returnCode != 1)) {_writeXmlRCTail($writer);}

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




# Function: _getSoftwareInfo(client_str)
# Purpose: To acquire the lslpp information needed for software inventory.
# Side effects: Variables LSLPP and LSLPP_PROD_INFO contain the output from the 
#               lslpp commands.
# Returns: 0 success, $ecFailure
sub _getSoftwareInfo
   {
   ckPrivate();
   my $client=shift;
   if (! defined($client)) {return $ecFailure};
   $lslpp_flags=_clean($lslpp_flags);
   if (! defined($lslpp_flags)) {return $ecFailure};
   if (($client eq "localhost") || ($client eq $hostname))
      {
      mesg(LVL_DEBUG, MSG_GENERIC, "Getting lslpp output for the localhost.");      
      if (($format eq "xml") || ($format eq "xmlrc"))
         {
         @LSLPP_PROD_INFO=`/usr/bin/lslpp -icq 2>&1 `;
         if ( $? != 0) {return $ecFailure;}
         $lslpp_flags="Lcq";
         }
      @LSLPP=`/usr/bin/lslpp -$lslpp_flags 2>&1 `;
      if ( $? != 0) {return $ecFailure;}
      }
   else
      { 
      mesg(LVL_DEBUG, MSG_GENERIC, "Getting lslpp 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") || ($format eq "xmlrc"))
         {
         @LSLPP_PROD_INFO=`/usr/sbin/nim -o lslpp -a lslpp_flags=-icq $client 2>&1 `;  
         if ( $? != 0) {return $ecFailure;}
         $lslpp_flags="Lcq";
         }
      @LSLPP=`/usr/sbin/nim -o lslpp -a lslpp_flags=-$lslpp_flags $client 2>&1 `; 
      if ( $? != 0) {return $ecFailure;}
      }
   return 0
   } # end _getSoftwareInfo



# Function: _getReposInfo(path_str)
# Purpose: To acquire the geninstall information needed for repository inventory.
# Side effects: Variable REPOS contains the output from the geninstall command.
# Returns: 0 success, $ecFailure
sub _getReposInfo
   {
   ckPrivate();
   my $path=shift;
   if (! defined($path)) {return $ecFailure};
   ($path) = $path =~ RE_PATH; # Untaint
   return $ecFailure unless $path;
   if ($user ne "root")
      {
      mesg(LVL_ERROR, MSG_NOT_ROOT);
      return $ecFailure;
      }
   @REPOS=`/usr/sbin/geninstall -Ld $path 2>&1 `;
   if ( $? != 0) {return $ecFailure;}
   return 0
   } # end _getReposInfo





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





# 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.Software\" ");
   print($writer "type=\"Software\" version=\"1.0\" timestamp=\"");
   print($writer time(),"000\">  \n\n");
   } # end _writeXmlHead





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

   print($writer "<ResourceCollection  \n");
    
   print($writer "  xmlns=\"http://www.ibm.com/xmlns/prod/SystemsManagement/v1.0/ResourceCollection\" \n");

   print($writer "  timestamp=\"");
   my $time = strftime("%Y-%m-%dT%H:%M:%S+00:00", gmtime);
   print($writer "$time\">  \n");
   } # end _writeXmlRCHead





# 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 LSLPP and LSLPP_PROD_INFO
#          variables.
# 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 %productIdHash;
   foreach my $line (@LSLPP_PROD_INFO)   
      {
      chomp($line);

      my($path,$fileset,$vendorCode,$prodId,$featureId,$parent,$extras);
      my($level);
#     field:1    2         3         4         5        6       7 
      ($path,$fileset,$vendorCode,$prodId,$featureId,$parent,$extras)=split(/:/,$line);
      ($fileset,$level)=split(/ /,$fileset);
      
      $productIdHash{$fileset}=$prodId;
      }
      
  
   my($packageName,$fileset,$level,$state,$ptfId,$fixState,$type,$desc);
   my($destDir,$uninstaller,$messCat,$messSet,$messNum,$parent,$automatic);
   my($efixLocked,$extras);
   
   
   my $filesets_array=[];
   my ($child_tagArrayRef,$child_array,$property_tagArrayRef);
   foreach my $line (@LSLPP)   
      {
      chomp($line);
      $line=~s/: :/::/g;                # Make entries that are a space blank
      $line=~s/: :/::/g;                # Need second run through

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

#     field:  1        2       3      4      5        6        7    8
      ($packageName,$fileset,$level,$state,$ptfId,$fixState,$type,$desc,
#     field: 9       10        11       12       13       14       15
      $destDir,$uninstaller,$messCat,$messSet,$messNum,$parent,$automatic,
#     field: 16      17
      $efixLocked,$extras)=split(/:/,$line);

      my $property_array=[];

      # PCP
      my $uniqueId='"'.$fileset;
      $uniqueId.='_'.$destDir if ($destDir ne "");
      $uniqueId.='"';
      $child_tagArrayRef=['Value',$fileset,"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','PCP',$child_array, 'name', '"PCP"', 'displayName', '"Component"'];
      push(@{$property_array},$property_tagArrayRef);

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

      # PPF
      $child_tagArrayRef=['Value',$packageName,"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','PPF',$child_array, 'name', '"PPF"', 'displayName', '"Feature"'];
      push(@{$property_array},$property_tagArrayRef);

      # PVK
      $child_tagArrayRef=['Value',$level,"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','PVK',$child_array, 'name', '"PVK"', 'displayName', '"Version"'];
      push(@{$property_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($level,0,1);
         $clientOSLevel.=".0.0.0";
         }

      # PIS
      $child_tagArrayRef=['Value',$destDir,"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','PIS',$child_array, 'name', '"PIS"', 'displayName', '"Instance"'];
      push(@{$property_array},$property_tagArrayRef);

      # PUI
      $child_tagArrayRef=['Value',$uninstaller,"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','PUI',$child_array, 'name', '"PUI"', 'displayName', '"Uninstall Info"'];
      push(@{$property_array},$property_tagArrayRef);

      # PFC
      $type=" " if($type eq "");        # Make this one a space if it is blank
      $child_tagArrayRef=['Value',$type,"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','PFC',$child_array, 'name', '"PFC"', 'displayName', '"Fix Type Code"'];
      push(@{$property_array},$property_tagArrayRef);

      # PSC
      $child_tagArrayRef=['Value',$fixState,"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','PSC',$child_array, 'name', '"PSC"', 'displayName', '"State Code"'];
      push(@{$property_array},$property_tagArrayRef);

      # PST
      my($fixStateDesc)=$fixState;
      $fixStateDesc=~s/^A$/APPLIED/;
      $fixStateDesc=~s/^B$/BROKEN/;
      $fixStateDesc=~s/^C$/COMMITTED/;
      $fixStateDesc=~s/^E$/EFIXLOCKED/;
      $fixStateDesc=~s/^O$/OBSOLETE/;
      $fixStateDesc=~s/^.$/UNKNOWN/;
      $child_tagArrayRef=['Value',$fixStateDesc,"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','PST',$child_array, 'name', '"PST"', 'displayName', '"State"'];
      push(@{$property_array},$property_tagArrayRef);

      # PAI
      my($automaticBoolean)=$automatic;
      $automaticBoolean=~s/^1$/true/;
      $automaticBoolean=~s/^0$/false/;
      $child_tagArrayRef=['Value',$automaticBoolean,"", 'type', '"boolean"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','PAI',$child_array, 'name', '"PAI"', 'displayName', '"Automatically Installed"'];
      push(@{$property_array},$property_tagArrayRef);

      # PLC
      my($efixLockedBoolean)=$efixLocked;
      $efixLockedBoolean=~s/^1$/true/;
      $efixLockedBoolean=~s/^0$/false/;
      $child_tagArrayRef=['Value',$efixLockedBoolean,"", 'type', '"boolean"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','PLC',$child_array, 'name', '"PLC"', 'displayName', '"Locked"'];
      push(@{$property_array},$property_tagArrayRef);

      # PMF
      $child_tagArrayRef=['Value',$messCat,"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','PMF',$child_array, 'name', '"PMF"', 'displayName', '"Message File"'];
      push(@{$property_array},$property_tagArrayRef);

      # PMT
      $child_tagArrayRef=['Value',$messSet,"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','PMT',$child_array, 'name', '"PMT"', 'displayName', '"Message Set"'];
      push(@{$property_array},$property_tagArrayRef);

      # PMI
      $child_tagArrayRef=['Value',$messNum,"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','PMI',$child_array, 'name', '"PMI"', 'displayName', '"Message Identifier"'];
      push(@{$property_array},$property_tagArrayRef);

      # PPN
      $child_tagArrayRef=['Value',$productIdHash{$fileset},"", 'type', '"string"'];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property','PPN',$child_array, 'name', '"PPN"', 'displayName', '"Product Name"'];
      push(@{$property_array},$property_tagArrayRef);

      my $fileset_tagArrayRef=['Resource',$fileset,$property_array, 'displayName', '"'.$fileset.'"', 'uniqueId', $uniqueId];
      push(@{$filesets_array},$fileset_tagArrayRef);
      } # 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: _createXmlRCStructure(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 LSLPP and LSLPP_PROD_INFO
#          variables.
# Side effects: -
# Returns: The tagArrayRef for an xml structure for the client passed in.
#          See SUMA::Util->writeXml for more details on structure. 
sub _createXmlRCStructure
   {
   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 %productIdHash;
   foreach my $line (@LSLPP_PROD_INFO)   
      {
      chomp($line);

      my($path,$fileset,$vendorCode,$prodId,$featureId,$parent,$extras);
      my($level);
#     field:1    2         3         4         5        6       7 
      ($path,$fileset,$vendorCode,$prodId,$featureId,$parent,$extras)=split(/:/,$line);
      ($fileset,$level)=split(/ /,$fileset);
      
      $productIdHash{$fileset}=$prodId;
      }
      
  
   my($packageName,$fileset,$level,$state,$ptfId,$fixState,$type,$desc);
   my($destDir,$uninstaller,$messCat,$messSet,$messNum,$parent,$automatic);
   my($efixLocked,$extras);
   
   
   my $filesets_array=[];
   my ($child_tagArrayRef,$child_array,$property_tagArrayRef);
   foreach my $line (@LSLPP)   
      {
      chomp($line);
      $line=~s/: :/::/g;                # Make entries that are a space blank
      $line=~s/: :/::/g;                # Need second run through

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

#     field:  1        2       3      4      5        6        7    8
      ($packageName,$fileset,$level,$state,$ptfId,$fixState,$type,$desc,
#     field: 9       10        11       12       13       14       15
      $destDir,$uninstaller,$messCat,$messSet,$messNum,$parent,$automatic,
#     field: 16      17
      $efixLocked,$extras)=split(/:/,$line);

      my $property_array=[];

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

      # Label
      my $uniqueId='"'.$fileset;
      $uniqueId.='_'.$destDir if ($destDir ne "");
      $uniqueId.='"';
      $child_tagArrayRef=['Value',$fileset];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property',"",$child_array, 'name', '"Label"', 'type', '"string"'];
      push(@{$property_array},$property_tagArrayRef);

      # MaintenanceVersion
      $child_tagArrayRef=['Value',$level];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property',"",$child_array, 'name', '"MaintenanceVersion"', 'type', '"string"'];
      push(@{$property_array},$property_tagArrayRef);

      # Version
      $child_tagArrayRef=['Value',$level];
      $child_array=[$child_tagArrayRef];
      $property_tagArrayRef=['Property',"",$child_array, 'name', '"Version"', 'type', '"string"'];
      push(@{$property_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($level,0,1);
         $clientOSLevel.=".0.0.0";
         }

      my $fileset_tagArrayRef=['ResourceInstance', "", $property_array, 'resourceName', '"'.$fileset.'"', 'resourceId', '""', 'resourceType', '"SoftwareUpdate"'];
      push(@{$filesets_array},$fileset_tagArrayRef);
      } # end foreach $line
   

   my $filesets_tagArrayRef=['ResourceList','',$filesets_array, 'resourceType', '"SoftwareElement"'];
   my $sysProp_array=[];
   push(@{$sysProp_array},$filesets_tagArrayRef);

   my $xml_arrayRef=$filesets_tagArrayRef;
   return $xml_arrayRef;
   } # end _createXmlRCStructure

 



# 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: _writeXmlRCTail(writer_fh)
# Purpose: To write the last tag to the filehandle passed in.
# Side effects: Writes to filehandle.
# Returns: 0 success
sub _writeXmlRCTail
   {
   ckPrivate();
   my $writer=shift;
   print($writer "</ResourceCollection>  \n");
   } # end _writeXmlRCTail





# Function: ckPrivate(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;