#!/usr/bin/perl
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
#  
#  
# Licensed Materials - Property of IBM 
#  
# (C) COPYRIGHT International Business Machines Corp. 2000,2007 
# 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 
# @(#)24   1.53   src/csm/core/cmds/lsnode.perl, csmcore, csm_rfish, rfishs001b 7/13/06 14:38:48
#
# List node or device attributes.  Note that this script can be called either as
# lsnode or lshwdev.  Several variables are set so that it can act as either one.
# All references to nodes, means devices if this is running as lshwdev.

use strict;
use locale;

BEGIN
{

	# this enables us to redirect where it looks for other CSM files during development
	$::csmroot = $ENV{'CSM_ROOT'} ? $ENV{'CSM_ROOT'} : '/opt/csm';
	$::csmpm   = "$::csmroot/pm";
	$::csmbin  = "$::csmroot/bin";
}
use lib $::csmpm;
use Getopt::Std;
require NodeUtils;
require NetworkUtils;
my $LSHWDEV   = ($0 =~ /lshwdev/) ? 1                  : 0;
my $NODECLASS = $LSHWDEV          ? 'IBM.DeviceHwCtrl' : 'IBM.ManagedNode';
my $KEYATTR   = $LSHWDEV          ? 'Name'             : 'Hostname';
$::NODEGROUP_CLASS = $LSHWDEV
  ? 'IBM.DeviceGroup'
  : undef
  ; # this global variable is used by the group expansion functions in NodeUtils

# For the usage, see nodecmds.msg
sub usage
{
	MessageUtils->message('I', $LSHWDEV ? 'IMsgLshwdevUsage' : 'IMsgLsnodeUsage');
	exit(scalar(@_) ? $_[0] : 1);
}

$::MSGCAT     = 'nodecmds.cat';
$::MSGMAPPATH = "$::csmroot/msgmaps";
$::MSGSET     = $LSHWDEV
  ? 'device_commands'
  : 'lsnode';    # setting this explicitly just to be safe

# Parse the cmd line args and check them
if (
	!getopts( $LSHWDEV ? 'ispw:a:Ad:D:lxhvVf:SFL:X:P' : 'ispw:a:Ad:D:lxN:hvVf:SHFn:X:P'
	)
   )
{
	&usage;
}
if ($::opt_h) { &usage(0); }


if ($LSHWDEV)
{
	$::opt_n = $::opt_d;
	$::opt_N = $::opt_D;
	$::opt_D = $::opt_L;
}    # translate the lshwdev options to the lsnode equivalents
else
{
	if (!defined($::opt_D)) { $::opt_D = $::opt_d; }
}    # -D and -d mean the same thing so collaspe them
if (
	defined($::opt_w)
	&& (   (scalar(@ARGV) > 0)
		|| defined($::opt_f)
		|| defined($::opt_N)
		|| defined($::opt_n))
   )
{
	&usage;
}
if ((($::opt_i || $::opt_s) + $::opt_p + defined($::opt_a)) > 1) { &usage; }
if (($::opt_i + $::opt_s + $::opt_p + $::opt_A) > 1 && !$::opt_l) { &usage; }
if (($::opt_i || $::opt_s) && $::opt_l) { &usage; }
if ((defined($::opt_a) + $::opt_A) > 1) { &usage; }
if ((defined($::opt_D) + $::opt_l) > 1) { &usage; }

# Can't use -X with -F
if ( defined($::opt_F) && defined($::opt_X) ) { &usage; }

# Only try to use XML::DOM if -X is used.
if ($::opt_X)
{
    my $module = "XML::DOM";
    eval "use $module";
    if($@) {
	$::MSGSET = 'lsnode';
        MessageUtils->message('E', 'EMsgPM_NOT_FOUND', $module);
        exit(1);
    }

    $::output_file = $::opt_X;
}

#if ($::opt_S && ($::opt_i || $::opt_s || $::opt_l)) { &usage; }        # sorting is the default now

# Defaults
$::delim     = $::opt_D || ', ';    # delimiter within a row (resource)
$::linedelim = "\n";                # delimiter between rows (resources)

# make -A the default for -l
if ($::opt_l && (($::opt_i || $::opt_s) + $::opt_p + defined($::opt_a)) == 0)
{
	$::opt_A = 1;
}

#if ($::opt_S) { $::opt_x = 1; }       # sorting is the default now
if ($::opt_P && !defined($::opt_D)) { $::opt_l = 1; }
if ($::opt_P && !defined($::opt_a)) { $::opt_A = 1; }
if ($::opt_F || $::opt_X) { $::opt_l = 1; $::opt_A = 1; }
my $hostnameAttr = $::opt_H ? 'Name' : $KEYATTR;
$::VERBOSE = $::opt_v || $::opt_V;
my $PROPNAME = $ENV{'CSM_NODE_PROP_ATTR'} || 'Properties';

$ENV{'CT_MANAGEMENT_SCOPE'} =
  1;    # set local scope so we can access IBM.ManagedNode and IBM.NodeGroup

my $select = &getSelect;

my %opthash;
if (defined($::opt_w))
{
	if (!($::opt_w eq '*')) { $opthash{'WhereStr'} = $::opt_w; }

	# else, we don't specify a where string, so we get everything
}
else    # get nodes from ARGV, -N, and -f
{
	$NodeUtils::errno = 0;
	$::nodelist       = NodeUtils->gatherNodeList(\@ARGV);

	#print "errno=$NodeUtils::errno\n";
	if (!scalar(@$::nodelist))
	{

		# If they tried to specify some nodes, but it resulted in an empty list, print nothing.
		# Otherwise, we continue on with an empty nodelist and display all nodes.
		if (   scalar(@ARGV)
			|| defined($::opt_n)
			|| defined($::opt_N)
			|| defined($::opt_f))
		{
			exit;
		}
	}

	#$::nodelist = NodeUtils->resolveAndUndup($::nodelist);
	#$opthash{'SkipResolve'} = 1;
}

if ($::opt_l) { $opthash{'IncludeAttrNames'} = 1; }
if ($LSHWDEV) { $opthash{'ResourceClass'}    = $NODECLASS; }
elsif ($::opt_H)
{
	$opthash{'ResourceClass'}   = 'IBM.Host';
	$ENV{'CT_MANAGEMENT_SCOPE'} = 3;            # set scope to whole cluster
}

my $outputref = NodeUtils->listNodeAttrs($::nodelist, $select, \%opthash);

@$outputref =
  sort @$outputref
  ; # always sort output.  Note: when all attrs are displayed, this only works how you would expect because Hostname happens to be the 1st attr returned by the ManagedNode class

if ($::opt_l) { &longFormat($outputref); }
else { &shortFormat($outputref); }

exit;

# Form the select string - the attributes to be returned
sub getSelect
{
	my $select;
	if    ($::opt_P) { $select = $PROPNAME; }
	elsif ($::opt_A) { $select = ''; }
	elsif ($::opt_i || $::opt_s) { $select = $hostnameAttr; }
	elsif ($::opt_p)         { $select = 'Status'; }
	elsif (length($::opt_a)) { $select = expandWildAttrs($::opt_a); }
	else { $select = $hostnameAttr; }

	# Set the linedelim to delim if it is only one attr we are displaying
	if (   (!$::opt_P && !$::opt_A && split(/[, ]+/, $select) <= 1)
		|| ($::opt_P && $::opt_x && split(/[, ]+/, $::opt_a) <= 1))
	{
		$::linedelim = $::opt_D || $::linedelim;
	}

	# Modify the select str if we need to include a prefix
	if (!$::opt_l || $::opt_P)
	{
		$::prefix = 1;  # by default we prefix each line with the short hostname
		  #if ($::opt_x || $::linedelim ne "\n" || !length($select) || (($select=~/\b$hostnameAttr\b/) && !$::opt_i)) { $::prefix = 0; }
		if (   $::opt_x
			|| !length($select)
			|| (($select =~ /\b$hostnameAttr\b/) && !$::opt_i))
		{
			$::prefix = 0;
		}
		if ($::prefix && !$::opt_i) { $select = "$hostnameAttr $select"; }
	}
	return $select;
}

# Expand wildcard chars in the attr name, if present
sub expandWildAttrs
{
	my $attrs = shift @_;
	if ($attrs =~ /[\*%]/)
	{
		my @patterns = split(/[, ]+/, $attrs);
		foreach my $p (@patterns)
		{
			$p =~ s/[\*%]/\.\*/g;
		}    # turn this into a valid regular expression
		my $outref =
		  NodeUtils->runrmccmd('lsrsrcdef-api', '', qq(-r $NODECLASS));
		my @newattrs;
		foreach my $l (@$outref)
		{
			my ($a) = split(/::/, $l);
			foreach my $p (@patterns)
			{
				if ($a =~ /^$p$/) { push @newattrs, $a; last; }
			}
		}
		if (scalar(@newattrs)) { return join(',', @newattrs); }
		else { MessageUtils->message('E14', 'EMsgNO_ATTR_MATCH'); }
	}
	else { return $attrs; }    # no wildcard chars
}

sub longFormat
{
	my ($outref) = @_;
	my @xml_list = ();

	#my $numfound = scalar(@$outref);              # will check this after displaying what we did find

	# Process the returned rows of data
	my $firsttime = 1;
	my ($YES, $NO, $ON, $OFF, $UNKNOWN);
	foreach my $l (@$outref)
	{
		if ($::opt_F) { print "\n"; }
		elsif (!defined($::opt_X) ) 
		{
			if (!$firsttime)
			{
				print "-------------------\n";
			}    # separate this resource from the previous
			else { $firsttime = 0; }
		}

		# takes the alternating attrs and values and puts them in the hash correctly
		my %attrs = split(/:\|:/, $l);

		# If we are showing properties change the Properties attr into individual properties and put them in the
		# attrs hash instead of the Properties attr
		if ($::opt_P)
		{
			my ($delim, $propstr) = $attrs{$PROPNAME} =~ /^(\W*)(.*?)$/;
			my $delimpat;
			if (length($delim))
			{
				$delimpat = $delim;
				$delimpat =~ s/([\\\|\(\)\[\{\^\$\*\+\?\.])/\\$1/g;
			}
			else { $delim = '|:|'; $delimpat = '\|:\|'; }

			#print "delim=$delim, delimpat=$delimpat\n";
			my %props = split(/$delimpat/, $propstr);
			if ($::opt_A)
			{
				my $hostname = $attrs{$hostnameAttr};
				%attrs = %props;
				$attrs{$hostnameAttr} = $hostname;
			}
			else
			{
				foreach my $a (split(/[, ]+/, $::opt_a))
				{
					$attrs{$a} = $props{$a};
				}
				delete $attrs{$PROPNAME};
			}
		}

		delete $attrs{"ActivePeerDomain"};
		if ($LSHWDEV)
		{

			#todo:  is it really true that these do not apply to the IBM.DeviceHwCtrl class?
			delete $attrs{ConfigChanged};
			delete $attrs{ChangedAttributes};

			# This attr is confusing in a device context, so do not display it.
			delete $attrs{NodeNameList};
		}

		# Make sure the hostname is displayed 1st
		if (defined($attrs{$hostnameAttr}))
		{
			if ($::opt_F || $::opt_P) { print "$attrs{$hostnameAttr}:\n"; }
			elsif ($::opt_X)	  { push ( @xml_list, "$attrs{$hostnameAttr}:"); }
			else { print " $hostnameAttr = $attrs{$hostnameAttr}\n"; }
			delete $attrs{$hostnameAttr
			  };    # so we do not repeat it in the following loop
		}

		if ($::opt_F || $::opt_X)
		{

			# Remove the attributes that should not go into a nodedef file
			if ($LSHWDEV)
			{
				foreach my $a (qw(PowerStatus Status)) { delete $attrs{$a}; }
			}
			else
			{
				foreach my $a (
					qw(AllowManageRequest ConfigChanged ChangedAttributes CSMVersion LastCFMUpdateTime PowerStatus Status Name NodeNameList UniversalId UpdatenodeFailed InstallStatus )
				  )
				{
					delete $attrs{$a};
				}
			}
		}
		else    # this is a regular -l output format
		{

			# Give Nmemonic explanations for some of the attributes
			my $r;    # this reduces the # of hash lookups
			if (defined($r = $attrs{'AllowManageRequest'}))
			{
				&getMeanings();
				if ($r == 0) { $attrs{'AllowManageRequest'} .= " $::NO"; }
				elsif ($r == 1)
				{
					$attrs{'AllowManageRequest'} .= " $::MGMT_REQUEST";
				}
			}
			if (defined($r = $attrs{'ConfigChanged'}))
			{
				&getMeanings();
				$attrs{'ConfigChanged'} .= $r ? " $::YES" : " $::NO";
			}
			if (defined($r = $attrs{'PowerStatus'}))
			{
				&getMeanings();
				$attrs{'PowerStatus'} .= (
						  $r
						  ? ($r == 1
							 ? " $::ON"
							 : ($r == 128 ? " $::UNCONFIGURED" : " $::UNKNOWN"))
						  : " $::OFF"
				);
			}
			if (defined($r = $attrs{'Status'}))
			{
				&getMeanings();
				$attrs{'Status'} .=
				  ($r
					? ($r == 1 ? " $::ALIVE" : " $::UNKNOWN")
					: " $::UNREACHABLE");
			}
			if (defined($r = $attrs{'UpdatenodeFailed'}))
			{
				&getMeanings();
				$attrs{'UpdatenodeFailed'} .= $r ? " $::TRUE" : " $::FALSE";
			}
		}

		# Now display the rest of the attributes, sorted
		foreach my $k (sort keys %attrs)
		{
			if ($::opt_F && !length($attrs{$k})) { next; }
			elsif ($::opt_X && !length($attrs{$k})) { next; }
 			elsif ($::opt_X) { push(@xml_list, "$k = $attrs{$k}"); }
			else		 { print " $k = $attrs{$k}\n"; }
		}
	}

	&checkForNotFound();

        if($::opt_X) { writeXML(@xml_list); } 
}

# Takes an array of node/device attributes and writes it out in
# XML format.
sub writeXML
{

#use XML::DOM;

	my $node;
	my $attribute;
	my $attrValue;
	my $tag;
	my @nodeList = @_;
	my $doc = XML::DOM::Document->new;
        my $xml_pi = $doc->createXMLDecl ('1.0');
	$doc->setXMLDecl($xml_pi);
	my $root = $doc->createElement('CSMResourceClass:CSMResourceClass');
        $doc->appendChild($root);

	if($LSHWDEV) { $tag = 'Device'; }
	else 	     { $tag = 'Node'; }

    	foreach my $line (@nodeList) {

            if (grep(/:\s*$/, $line)) {  #See if it's a stanza name (Hostname or Device Name)

                my ($name, $junk, $junk2) = split(/:/, $line);

		my $attr;
		my $val = $name;

                if ($tag eq 'Device') { $attr = 'Name'; }
	  	else		      { $attr = 'Hostname'; }

                $root->addText("\t");
                $node = $doc->createElement($tag);
                $node->addText("\n");
                $attribute = $doc->createElement($attr);
                $attrValue = $doc->createTextNode($val);
                $attribute->appendChild($attrValue);
                $node->addText("\t\t");
                $node->appendChild($attribute);
                $node->addText("\n\t");
                $root->addText("\n\t");
                $root->appendChild($node);
                $root->addText("\n");
            }
            elsif (($line =~ /^\s*(\w+)\s*=\s*(.*)\s*/)) {    # set these attrs

                my $attr = $1;
                my $val  = $2;
                $attr =~ s/^\s*//;    # Remove any leading whitespace
                $attr =~ s/\s*$//;    # Remove any trailing whitespace
                $val  =~ s/^\s*//;
                $val  =~ s/\s*$//;

                # remove spaces and quotes
                $val =~ s/^\s*"\s*//;
                $val =~ s/\s*"\s*$//;

                $node->appendChild($doc->createTextNode("\t"));
                $attribute = $doc->createElement($attr);
                $attrValue = $doc->createTextNode($val);
                $attribute->appendChild($attrValue);
                $node->appendChild($attribute);
                $node->addText("\n\t");
            }

        }

    $doc->printToFile($::output_file);
    $doc->dispose;

}

# Gets a list of the words: (yes) (no) (on) (off) (unknown)
# and puts them in global vars
sub getMeanings
{

	# Check if we already got them so we do not do it every time.
	if (!defined($::YES))
	{
		my $saveset = $::MSGSET;
		$::MSGSET = 'lsnode';
		(
		 $::YES, $::NO, $::ON, $::OFF, $::UNKNOWN, $::TRUE, $::FALSE,
		 $::MGMT_REQUEST
		)
		  = split(/\|/, MessageUtils->getMessage('IMsgATTRIBUTE_MEANINGS'));
		($::UNREACHABLE, $::ALIVE, $::UNCONFIGURED) =
		  split(/\|/, MessageUtils->getMessage('IMsgStatusATTRIBUTE_MEANINGS'));
		$::MSGSET = $saveset;
	}
}

sub shortFormat
{
	my ($outref) = @_;

	#my $numfound = scalar(@$outref);              # will check this after displaying what we did find

	#if ($::opt_S)         # sorting is the default now
	#  {
	#	my $ref2 = [];
	#	@$ref2 = sort @$outref;
	#	$outref = $ref2;
	#  }

	my $firsttime = 1;
	my $prefstr   = '';
	foreach my $l (@$outref)
	{
		if (!$firsttime)
		{
			print $::linedelim;
		}    # separate this row from the previous
		else { $firsttime = 0; }

		my @row;

		# If we are showing properties change the Properties attr into individual properties and put them in the
		# row array instead of the Properties attr
		if ($::opt_P)
		{
			my ($delim, $propstr);
			if ($::prefix)
			{
				($row[0], $delim, $propstr) = $l =~ /^(.+):\|:(\W*)(.*?)$/;
			}
			else { ($delim, $propstr) = $l =~ /^(\W*)(.*?)$/; }
			my $delimpat;
			if (length($delim))
			{
				$delimpat = $delim;
				$delimpat =~ s/([\\\|\(\)\[\{\^\$\*\+\?\.])/\\$1/g;
			}
			else { $delim = '|:|'; $delimpat = '\|:\|'; }

			#print "delim=$delim, delimpat=$delimpat\n";
			my %props = split(/$delimpat/, $propstr);
			if ($::opt_A) { push @row, values(%props); }
			else
			{
				foreach my $a (split(/[, ]+/, $::opt_a))
				{
					push @row, $props{$a};
				}
			}
		}
		else { @row = split(/:\|:/, $l); }

		#Note: With both -i and -s there is only 1 column.  Someday support -i and -s with -A.
		if ($::opt_s)    # display short hostname - strip domain from hostname
		{
			($row[0]) = NodeUtils->getShorthost($row[0]);
		}
		elsif ($::prefix)    # use the 1st column to form the prefix
		{
			if (NetworkUtils->isIpaddr($row[0]))
			{
				($prefstr) = $row[0];
			}                # do not strip
			else
			{
				($prefstr) = split(/\./, $row[0]);
			}                # strip domain from hostname
			$prefstr .= ':  ';
			if (!$::opt_i)
			{
				shift @row;
			}                # the hostname was just there for the prefix
		}
		if ($::opt_i)        # change the hostname to ip address
		{
			my ($hostname, $ipaddr) = NetworkUtils->getHost($row[0]);
			$row[0] = $ipaddr;
		}
		elsif ($::opt_p)
		{
			my $val = $row[0];
			&getMeanings();
			$row[0] .=
			  ($val
				? ($val == 1 ? " $::ALIVE" : " $::UNKNOWN")
				: " $::UNREACHABLE");
		}

		print $prefstr, join($::delim, @row);
	}

	if (!$firsttime)
	{
		print "\n";
	}    # end with a newline unless there was no output

	&checkForNotFound();
}

sub checkForNotFound
{

	#my $numfound = shift;
	#if (defined(@$::nodelist) && scalar(@$::nodelist) && $numfound<scalar(@$::nodelist))
	if ($::LISTNODEATTRS_NUMNOTFOUND)
	{
		if (scalar(@$::nodelist) == 1)
		{
			MessageUtils->message('E12',
					   $LSHWDEV ? 'EMsgDEVICE_NOT_FOUND' : 'EMsgNODE_NOT_FOUND',
					   $$::nodelist[0]);
		}
		elsif (scalar(@$::nodelist) <= 10)
		{
			MessageUtils->message(
							   'W12',
							   $LSHWDEV
							   ? 'EMsgSOME_NOT_FOUND_LIST_dev'
							   : 'EMsgSOME_NOT_FOUND_LIST',
							   join(', ', @$::nodelist)
							  );
		}
		else
		{
			MessageUtils->message('W12',
					$LSHWDEV ? 'EMsgSOME_NOT_FOUND_dev' : 'EMsgSOME_NOT_FOUND');
		}
	}
}
