#!/usr/bin/env perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
#------------------------------------------------------------------------------
=head1  monaixsyslog
=head2  
=cut
#------------------------------------------------------------------------------
use locale;
use Getopt::Long;

my $dirname = "xcat_aix_syslog";
my $vardir = "/var/opt/$dirname";   

my $default_runfile = "$vardir/.monaixsyslog_run";
my $default_file = "$vardir/syslog.out";
my $default_pri = "*.warn";

$::MAX_SENSOR_STRING = 65535;
my ($facility_priority, $logfile, $runfile) = &getArgs();

my ($syslogconf, $embedinfo);

$syslogconf = "/etc/syslog.conf";
$embedinfo = "$facility_priority   $logfile    rotate size 4m files 1";

if (!-d $vardir) { mkdir($vardir); }

#check to see if this is the first time this script has been run
if (!-e $runfile)
{      #first time
	if ($^O =~ /^aix/i)
	{
		runcmd("grep \"$embedinfo\" $syslogconf", -1);
		if ($::RUNCMD_RC == 1)
		{    #grep did not find embedinfo
			    #update syslog.conf
			if (!-d $vardir) { mkdir($vardir); }
			if (!-e $logfile)
			{
			    touchFile($logfile);
			}
			runcmd("echo \"$embedinfo\" >> $syslogconf");
			my $cmd = "refresh -s syslogd";
			runcmd($cmd);
		}
		touchFile($runfile);
	}	
	else
	{
		print "non-AIX platform, this scripts should not be ran.\n";
		exit 1;
	}
}

#Check for errors

if ($^O =~ /^aix/i)
{
    unless (open(RUNFILE, "<$runfile"))
    {
        print "Cannot open file $runfile\n"; 
        exit 1;
    }
    
    my $marker = <RUNFILE>;
    close(RUNFILE);

    my ($new_number, $new_content, $to_rfile);
    # If the $runfile is empty, then we should read the $logfile from the beginning.
    if (!$marker)
    {
        ($new_number, $new_content, $to_rfile) = &refreshSensorFromLogfile($logfile, 0, undef);
    }
    else
    {
        my @info = split ':::', $marker;
        my ($last_modified_time, $line_number, $mark_content) = @info;
        
        my @stats = stat($logfile);
        my $time = $stats[9];
        if ($time == $last_modified_time)
        {
            # The log file has not been updated since last modified.
            exit 0;
        }

        ($new_number, $new_content, $to_rfile) = &refreshSensorFromLogfile($logfile, $line_number, $mark_content);

        # If the $to_rfile is set, then we should refresh the info from rotated file to Sensor first.
        if ($to_rfile)
        {
            # Read the rotated file first.
            my $rotated_file = "$logfile" . ".0";
            ($new_number, $new_content, $to_rfile) = &refreshSensorFromLogfile($rotated_file, $line_number, $mark_content);

            # Then read the log file just from the beginning to refresh the Sensor.
            ($new_number, $new_content, $to_rfile) = &refreshSensorFromLogfile($logfile, 0, undef);
        }
    }
    
    # Get the last modified time for this log file
    my @stats = stat($logfile);
    my $new_time = $stats[9];

    &updateRunfile($new_time, $new_number, $new_content, $runfile);
}
else
{
    print "non-AIX platform, this scripts should not be ran.\n";
	exit 1;
}

exit 0;

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

=head3    getArgs

	parse the command line and check the values

	paras: 
	-p  :    <facility>.<priority>, the default value is "*.warn"
	-f  :    <fifo_name>, the default value is "/var/opt/xcat_aix_syslog/syslog_fifo"

=cut

#-------------------------------------------------------------------------------
sub getArgs()
{
    my $routine = "getArgs";
    print "ENTERING: $routine\n" if $::DEBUG;

    my @command_line = ();
    @command_line   = @ARGV;

    # Checks case in GetOptions
    $Getopt::Long::ignorecase = 0;

    my ($facility_priority, $file, $runfile);

    if (
        !GetOptions(
                    'p=s'      => \$facility_priority,
                    'f=s'      => \$file,
        )
      )
    {
        print "LEAVING: $routine\n" if $::DEBUG;
        exit 1;
    }

    # Set runfile mark file
    if ($facility_priority || $file)
    {
        my @para = split '/', $file;
        my $newpara = join '-', @para;
        $runfile = "$vardir/.monaixsyslog_run" . "-$facility_priority". "-$newpara";
    }
    else
    {
        $runfile = $default_runfile;
    }

    if (!$file)
    {
        $file = $default_file; 
    }

    if (!$facility_priority)
    {
        $facility_priority = $default_pri;
    }
    
    return ($facility_priority, $file, $runfile);
}

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

=head3    refreshSensorFromLogfile

	read the log file line by line to refresh the Sensor

    Args:
    $file       - the log file
    $bgnline    - the beginning line number that we should read from
    $bgncontent - the line content related to $line

    Return:
    $i            - the line number that has been read and refreshed to Sensor.
    $mark_content - the line content related to $i
    $to_rfile     - the flag that indicates whether we need to read from the 
                    rotated file.

=cut

#-------------------------------------------------------------------------------
sub refreshSensorFromLogfile()
{
    my ($file, $bgnline, $bgncontent) = @_;
    unless (open(FILE, "<$file"))
    {
        # The file may be opened by syslogd.
        exit 0;
    }
    
    my $i = 0;
    my $matchflag = 0;
    my $to_rfile = 0;
    my $mark_content;
    my $allinfo = "";
    while (my $line = <FILE>)
    {
        if ($matchflag || $bgnline == 0)
        {
            # Start reading the file from this line and push it to the sensor
            # and update the mark file
            $allinfo .= $line;
    		$i = $i + 1;
    		$mark_content = $line;
        }
        else
        {
            if ($i != $bgnline - 1)
            {
                $i = $i + 1;
                next;
            }

            if ($line eq $bgncontent)
            {
                $matchflag = 1;
                $i = $i + 1;
                next;
            }
            else
            {
                # The line number is the same, but the content is different
                # that indicates the log file has been rotated.
                $to_rfile = 1;
                last;
            }
        }
    }
    if ($allinfo)
    {
        my $strlen = length($allinfo);
        # The condition/response can not handle
        # the long sensor String very well,
        # use file to pass messages.
        # file name: /var/opt/xcat_aix_syslog/tmplogmsg_$$
        if ($strlen > $::MAX_SENSOR_STRING)
        {
            srand(time | $$);
            my $filename = "$vardir/tmplogmsg_$$";
            while (-e $filename)
            {
                $filename = createRandomName($filename);
            }
            if (open(TMPLOG, ">$filename"))
            {
                print TMPLOG $allinfo;
                close TMPLOG;
                print "XCAT_MONAIXSYSLOG_FILE:$filename";
            }
            else
            {
                #open failed, why?
                print "OPEN_FILE_FAILED: $filename";
            }
        }
        else
        {
             print $allinfo;
        }
    }
    close(FILE);

    return ($i, $mark_content, $to_rfile);
}

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

=head3    updateRunfile

	use the new marker line to update the runfile

    Args:
    $time - last mofidied time
    $line - line number
    $content - line content
    $file - the run file

    Return:
    $i            - the line number that has been read and refreshed to Sensor.
    $mark_content - the line content related to $i

=cut

#-------------------------------------------------------------------------------
sub updateRunfile()
{
    my ($time, $line, $content, $file) = @_;
    
    # the marker line is something like "last_modified_time:::line_number:::mark_content"
    my $new_marker = join(":::", $time, $line, $content);
    runcmd("echo \"$new_marker\" > $file");
}

#--------------------------------------------------------------------------------
=head3    runcmd
    Run the given cmd and return the output in an array (already chopped).  Alternatively,
    if this function is used in a scalar context, the output is joined into a single string
    with the newlines separating the lines.  
    Arguments:
        command, exitcode and reference to output
    Returns:
        see below
    Error:
        Normally, if there is an error running the cmd, it will display the error msg
        and exit with the cmds exit code, unless exitcode is given one of the
        following values:
             0:     display error msg, DO NOT exit on error, but set
                $::RUNCMD_RC to the exit code.
            -1:     DO NOT display error msg and DO NOT exit on error, but set
                $::RUNCMD_RC to the exit code.
            -2:    DO the default behavior (display error msg and exit with cmds
                exit code.
        number > 0:    Display error msg and exit with the given code
    Example:
        my $outref =  runcmd($cmd, -2, 1);     
    Comments:
        If refoutput is true, then the output will be returned as a reference to
        an array for efficiency.
=cut
#--------------------------------------------------------------------------------
sub runcmd
{
  my ($cmd, $exitcode, $refoutput) = @_;
  $::RUNCMD_RC = 0;
  if (!($cmd =~ /2>&1$/)) { $cmd .= ' 2>&1'; }
  
  my $outref = [];
  @$outref = `$cmd`;
  if ($?)
  {
    $::RUNCMD_RC = $? >> 8;
    my $displayerror = 1;
    my $rc;
    if (defined($exitcode) && length($exitcode) && $exitcode != -2)
    {
      if ($exitcode > 0)
      {
	$rc = $exitcode;
      }    # if not zero, exit with specified code
      elsif ($exitcode <= 0)
      {
	$rc = '';    # if zero or negative, do not exit
	if ($exitcode < 0) { $displayerror = 0; }
      }
    }
    else
    {
      $rc = $::RUNCMD_RC;
    }    # if exitcode not specified, use cmd exit code
    if ($displayerror)
    {
      my $errmsg = '';
      if (($^O =~ /^linux/i) && $::RUNCMD_RC == 139)
      {
        $errmsg = "Segmentation fault  $errmsg";
      }
      else
      {
        # The error msgs from the -api cmds are pretty messy.  Clean them up a little.
        filterRmcApiOutput($cmd, $outref);
        $errmsg = join('', @$outref);
        chomp $errmsg;
      }
      print "Exit code $::RUNCMD_RC from command: $cmd\nError message from cmd: $errmsg\n"
    }
  }
  if ($refoutput)
  {
    chomp(@$outref);
    return $outref;
  }
  elsif (wantarray)
  {
    chomp(@$outref);
    return @$outref;
  }
  else
  {
    my $line = join('', @$outref);
    chomp $line;
    return $line;
  }
}

#--------------------------------------------------------------------------------
=head3    filterRmcApiOutput
    filter RMC Api Output
    Arguments:
        RMC command
        Output reference
    Returns:
        none
    Globals:
        none
    Error:
        none
    Example:
          filterRmcApiOutput($cmd, $outref);
    Comments:
        The error msgs from the RPM -api cmds are pretty messy.
        This routine cleans them up a little bit.
=cut
#--------------------------------------------------------------------------------
sub filterRmcApiOutput
{
  my ($cmd, $outref) = @_;
  if (!($cmd =~ m|^/usr/bin/\S+-api |))  {
    return;
  }    # give as much info as possible, if verbose

  # Figure out the output delimiter
  my ($d) = $cmd =~ / -D\s+(\S+)/;
  if (length($d))  {
    $d =~ s/^(\'|\")(.*)(\"|\')$/$2/;    # remove any surrounding quotes
    # escape any chars perl pattern matching would intepret as special chars
    $d =~ s/([\|\^\*\+\?\.])/\\$1/g;
  }
  else
  {
    $d = '::';
  }    # this is the default output delimiter for the -api cmds
  $$outref[0] =~ s/^ERROR${d}.*${d}.*${d}.*${d}.*${d}//;
}

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

=head3  touchFile
    Arguments: $filename, $donotExit
    Returns: non zero return code indicates error
    Example:  touchFile("/var/opt/xcat/touch");
=cut

#--------------------------------------------------------------------------------
sub touchFile
{
  my ($filename, $donotExit) = @_;
  my $fh;
  my $rc = 0;
  if (!-e $filename)  {   
    #if the file doesn't exist we need to open and close it
    open($fh, ">>$filename") or $rc++;
    if ($rc > 0 && !$donotExit)    {
      print "Touch of file $filename failed with: $!\n";
      return $rc;
    }
    close($fh) or $rc++;
  }
  else  { 
    #if the file does exist we can just utime it (see the perlfunc man page entry on utime)
    my $now = time;
    utime($now, $now, $filename);
  }
  if ($rc > 0 && !$donotExit)  {
      print "Touch of file $filename failed with: $!\n";
    return $rc;
  }
  return 0;
}


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

=head3    createRandomName

        Create a randome file name.

        Arguments:
                Prefix of name
        Returns:
                Prefix with 8 random letters appended
        Error:
                none
        Example:
                $file = createRandomName($namePrefix);
        Comments:
                None

=cut

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

sub createRandomName
{
    my $name=shift;

    my $nI;
    for ($nI = 0 ; $nI < 8 ; $nI++)
    {
        my $char = ('a' .. 'z', 'A' .. 'Z')[int(rand(52)) + 1];
        $name .= $char;
    }
    $name;
}