#!/usr/bin/env perl
# 
# $Header: has/utl/crswrapexece.pl.sbs /st_has_12.2.0.1.0/1 2016/07/29 00:47:39 jgobbur Exp $
#
# exece.pl
# 
# Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
#
#    NAME
#      crswrapexece.pl - EXECute with imported Environment variables
#
#    DESCRIPTION
#      Importing environment variables from <env_file> then execute command.
#
#    NOTES
#      - Only being used by crswrap.sh at the time of creation 
#      - Intended to be used in *nix environment only.
#      - Valid <env_file> format
#        * Empty lines: lines with all white space
#        * Comments: line starts with #.
#        * <key>=<value>
#        * <key> is all non-whitespace characters on the left of the
#          first "=" character.
#        * <value> is everything on the right of the first "=" character
#          (including whitespaces).
#        * Surrounding double-quote (") won't be stripped.
#        * Key with blank <value> ('') will be undefined.
#          (e.g: 'Hello=', Hello will be undefined)
#      - Log msgs to 3 places:
#        * client/crswrapexece.log: log all messages; clobbered every time
#                                   crswrapexece.pl is executed.
#        * syslog: log all failure messages.
#        * alertlog: log only failure messages that end-users can do something
#                    about.
#  
#      Design Objectives:
#        - Simple => Fast and less error prone
#        - Robust => Hard to make it fail
#
# Usage:
#   > crswrapexece.pl <env_file> <command>
#
#    MODIFIED   (MM/DD/YY)
#    jgobbur     07/27/16 - to fix bug 24292392.
#    rwessman    07/11/14 - Bug 19164135 - only exec binaries in
#                           $ORA_CRS_HOME/bin.
#    shiyer      01/09/14 - Logging to ADR location
#    hchau       12/04/09 - Embrace logging to 3 different medium: syslog,
#                           file, alertlog.
#    hchau       11/12/09 - Change logging mechanism from syslog to clsecho
#    hchau       09/18/09 - Remove setlogsock to make syslog work in Solaris
#                           and HPUX
#    hchau       08/17/09 - Implement undefining env variable
#    hchau       03/27/09 - Change to a simplier key-value pair format to
#                           support whitespace in value
#    hchau       12/04/08 - Creation
# 

use strict;
use File::Spec;

### Main ###
my $CRS_HOME = "%ORA_CRS_HOME%";
my $MY_HOST = "%HOST%";
my $MY_ORACLE_BASE = "%ORACLE_BASE%";

my $numError = 0;
my $logfile = "$MY_ORACLE_BASE/crsdata/$MY_HOST/output/crswrapexece.log";
my $envFile = '';
my @progAndArgs = ();

filelog_trunc($logfile, "Executed cmd: $0 @ARGV");

if (@ARGV < 2)
{
   alertlog(10101);
   syslog('err', "$0: Missing arguments");
   filelog_append($logfile, "Missing arguments");

   print STDERR "usage: $0 <env_file> <command>\n";

   exit 1;
}

($envFile, @progAndArgs) = @ARGV;

# Bug 19164135 - only allow executables located in $ORA_CRS_HOME/bin.
my $bindir = File::Spec->catfile(${CRS_HOME}, 'bin', '');

# Bug 24292392 - to handle double slash arguments. 
$progAndArgs[0] = File::Spec->catfile($progAndArgs[0]);
$progAndArgs[1] = File::Spec->catfile($progAndArgs[1]);

abort("$progAndArgs[0] does not reside in $bindir", 10104, "$progAndArgs[0]",
      "$bindir")
  if (index($progAndArgs[0], $bindir) != 0);

abort("$progAndArgs[0] does not exist", 10106, "$progAndArgs[0]")
  if (! -e $progAndArgs[0]);

# Only executable files are allowed.
abort("$progAndArgs[0] is not an executable", 10105, "$progAndArgs[0]")
  if (! -X $progAndArgs[0]);

$numError = importEnvFromFile($envFile);

# We choose to aborting executing if there is a problem in env_file
# Note: another option is to ignore offending env_var entries.
if ($numError > 0)
{
  alertlog(10103, "$numError", "$envFile");
  syslog('err', 
         "$0: Found $numError error(s) in $envFile. " .
         "Abort executing $progAndArgs[0]");
  filelog_append($logfile, "Found $numError error(s) in $envFile. " .
                           "Abort executing $progAndArgs[0]");
  exit 1;
}

filelog_append($logfile, "executing \"@progAndArgs\"");
exec { $progAndArgs[0] } @progAndArgs;

# Reached here only if exec failed.
alertlog(10100, "@ARGV");
syslog('err', "$0: Error executing \"@progAndArgs\"");
filelog_append($logfile, "Error executing \"@progAndArgs\"");

### Subroutines ###
sub importEnvFromFile()
{
  my $envFile = shift;
  my $lineNum = 0;
  my $numErr = 0;

  if ( ! open(ENVFILE, "< $envFile") )
  {
    alertlog(10102, "$envFile");
    syslog('err', "$0: Unable to open $envFile");
    filelog_append($logfile, "Unable to open $envFile");

    exit 1;
  }

  # Validate and import env file.
  foreach (<ENVFILE>)
  {
    $lineNum++;

    next if (/^\s*$/);  # Skip blank line
    next if (/^\s*#/);  # Skip comments

    if ( ! /^(\S+?)=(.*)$/ )
    {
      $numErr++;
      filelog_append($logfile, "Error on line $lineNum: $_");
      print STDERR "$0: Error on line $lineNum: $_";

      next;
    }

    my $key = $1;
    my $value = $2;

    # If value is blank, the $key will be undefined;
    # otherwise, set the key to appropriate value.
    if ($value eq '')
    {
      delete $ENV{$key} if (defined $ENV{$key});
    }
    else
    {
      $ENV{$key} = $value;
    }
  }

  return $numErr; 
}

# Description: Log an error to the alert log and system logs then quit.
# Syntax: abort(message, crs_msgid, crs_msg_args)
# abort("$progAndArgs[0] is not an executable", 10105, $progAndArgs[0]);
sub abort()
{
  my $message = shift;
  my $message = "$message Abort executing $progAndArgs[0]";
  my $message_num = shift;

  alertlog($message_num, @_);

  syslog('err', "$0: $message");

  filelog_append($logfile, "$message");
  exit 1;
}

# Description: Log an entry to system log, and print to stdout.
# Syntax: syslog(severity, msg)
# e.g: syslog('info', 'Missing arguments');
sub syslog()
{
  my ($severity, $msg) = @_;
  # Directly use clsecho.bin since lib path should already be setup.
  my $clsecho = "$CRS_HOME/bin/clsecho.bin";
  my @options = ('-s', '-c', "$severity");

  return 0 if ($severity !~ /^(err|warn|info)$/);

  system($clsecho, @options, $msg);

  return 1;
}

# Description: Log an entry to alert log using /has/mesg/crs<lang>.msg.
# Syntax: alertlog(crs_msgid, <args...> )
# e.g: alertlog(2406, $numerror);
sub alertlog()
{
  my $crs_msgid = shift;
  my @args = @_;
  # Directly use clsecho.bin since lib path should already be setup.
  my $clsecho = "$CRS_HOME/bin/clsecho.bin";
  my @options = ('-l', '-n', '-t', '-p', 'has', '-f', 'crs', '-m', "$crs_msgid");

  system($clsecho, @options, @args);

  return 1;
}

# Description: Log an entry to a specific file, if the file exists,
#              it will be truncated to 0 before inserting this msg.
# Syntax: filelog(logpathname, msg)
# e.g: filelog_trunc('crswrapexece.log', 'Missing arguments');
sub filelog_trunc()
{
  my ($logpathname, $msg) = @_;
  # Directly use clsecho.bin since lib path should already be setup.
  my $clsecho = "$CRS_HOME/bin/clsecho.bin";
  my @options = ('-r', '-n', '-t', '-o', "$logpathname");

  system($clsecho, @options, $msg);

  return 1;
}

# Description: Append an log entry to a specific file.
# Syntax: filelog_append(logpathname, msg)
# e.g: filelog_append('crswrapexece.log', 'Missing arguments');
sub filelog_append()
{
  my ($logpathname, $msg) = @_;
  # Directly use clsecho.bin since lib path should already be setup.
  my $clsecho = "$CRS_HOME/bin/clsecho.bin";
  my @options = ('-n', '-t', '-o', "$logpathname");

  system($clsecho, @options, $msg);

  return 1;
}
