#!/usr/bin/perl
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
#  
#  
# Licensed Materials - Property of IBM 
#  
# (C) COPYRIGHT International Business Machines Corp. 2005,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 
# @(#)54   1.22   src/csm/core/cmds/mkresources.perl, csmcore, csm_rfish, rfishs001b 10/17/06 21:23:08

#######################################################################
# mkresources is a way for CSM components to
# predefine conditions, responses, associations, sensors and node
# groups (and can be extended to support any RSCT resource
# class). This will allow CSM components to easily use the advanced
# logic in predefined-condresp on all CSM endpoints (instead of just
# the management server).

# To use the infrastructure, components create perl modules in
# /opt/csm/install/resources. Each resource should have its own perl
# module (so that it is easy to update a resource without interfering
# with other resources), and should be named <Resource Name>.pm.

# After the resource perl modules are installed, they will be created
# by the next execution of the /opt/csm/bin/mkresources
# command. This command should be called by the post install scripts
# of packaging files, script run after install or from the command line.
#######################################################################

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 lib "/opt/csm/install/resources";
use Getopt::Long;
require NodeUtils;
require MessageUtils;
require ServerUtils;

$::MKRESMSGCAT = 'nodecmds.cat';
$::MKRESMSGSET = "mkres";
$::MSGMAPPATH  = "$::csmroot/msgmaps";
if (defined $ENV{'CSM_VERBOSE'}) { $::VERBOSE = 1; }

$Getopt::Long::ignorecase = 0;    #Checks case in GetOptions
Getopt::Long::Configure("bundling")
  ;    #allows short command line options to be grouped (e.g. -av)

our (%res, %exi, %cre, %cha);    #predeclare so use strict is happy

#--------------------------------------------------------------------------------

=head3    queryResources

        Queries all resources of a given class or classes. Places 
        results into a global hash for each resource class.

        Arguments: a list of RSCT resource classes

        Globals: %::EXISTS::{$resource}
=cut

#--------------------------------------------------------------------------------

sub queryResources
{
    my @resources = @_;

    my $where = "";
    foreach my $res (@resources)
    {
        if ($res eq "IBM.Association")
        {

            #special case: run lscondresp because Associations do not have names
            #cant run lsrsrc because Assoctation also does not store names of resources (just handles)
            my @condresp = NodeUtils->runcmd("LANG=C /usr/bin/lscondresp");
            my $class    = $res;
            $class =~ s/^IBM\.//;
            splice @condresp, 0,
              2;    #delete first two lines -- they are just comments
            foreach my $line (@condresp)
            {
                my ($condition, $response, $node, $state) = split ' ', $line;
                $condition = &removeQuotes($condition);
                $response  = &removeQuotes($response);
                my $key        = "${condition}:_:${response}";
                my $ActiveFlag = 0;                              #assume offline
                if ($state =~ m/Active/)
                {
                    $ActiveFlag = 1;
                }

                #currently does not checked for locked

                # This \%{typeglob} syntax auto-vivifies
                # the hash table for us, and gives us a reference.
                my $ref = \%{$::EXISTS::{$class}};
                $ref->{$key} = {ActiveFlag => $ActiveFlag,};
            }
        }
        else
        {
            $where .= " -s ${res}::::'*p0x0020'";
        }
    }

    my $output =
      NodeUtils->runrmccmd("lsrsrc-api", "-i -m -n -D ':|:'", $where);
    foreach my $line (@$output)
    {
        my @array = split(/:\|:/, $line);
        my $class = shift @array;    #the -m flag puts the class name in front
        $class =~ s/^IBM\.//;
        my %attrs = @array;

        # This \%{typeglob} syntax auto-vivifies
        # the hash table for us, and gives us a reference.
        my $ref = \%{$::EXISTS::{$class}};
        my $key = $attrs{'Name'};
        $ref->{$key} = {%attrs};     #sets the EXISTS array with the info
    }
}

#--------------------------------------------------------------------------------

=head3    traverseDirectories

        Calls readFiles on each sub-directory of the given path.
        Creates a global array with all target resource classes.

        Arguments: A directory

        Globals: @::DIRECTORIES (will hold all resource classes)
=cut

#--------------------------------------------------------------------------------
sub traverseDirectories
{
    my ($dir) = @_;
    my ($dir_fh, $file);

    opendir($dir_fh, $dir)
      or die MessageUtils->messageFromCat($::MKRESMSGCAT, $::MSGMAPPATH,
                                $::MKRESMSGSET, 'I', 'EMsgCanNotOpenDir', $dir);
    while ($file = readdir($dir_fh))
    {
        if ($file ne '.' and $file ne '..')
        {
            my $subdir = "$dir/$file";
            if (-d $subdir)
            {    #only look at directories
                &readFiles($subdir);
                push @::DIRECTORIES, $file;    #file=just the filename
            }
        }
    }
    closedir($dir_fh)
      or die MessageUtils->messageFromCat($::MKRESMSGCAT, $::MSGMAPPATH,
                               $::MKRESMSGSET, 'I', 'EMsgCanNotCloseDir', $dir);

}

#--------------------------------------------------------------------------------

=head3    readFiles

        Calls require on all .pm files in a given directory

        Arguments: A directory
=cut

#--------------------------------------------------------------------------------
sub readFiles
{
    my ($dir) = @_;
    my ($dir_fh, $file);
    opendir($dir_fh, $dir)
      or die MessageUtils->messageFromCat($::MKRESMSGCAT, $::MSGMAPPATH,
                                $::MKRESMSGSET, 'I', 'EMsgCanNotOpenDir', $dir);
    while ($file = readdir($dir_fh))
    {
        if ($file ne '.' and $file ne '..')
        {
            $file = "$dir/$file";
            if ($file =~ m/\.pm$/)
            {

                #its a perl module
                require $file;
            }
        }
    }
    closedir($dir_fh)
      or die MessageUtils->messageFromCat($::MKRESMSGCAT, $::MSGMAPPATH,
                               $::MKRESMSGSET, 'I', 'EMsgCanNotCloseDir', $dir);
}

#--------------------------------------------------------------------------------

=head3    compareResources

        Compares existing resources to those requiring definition.

        Globals: uses %::EXISTS and %::RES and makes %::CHANGE and %::CREATE
=cut

#--------------------------------------------------------------------------------
sub compareResources
{
    foreach my $class (@::DIRECTORIES)
    {    #this has all subdirectory names
        $class =~ s/^IBM\.//;    #the IBM prefix is not used in the hash name
        local *exi = $::EXISTS::{$class};    #defined on system
        local *res = $::RES::{$class};       #defined in file
        foreach my $resource (keys %res)
        {
            if (defined $exi{$resource})
            {                                #exists on the system
                if (defined $res{$resource}{'Locked'}
                    && $res{$resource}{'Locked'} == 1)
                {

                    #only change the resource if it is supposed to be locked
                    foreach my $attr (keys %{$res{$resource}})
                    {
                        if ($exi{$resource}{$attr} ne $res{$resource}{$attr})
                        {
                            if (!($class eq "Association" && $attr eq "Locked"))
                            {    # association locked attrs are not stored
                                    # something has changed
                                if ($::VERBOSE)
                                {
                                    print
                                      "Differs: Class=$class\tExists=$exi{$resource}{$attr}\tDefined=$res{$resource}{$attr}\n";
                                }
                                $::CHANGE::{$class}{$resource} =
                                  $res{$resource};
                                last;
                            }
                        }
                    }
                }
            }
            else
            {    #resource is not defined on the system
                $::CREATE::{$class}{$resource} = $res{$resource};
            }

        }
    }
}

#--------------------------------------------------------------------------------

=head3    removeQuotes

        removes starting and ending quotes that are in the output of lsrsrc

        Arguments: string
        Returns: string with no leading or trailing quotes
       
=cut

#--------------------------------------------------------------------------------
sub removeQuotes
{
    my ($string) = @_;
    $string =~ s/^\"|^\'//;
    $string =~ s/\"$|\'$//;
    return $string;
}

#--------------------------------------------------------------------------------

=head3    createResources

        Calls mkrsrc-api on all resources in the %::CREATE hash
       
        Globals: %::CREATE
=cut

#--------------------------------------------------------------------------------
sub createResources
{
    my $string;
    my $counter = 0;
    my @assoc_cmds;
    my $sensorflg = 0;
    foreach my $class (@::DIRECTORIES)
    {    #all the class names
        local *cre = $::CREATE::{$class};
        if ($class eq "Sensor")
        {
            $sensorflg = 1;
        }
        else
        {
            $sensorflg = 0;
        }
        foreach my $resource (keys %cre)
        {
            if ($class eq "Association")
            {    #special case
                my ($cond, $resp) = split ":_:", $resource;
                if ($cre{$resource}{'ActiveFlag'} == 1)
                {
                    push @assoc_cmds, "/usr/bin/startcondresp $cond $resp";
                    if ($cre{$resource}{'Locked'} == 1)
                    {
                        push @assoc_cmds,
                          "/usr/bin/startcondresp -L $cond $resp";
                    }
                }
                else
                {    #not active
                    push @assoc_cmds, "/usr/bin/mkcondresp $cond $resp";

                    #no need to lock stopped associations
                }
            }
            else
            {
                $string .= " IBM.${class}::";
                foreach my $attr (keys %{$cre{$resource}})
                {
                    my $value = $cre{$resource}{$attr};
                    $string .= "${attr}::" . NodeUtils->quote($value) . "::";
                }
                if (($sensorflg == 1) && ($::INSTALL))
                {

                    #  make the Sensor with no userid check
                    $string .= "::Options::1";
                }

                #
                # Only build up to 10 resources at a pass
                # to avoid command line limit
                #
                $counter = $counter + 1;
                if ($counter > 10)
                {
                    if ($string =~ m/\w+/)
                    {

                        #my $cmd = "/usr/bin/mkrsrc-api $string";
                        #print "running $cmd\n";
                        #system($cmd);
                        NodeUtils->runrmccmd("mkrsrc-api", "", $string);
                        $string  = "";
                        $counter = 0;
                    }
                }
            }
        }
    }
    if ($string =~ m/\w+/)    # for any remaining resources
    {

        #my $cmd = "/usr/bin/mkrsrc-api $string";
        #print "running $cmd\n";
        #system($cmd);
        NodeUtils->runrmccmd("mkrsrc-api", "", $string);
    }
    foreach my $cmd (@assoc_cmds)
    {

        #need to make associations after conds and resps have been made
        NodeUtils->runcmd("$cmd");
    }
}

#--------------------------------------------------------------------------------

=head3    changeResources

        Calls chrsrc-api on all resources in the %::CHANGE hash

        Globals: %::CHANGE
=cut

#--------------------------------------------------------------------------------
sub changeResources
{
    my $string;
    my $ustring;    #unlock
    my @unlock;     #unlock each class
    my $where;      #unlock each class
    foreach my $class (@::DIRECTORIES)
    {               #all the class names
        local *cha = $::CHANGE::{$class};
        foreach my $resource (keys %cha)
        {
            if ($class eq "Association")
            {       #special case
                    #code here is identical to createResource
                my ($cond, $resp) = split ":_:", $resource;
                if ($cre{$resource}{'ActiveFlag'} == 1)
                {
                    NodeUtils->runcmd("/usr/bin/startcondresp $cond $resp");
                    if ($cre{$resource}{'Locked'} == 1)
                    {
                        NodeUtils->runcmd(
                                       "/usr/bin/startcondresp -L $cond $resp");
                    }
                }
                else
                {    #not active
                    NodeUtils->runcmd("/usr/bin/mkcondresp $cond $resp");

                    #no need to lock stopped associations
                }
            }
            else     # not class association
            {
                $where = qq/"Name IN ('XXX')"/;
                $string .= " -s IBM.${class}::${where}::";
                push @unlock, $cha{$resource}{'Name'};
                delete $cha{$resource}{'Name'};

                foreach my $attr (keys %{$cha{$resource}})
                {
                    my $value = $cha{$resource}{$attr};

                    $string .= "${attr}::" . NodeUtils->quote($value) . "::";
                }
            }
            if (@unlock)
            {
                $where = qq/"Name IN ('XXX')"/;

                $ustring .= " -s IBM.${class}::${where}::Locked::'0'";
            }
        }    # foreach resource
    }    # foreach key
         #
         # although @unlock contains the resource and not the node name
         # this is a hack to use runrmccmd and the node_ref must
         # be provided even though we are not really dealing with nodes
         # here

    if ($ustring =~ m/\w+/)
    {
        NodeUtils->runrmccmd("chrsrc-api", "", $ustring, undef, \@unlock);
    }
    if ($string =~ m/\w+/)
    {
        NodeUtils->runrmccmd("chrsrc-api", "", $string, undef, \@unlock);
    }
}

#--------------------------------------------------------------------------------

=head3    usage

        Prints the command usage statement
=cut

#--------------------------------------------------------------------------------

sub usage
{
    MessageUtils->messageFromCat($::MKRESMSGCAT, $::MSGMAPPATH, $::MKRESMSGSET,
                                 'I', 'IMsgMkresourceUsage');

}

#--------------------------------------------------------------------------------

=head3    writeAllFiles

        creates all files for the given resources classes
      
        Arguments: class names in an array
=cut

#--------------------------------------------------------------------------------
sub writeAllFiles
{
    my @classes = @_;
    foreach my $class (@classes)
    {
        my $output =
          NodeUtils->runrmccmd("lsrsrc-api", "-i", "-s ${class}::::Name");
        foreach my $line (@$output)
        {
            &writeFile("${class}::$line");
        }
    }
}

#--------------------------------------------------------------------------------

=head3    writeFile

        creates a file with the resource info in 
        /opt/csm/install/resources/base/<class>

        Arguments: class::resource_name
=cut

#--------------------------------------------------------------------------------
sub writeFile
{
    my ($input) = @_;

    my ($class, $resourcefilename) = split "::", $input;
    if (!$resourcefilename)
    {
        MessageUtils->messageFromCat($::MKRESMSGCAT, $::MSGMAPPATH,
                                     $::MKRESMSGSET, 'E',
                                     'EMsgResourceMissing');
        exit 1;
    }
    my $resource;
    push(@$resource, $resourcefilename);

    if (!-e "/opt/csm/install/resources")
    {
        mkdir("/opt/csm/install/resources");
    }
    if (!-e "/opt/csm/install/resources/base")
    {
        mkdir("/opt/csm/install/resources/base");
    }
    if (!-e "/opt/csm/install/resources/base/$class")
    {
        mkdir("/opt/csm/install/resources/base/$class");
    }
    my $file = "/opt/csm/install/resources/base/$class/$resourcefilename.pm";

    my $where  = qq/"Name IN ('XXX')"/;
    my $string = " -s ${class}::${where}::*p0x0002";
    my $output =
      NodeUtils->runrmccmd("lsrsrc-api", "-i -n -D ':|:'",
                           $string, undef, $resource);

    $string = " -s ${class}::${where}::*p0x0008";
    my $optional =
      NodeUtils->runrmccmd("lsrsrc-api", "-i -n -D ':|:'",
                           $string, undef, $resource);

    #my @output = NodeUtils->runcmd("/usr/bin/lsrsrc -s $where $class");
    #uses lsrsrc instead of lsrsrc-api because format is almost right (just needs a few mods)

    my $fh;
    open($fh, ">$file")
      or die MessageUtils->messageFromCat($::MKRESMSGCAT, $::MSGMAPPATH,
                             $::MKRESMSGSET, 'I', 'EMsgCanNotOpenFile2', $file);
    print $fh "#!/usr/bin/perl\n\n";
    $class =~ s/IBM\.//;

    print $fh '$RES::' . $class . "{" . "'"
      . $resourcefilename . "'"
      . "} = {\n";
    foreach my $line (@$output)
    {
        my %attrs = split /:\|:/,
          $line;  #can't go straight into a hash because -p creates extra fields
        foreach my $attr (keys %attrs)
        {
            if (   $attr !~ m/ActivePeerDomain/
                && $attr !~ m/NodeNameList/
                && $attr !~ m/NodeIDs/)
            {
                my $value = $attrs{$attr};
                if ($value =~ m/\w/ || $value =~ m/\d/)
                {

                    # print "value = |$value|\n";
                    #$value = &removeQuotes($value); #quotes are not needed becaues of q()
                    #print "value = |$value|\n";
                    my $line = "\t$attr => q($value),";
                    print $fh "$line\n";
                }
            }
        }
    }
    foreach my $line (@$optional)
    {
        my %attrs = split /:\|:/,
          $line;  #can't go straight into a hash because -p creates extra fields
        foreach my $attr (keys %attrs)
        {
            if (   $attr !~ m/ActivePeerDomain/
                && $attr !~ m/NodeNameList/
                && $attr !~ m/NodeIDs/)
            {
                my $value = $attrs{$attr};
                if ($value =~ m/\w/ || $value =~ m/\d/)
                {

                    # print "value = |$value|\n";
                    #$value = &removeQuotes($value); #quotes are not needed becaues of q()
                    #print "value = |$value|\n";
                    my $line = "\t$attr => q($value),";
                    print $fh "$line\n";
                }
            }
        }
    }

    print $fh "};";
    print $fh "\n";
    print $fh "1;";
    print $fh "\n";
    close($fh)
      or die MessageUtils->messageFromCat($::MKRESMSGCAT, $::MSGMAPPATH,
                             $::MKRESMSGSET, 'I', 'EMsgCanNotCloseFile', $file);
    MessageUtils->messageFromCat($::MKRESMSGCAT, $::MSGMAPPATH, $::MKRESMSGSET,
                                 'V', 'IMsgFileCreated', $file);
}

#######################################################################
# main Main MAIN
#######################################################################
#
# get command line input
my @command_line = ();
@command_line = @ARGV;
my $command_line = $0 . " " . join(" ", @command_line);

# get arguments
if (
    !GetOptions(
                'h'        => \$::HELP,
                'v'        => \$::VERBOSE,
                'V'        => \$::VERBOSE,
                'all'      => \$::ALL,
                'install'  => \$::INSTALL,
                'dir=s'    => \$::BASEDIR,
                'mkfile=s' => \$::MKFILE,
                'mkall'    => \$::MKALL,
    )
  )
{
    &usage;
    exit 1;
}

if ($::HELP) { &usage; exit; }
if (NodeUtils->isHMC() && ($ENV{'DC_ENVIRONMENT'} ne 1))
{
    MessageUtils->messageFromCat(
        'csmInstall.cat',
        $::MSGMAPPATH,
        'csminstall',
        'E1',
        'EMsgDisabledCmdOnHMC',
        'mkresources'
    );
}

# setup logging
#
my $logpath = "/var/log/csm";    # this commands runs on MS and nodes
                                 # on MS can get from CSMLogDir
                                 # but on nodes will need in configinfo file
my $mkresourcelog = "$logpath/mkresources.log";
if (!(-e $logpath))
{
    mkdir($logpath, 0755);
}
MessageUtils->start_logging($mkresourcelog);
MessageUtils->messageFromCat(
                             $::MKRESMSGCAT, $::MSGMAPPATH,
                             $::MKRESMSGSET, 'V',
                             'IMsgInput',    $command_line
                             );    # Log input
my $logging = 1;

# any function requested
if (!$::ALL && !$::BASEDIR && !$::MKFILE && !$::MKALL)
{
    &usage;
    exit 1;
}
if ($::MKFILE)
{
    &writeFile($::MKFILE);
    exit;
}
if ($::MKALL)
{
    &writeAllFiles('IBM.Condition', 'IBM.EventResponse', 'IBM.Sensor');
    exit;
}

if ($::ALL)
{

    #go through all subdirectories of /opt/csm/install/resources
    my ($dir_fh, $file);
    my $dir = "/opt/csm/install/resources";
    opendir($dir_fh, $dir)
      or die MessageUtils->messageFromCat($::MKRESMSGCAT, $::MSGMAPPATH,
                                $::MKRESMSGSET, 'I', 'EMsgCanNotOpenDir', $dir);
    while ($file = readdir($dir_fh))
    {
        if ($file ne '.' and $file ne '..')
        {
            my $subdir = "$dir/$file";
            if (-d $subdir)
            {    #only look at directories
                &traverseDirectories($subdir);
            }
        }
    }
    closedir($dir_fh)
      or die MessageUtils->messageFromCat($::MKRESMSGCAT, $::MSGMAPPATH,
                               $::MKRESMSGSET, 'I', 'EMsgCanNotCloseDir', $dir);
}
else
{
    if (!defined $::BASEDIR)
    {
        $::BASEDIR = "/opt/csm/install/resources/base";
    }
    &traverseDirectories($::BASEDIR);
}

#TODO: wait for RSCT to come online

&queryResources(@::DIRECTORIES);

#compares whats defined in the files to the existing resources
&compareResources();

&createResources();

&changeResources();

END
{
    if ($logging)
    {
        MessageUtils->stop_logging($mkresourcelog);
    }
}
exit 0;
