#!/usr/bin/perl -w
# IBM_PROLOG_BEGIN_TAG
# This is an automatically generated prolog.
#
# Licensed Materials -Property of IBM
# 5737-F34
#
# (C) Copyright IBM Corp. 2005,2024
# 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
#-------------------------------------------------------------------------------
# Items that may be overridden:
#   - GPFS source location -- Does not require any knowledge of the system
#                             configurable parameters.
#   - OS Kernel headers    -- Does not necessarily preclude the automatically
#     determined system configuration (but the implications of specifying them
#     indicates a mismatch with automatically determinable items).
#-------------------------------------------------------------------------------

#---------------------------------
# Global Variables
#---------------------------------
$sharkcloneroot = undef;
# define true and false
use constant { TRUE => 1, FALSE => 0 };

# BGP IO Node directories
$BGP_IO_ROOT = "/bgsys/drivers/ppcfloor/linux/OS";
$bgp_io_kernel_bld_dir = "$BGP_IO_ROOT/usr/src/linux";
$bgp_io_kernel_hdr_dir = "$bgp_io_kernel_bld_dir/include";
$bgp_io_kernel_release = "$bgp_io_kernel_bld_dir/include/linux/utsrelease.h";

$bgp_io_gpl_build = FALSE;

# indicates to verify/install missing packages
# set by user options --installreq, --verifyreq, --buildrpm
my $installReq = FALSE;   # install any missing required packages
my $verifyReq = FALSE;    # verify requested
my $buildrpm = FALSE;     # verify RPM build prerequisits

$suse_release   = "/etc/SuSE-release";
$redhat_release = "/etc/redhat-release";
$debian_release = "/etc/debian_version";
$lsb_release    = "/etc/lsb-release";
$pkvm_release   = "/etc/ibm_powerkvm-release";
$os_release     = "/etc/os-release";
$anolis_release = "/etc/anolis-release";
$alinux_release = "/etc/alinux-release";

$missing_packages = "";

#
# Supported Distribution
#	REDHAT_AS_LINUX
#	KERNEL_ORG_LINUX
#	FEDORA_LINUX
#	SUSE_LINUX
#	UBUNTU_LINUX
#	IBM_PKVM_LINUX
#	ALICLOUD_LINUX
#

#
# Repository
#        yum
#        zypper
#        dpkg
#

#
# Package to be installed
# For kernel header
#
#

#
# Package Manager Info : hash of @pkg_mgr_info
#   @pkg_mgr_info 
#     - package manager name : yum, zypper etc
#     - syntax to install package
#     - syntax to query package
#
my %package_manager = (
  "REDHAT_AS_LINUX"  => ["yum",  "/usr/bin/yum -y install", "/bin/rpm -q"],
  "KERNEL_ORG_LINUX" => ["yum",  "/usr/bin/yum -y install", "/bin/rpm -q"],
  "FEDORA_LINUX"     => ["yum",  "/usr/bin/yum -y install", "/bin/rpm -q"],
  "DEBIAN_LINUX"     => ["apt-get", "/usr/bin/apt-get -y install", "/usr/bin/dpkg -L"],
  "UBUNTU_LINUX"     => ["apt-get", "/usr/bin/apt-get -y install", "/usr/bin/dpkg -L"],
  "SUSE_LINUX"       => ["zypper", "/usr/bin/zypper install -y", "/bin/rpm -q"],
  "SUSE_LINUX_12"    => ["zypper", "/usr/bin/zypper install -y", "/bin/rpm -q"],
  "IBM_PKVM_LINUX"   => ["yum",  "/usr/bin/yum -y install", "/bin/rpm -q"],
  "ALICLOUD_LINUX"   => ["yum",  "/usr/bin/yum -y install", "/bin/rpm -q"],
);
my %kernel_header = (
  "REDHAT_AS_LINUX"  => "kernel-devel",
  "KERNEL_ORG_LINUX" => "kernel-devel",
  "FEDORA_LINUX"     => "kernel-devel",
  "DEBIAN_LINUX"     => "linux-headers",
  "UBUNTU_LINUX"     => "linux-headers",
  "SUSE_LINUX"       => "kernel-default-devel",
  "IBM_PKVM_LINUX"   => "kernel-devel",
  "ALICLOUD_LINUX"   => "kernel-devel",
);

my %rpmbuild_pkg = (
  "REDHAT_AS_LINUX"  => "rpm-build",
  "KERNEL_ORG_LINUX" => "rpm-build",
  "FEDORA_LINUX"     => "rpm-build",
  "DEBIAN_LINUX"     => "",
  "UBUNTU_LINUX"     => "",
  "SUSE_LINUX"       => "rpm",
  "SUSE_LINUX_12"    => "rpm-build",
  "IBM_PKVM_LINUX"   => "rpm-build",
  "ALICLOUD_LINUX"   => "rpm-build",
);

my %system_header = (
  "REDHAT_AS_LINUX"  => "kernel-headers",
  "KERNEL_ORG_LINUX" => "",
  "FEDORA_LINUX"     => "",
  "DEBIAN_LINUX"     => "",
  "UBUNTU_LINUX"     => "",
  "SUSE_LINUX"       => "linux-kernel-headers",
  "IBM_PKVM_LINUX"   => "kernel-headers",
  "ALICLOUD_LINUX"   => "kernel-headers",
);

my $CCT_COMPNAME = 0;
my $CCT_REDHAT   = 1;  # RHEL 6.2
my $CCT_SUSE     = 2;  # SLES11 SP2
my $CCT_DEBIAN   = 3;
my $MAX_CCT      = 5;

my %distro_IX = (
  "REDHAT_AS_LINUX"  => $CCT_REDHAT,
  "KERNEL_ORG_LINUX" => $CCT_REDHAT,
  "FEDORA_LINUX"     => $CCT_REDHAT,
  "SUSE_LINUX"       => $CCT_SUSE,
  "DEBIAN_LINUX"     => $CCT_DEBIAN,
  "UBUNTU_LINUX"     => $CCT_DEBIAN,
  "IBM_PKVM_LINUX"   => $CCT_REDHAT,
  "ALICLOUD_LINUX"   => $CCT_REDHAT,
);
my $libelf_dev_pkg = ["elfutils-libelf-devel", " elfutils-libelf-devel", "libelf-devel", " libelf-dev"];
my $compiler_component = [
  ["make", "make", "make", "make"],
  ["cpp",  "cpp",  "cpp",  "cpp"],
  ["gcc",  "gcc",  "gcc",  "gcc"],
  ["g++",  "gcc-c++",  "gcc-c++", "g++"],
  ["ld",   "binutils", "binutils", "binutils"],
];

#-------------------------------------------------------------------------------
# Function: usage
#
# This function displays the usage for the auto-configuration program.
#-------------------------------------------------------------------------------
sub usage()
{
  # Obtain invoked program name
  $program = `basename $0`;
  chomp($program);

  # Display full syntax description
  print "\nUsage: ${program} [--help] [sharkcloneroot=<dir>]\n";
  print "\nOptions:\n";
  print "\n    --help";
  print "\n           Display ${program} help and exit.\n";
  print "\n    --installreq";
  print "\n        Install required packages for GPL build using default package manager if missed.\n";
  print "\n    --verifyreq";
  print "\n        Verify if all required packages for GPL build exist.\n";
  print "\n    --buildrpm";
  print "\n        Verify if all required packages for RPM build exist.\n";
  print "\n    sharkcloneroot=<dir>";
  print "\n           Specify the location of the source tree to configure.";
  print "\n           If not specified, check the SHARKCLONEROOT environment";
  print "\n           variable. If neither is specifiec, default to the GPL";
  print "\n           source directory /usr/lpp/mmfs/src.\n";
  print "\n    linuxdistribution=<Linux distribution>";
  print "\n           Specify the Linux distribution.";
  print "\n           e.g. linuxdistribution=REDHAT_AS_LINUX\n";
  print "\n    linuxkernelrelease=<Linux kernel release>";
  print "\n           Specify the Linux kernel release (format of 'uname -r').";
  print "\n           e.g. linuxkernelrelease=3.10.0-327.el7.x86_64\n";
  print "\n           This option can ONLY be used with --genenvonly\n";
}

#-------------------------------------------------------------------------------
# Function: get_OS
#
# This function determines the target Operating System.
#
# INPUT     none
# RETURN    GPFS_AIX
#           GPFS_LINUX
#           UNSUPPORTED
#-------------------------------------------------------------------------------
sub get_OS()
{
  local ($os);
  local ($gpfs_os);

  $os = `uname`;
  if ($os =~ "AIX")
  {
    $gpfs_os = "GPFS_AIX";
  }
  elsif ($os =~ "Linux")
  {
    $gpfs_os = "GPFS_LINUX";
  }
  else
  {
    $gpfs_os = "UNSUPPORTED";
  }

  return($gpfs_os);
}

#-------------------------------------------------------------------------------
# Function: get_gcc_version
#
# This function returns the GCC version.
#
# INPUT     none
# RETURN    gcc version number, follow definition of GNU predefined macro GNU_VERSION:
#               #define GCC_VERSION (__GNUC__ * 10000 \
#                                    + __GNUC_MINOR__ * 100 \
#                                    + __GNUC_PATCHLEVEL__)
#-------------------------------------------------------------------------------
sub get_gcc_version
{
  my $version = 0;
  my $gnuc = 0, $gnuc_minor = 0, $gnuc_patchlevel = 0;
  my $predef_macros = `gcc -E -dM - < /dev/null`;

  if ($predef_macros =~ /__GNUC__ (\d+)/)
  {
    $gnuc = $1;
  }
  if ($predef_macros =~ /__GNUC_MINOR__ (\d+)/)
  {
    $gnuc_minor = $1;
  }
  if ($predef_macros =~ /__GNUC_PATCHLEVEL__ (\d+)/)
  {
    $gnuc_patchlevel = $1;
  }

  $version = $gnuc * 10000 + $gnuc_minor * 100 + $gnuc_patchlevel;

  return $version;
}

#-------------------------------------------------------------------------------
# Function: get_Architecture
#
# This function determines the target hardware architecture.
#
# INPUT     none
# RETURN    GPFS_ARCH_PPC64
#           GPFS_ARCH_PPC64_LE
#           GPFS_ARCH_X86_64
#           GPFS_ARCH_S390X
#           GPFS_ARCH_AARCH64
#           NOLONGER   (ia64, x86, ppc32 etc)
#           UNSUPPORTED        
#-------------------------------------------------------------------------------
sub get_Architecture()
{
  local ($arch);
  local ($gpfs_arch);

  if ($bgp_io_gpl_build)
  {
    # BG build on ppc64, can not use uname -m
    if (-f $redhat_release)
    {
      # this means BGQ
      $gpfs_arch="GPFS_ARCH_PPC64";
    }
    else
    {
      $gpfs_arch="NOLONGER";
    }
  }
  else
  {
    $arch = `uname -m`;
    if ($arch =~ /^ia64$/ || $arch =~ /^i\d86$/ || $arch =~ /^ppc$/)
    {
      $gpfs_arch = "NOLONGER";
    }
    elsif ($arch =~ /^ppc64$/)
    {
      $gpfs_arch = "GPFS_ARCH_PPC64";
    }
    elsif ($arch =~ /^x86_64$/)
    {
      $gpfs_arch = "GPFS_ARCH_X86_64";
    }
    elsif ($arch =~ /^s390x$/)
    {
      $gpfs_arch = "GPFS_ARCH_S390X";
    }
    elsif ($arch =~ /^ppc64le$/)
    {
      $gpfs_arch = "GPFS_ARCH_PPC64_LE";
    }
    elsif ($arch =~ /^aarch64$/)
    {
      $gpfs_arch = "GPFS_ARCH_AARCH64";
    }
    else
    {
      $gpfs_arch = "UNSUPPORTED";
    }
  }
  return($gpfs_arch);
}

#----------------------------------------------------------------------
# Function: get_DistValues
#
# This function determines the target Linux distribution values.
#
# INPUT     distribution  : use this value if specified by users 
#                           this is only meaningful if 
#                           $redhat_release exists
# RETURN    REDHAT_AS_LINUX
#           SUSE_LINUX
#           KERNEL_ORG_LINUX
#           FEDORA_LINUX
#           DEBIAN_LINUX
#           UBUNTU_LINUX
#           IBM_PKVM_LINUX
#           ALICLOUD_LINUX
#           UNSUPPORTED
#----------------------------------------------------------------------
sub get_DistValues
{
  local ($dist);

  my $distribution = shift; 
  my $linuxkernelrelease = shift;
  my $dist_version = "";  # for SLES, Ubuntu
  my $dist_patchlevel = 0; # for SLES, Ubuntu
  my $dist_sublevel = 0; # for Ubuntu
  my $dist_version_release = ""; # for Ubuntu
  my $dist_patchlevel_release = 0; # for Ubuntu

  # BGQ has special processing
  # 4.1 supports BGQ only (Always RHEL)
  if ($bgp_io_gpl_build)
  { 
    $dist = "REDHAT_AS_LINUX";
  }
  elsif (-f $suse_release)
  {
    # SuSE Release
    $dist = "SUSE_LINUX";
    open(RELFILE, $suse_release) || die "Cannot open $suse_release";
    LINE: while ($line = <RELFILE>)
    {
      if ($line =~ /VERSION = (\d+)/ )
      {
        $dist_version = $1;
        die ("SLES $dist_version is no longer supported.\n") if ($dist_version < 11);
      }
      if ($line =~ /PATCHLEVEL = (\d+)/)
      {
        $dist_patchlevel = $1;
      }
    }
    close(RELFILE);
  }
  elsif (-f $anolis_release || -f $alinux_release)
  {
    # Alibaba Cloud Linux or Anolis Linux
    my $rc;
    my ($kernel, $kernel_verbose, $kernel_actual, $kernel_string);

    ($rc, $kernel, $kernel_verbose, $kernel_string, $kernel_actual) =
        getKernelVersion($distribution, $dist_version, $dist_patchlevel, $linuxkernelrelease);

    if ($rc)
    {
      $dist = "UNSUPPORTED";
    }
    elsif ($kernel < 41900000)
    {
      # RHEL 7.x/8.x compatible
      $dist = "REDHAT_AS_LINUX";
    }
    elsif ($kernel < 41991027)
    {
      # Alibaba Cloud Linux 2 with kernel < 4.19.91-27
      $dist = "KERNEL_ORG_LINUX";
    }
    else
    {
      # Alibaba Cloud Linux 3, and 2 with kernel >= 4.19.91-27
      $dist = "ALICLOUD_LINUX";
    }
  }
  elsif (-f $redhat_release)
  {
    # RedHat Release
    if ($distribution eq "")
    {
      open(RELFILE, $redhat_release) || die "Cannot open $redhat_release";
      LINE: while ($line = <RELFILE>)
      {
        if ($line =~ /Red Hat Linux release (\S+)/ )
        {
          $dist = "KERNEL_ORG_LINUX";
        }
        elsif ($line =~ /Red Hat Enterprise Linux|CentOS|AlmaLinux|Rocky/ )
        {
          $dist = "REDHAT_AS_LINUX";
        }
        elsif ($line =~ /Fedora/)
        {
          $dist = "FEDORA_LINUX";
        }
        else
        {
          die("\nCannot determine the distribution type.  /etc/redhat-release is present,\n",
              "but the release name is not recognized.  Specify the distribution\n",
              "type explicitly.\n\n",
              "  e.g. LINUX_DISTRIBUTION=REDHAT_AS_LINUX \n")
        }
      }
      close(RELFILE);
    } else
    {
      $dist = $distribution;
    }
  }
  elsif (-f $debian_release)
  {
    open(RELFILE, $debian_release) || die "Cannot open $debian_release";
    LINE: while ($line = <RELFILE>)
    {
      if ($line =~ /^(\d+)([.])(\d+)([.])(\d+)$/ || $line =~ /^(\d+)([.])(\d+)$/)
      {
        $dist = "DEBIAN_LINUX";
      }
    }
    close(RELFILE);

    # Is it Ubuntu Release?
    if (-f $lsb_release)
    {
      open(RELFILE, $lsb_release) || die "Cannot open $lsb_release";
      LINE: while ($line = <RELFILE>)
      {
        if ($line =~ /DISTRIB_ID=Ubuntu/)
        {
          $dist = "UBUNTU_LINUX";
        }
        if ($line =~ /DISTRIB_DESCRIPTION=.* (\d+)([.])(\d+)([.])(\d+) .*/)
        { # DISTRIB_DESCRIPTION="Ubuntu 14.04.1 LTS"
          $dist_version = $1;
          $dist_patchlevel = $3 * 1;
          $dist_sublevel = $5;
        }
        elsif ($line =~ /DISTRIB_DESCRIPTION=.* (\d+)([.])(\d+) .*/)
        { # DISTRIB_DESCRIPTION="Ubuntu 14.04 LTS" ?
          $dist_version = $1;
          $dist_patchlevel = $3 * 1;
        }
        elsif ($line =~ /DISTRIB_RELEASE=(\d+)\.(\d+)/)
        {
          $dist_version_release = $1;
          $dist_patchlevel_release = $2 * 1;
        }
      }
      close(RELFILE);
      if ($dist_version eq "")
      {
        # Development builds do not have the version in DISTRIB_DESCRIPTION
        # If the version has not been found in DISTRIB_DESCRIPTION,
        # use the one from DISTRIB_RELEASE
        $dist_version = $dist_version_release;
        $dist_patchlevel = $dist_patchlevel_release;
      }
    }

    # Unknown distribution
    if ($dist eq "")
    {
      die("\nCan't determine the distribution type.  $debian_release is present,\n",
          "but the version number is not recognized.  Please specify the distribution\n",
          "type explicitly.\n\n",
          "  e.g. make LINUX_DISTRIBUTION=REDHAT_AS_LINUX Autoconfig\n\n")
    }
  }
  elsif (-f $pkvm_release)
  {
    # IBM pKVM Release
    if ($distribution eq "")
    {
      open(RELFILE, $pkvm_release) || die "Cannot open $pkvm_release";
      LINE: while ($line = <RELFILE>)
      {
        if ($line =~ /IBM_PowerKVM release (\d+)([.])(\d+)/)
        {
          $dist = "IBM_PKVM_LINUX";
          $dist_version = $1.".".$3;
        }
        else
        {
          die("\nCannot determine the distribution type.  /etc/ibm_powerkvm-release is present,\n",
              "but the release name is not recognized.  Specify the distribution\n",
              "type explicitly.\n\n",
              "  e.g. LINUX_DISTRIBUTION=IBM_PKVM_LINUX \n")
        }
      }
      close(RELFILE);
    } else
    {
      $dist = $distribution;
    }
  }
  elsif (-f $lsb_release)
  {
    # Linux Standard Base Release
    $dist = "UNSUPPORTED";
    open(RELFILE, $lsb_release) || die "Cannot open $lsb_release";
    LINE: while ($line = <RELFILE>)
    {
      if ($line =~ /DISTRIB_ID=Ubuntu/ )
      {
        $dist = "UBUNTU_LINUX";
        last;
      }
    }
    close(RELFILE);
  }
  elsif (-f $os_release) # in SLES 15, /etc/SuSE-release is removed
  {
    $dist = "UNSUPPORTED";
    open(RELFILE, $os_release) || die "Cannot open $os_release";
    # /etc/os-release lines to parse

    # NAME="SLES"
    # VERSION="12-SP2"

    # NAME="SLES"
    # VERSION="15"

    # NAME="Ubuntu" (not support yet)
    # VERSION="16.04.3 LTS (Xenial Xerus)"

    # NAME="Ubuntu" (not support yet)
    # VERSION="18.04 LTS (Bionic Beaver)"

    LINE: while ($line = <RELFILE>)
    {
      if ($line =~ /^NAME="(\w+)"/ )
      {
        if ($1 eq "SLES")
        {
          $dist = "SUSE_LINUX";
        }
        # TODO: other distros if necessary
      }

      if ($line =~ /^VERSION="(\d+)(-SP){0,1}(\d*)( .+){0,1}"/)
      {
        $dist_version = $1;
        if (defined($2) && ($2 eq "-SP"))
        {
          $dist_patchlevel = $3;
        }
      }
    }

    if ($dist eq "SUSE_LINUX")
    {
      $suse_release = $os_release;
      die ("SLES $dist_version is no longer supported.\n") if ($dist_version < 11);
    }

    close(RELFILE);
  }
  else
  {
    $dist = "UNSUPPORTED";
  }

  return($dist, $dist_version, $dist_patchlevel, $dist_sublevel);
}

#----------------------------------------------------------------------
# Function: program_present
#
# Check if specific program can be found in PATH
#
# INPUT     $prog   program name
# RETURN    1  the progam exist
#           0  the progam does not exist
#----------------------------------------------------------------------
sub program_present()
{
  my $prog = shift;
  my $rc = 1;
  my @cmdresult = ();

  @cmdresult = `which $prog 2>&1`;
  $rc = $?;
  if($rc == 0)
  {
    print "  $prog is present at $cmdresult[0]";
    return 1;
  }
  return 0; 
}

#----------------------------------------------------------------------
# Function: verify_rpmbuild
#
# Verify if all compiler tools are available
#
# INPUT     $dist   List Distribution Name
# RETURN    number of missing compiler tools
#----------------------------------------------------------------------
sub verify_rpmbuild()
{
  my ($dist, $version) = @_;
  my $distroIx = $distro_IX{$dist};
  my $rc = 0;
  my $pkg_to_be_installed = "";

  return 0 if ( -f "/usr/bin/rpmbuild" );

  $dist = "SUSE_LINUX_12" if($dist eq "SUSE_LINUX" && $version >= 12);
  $pkg_to_be_installed = $rpmbuild_pkg{$dist}; 
  if( $pkg_to_be_installed eq "")
  {
    print ("    rpmbuild is not supported.\n");
    return 1;
  }
  return verify_package_installed($pkg_to_be_installed, $dist, "rpmbuild is");
}

#----------------------------------------------------------------------
# Function: is_libelf_dev_pkg_required
#
# Checks if libelf devel package is required for build
#
# INPUT     none
# RETURN    1 if libelf devel is required else 0
#----------------------------------------------------------------------
sub is_libelf_dev_pkg_required()
{
  my $kernelver=`uname -r`;
  chomp($kernelver);
  my $libmodconfig='/lib/modules/'.$kernelver.'/config';
  my $bootconfig='/boot/config-'.$kernelver;
  my $orc = "";
  my $stkval = "";

  # On RHEL/Ubuntu/SLES location of kernel config file is
  # /boot/config-<kernel-version>, however on CoreOS
  # config file is present at
  # /lib/modules/<kernel-version>/config. In case we do
  # not find config in above locations, skip libelf devel
  # package checks.

  open(RELFILE, $bootconfig) || open(RELFILE, $libmodconfig) ||
  (print("Warning: Unable to open kernel config at ",
        "$bootconfig or $libmodconfig, skipping ",
        "libelf devel pkg check\n") && return 0);
  LINE: while ($line = <RELFILE>)
  {
    if ($line =~ /^\s*CONFIG_UNWINDER_ORC\s*=\s*(\w+)/ )
    {
      $orc = $1;
    }
    if ($line =~ /^\s*CONFIG_STACK_VALIDATION\s*=\s*(\w+)/ )
    {
      $stkval = $1;
    }
    if (($orc eq "y") and ($stkval eq "y"))
    {
      close(RELFILE);
      return 1;
    }
  }
  close(RELFILE);
  return 0;
}

#----------------------------------------------------------------------
# Function: verify_libelf_dev
#
# Verify if libelf devel pkg is required and installed.
#
# INPUT     $dist   List Distribution Name
# RETURN    0 if libelf devel pkg is required and installed, else 1
#----------------------------------------------------------------------
sub verify_libelf_dev()
{
  my ($dist) = @_;
  my $distroIx = $distro_IX{$dist};
  my $rc = 0;
  my $rc_vp = 0;
  if (is_libelf_dev_pkg_required()) {
    print ("Verifying libelf devel package...\n");
    $rc_vp = verify_package_installed($libelf_dev_pkg->[$distroIx], $dist, "");
    $rc = 1 if (($rc_vp != 1) and ($rc_vp != 0));
  }
  return $rc;
}
#----------------------------------------------------------------------
# Function: verify_compiler
#
# Verify if all compiler tools are available
#
# INPUT     $dist   List Distribution Name
# RETURN    number of missing compiler tools
#----------------------------------------------------------------------
sub verify_compiler()
{
  my ($dist) = @_;
  my $distroIx = $distro_IX{$dist};
  my $rc = 0;
  my $rc_vp = 0;

  for($i=0; $i<$MAX_CCT; $i+=1)
  {
    $comp = $compiler_component->[$i];
    next if &program_present($comp->[$CCT_COMPNAME]);
    print "  $comp->[$CCT_COMPNAME] is missing.\n";
    $rc_vp = verify_package_installed($comp->[$distroIx], $dist, "compiler tools are");
    $rc += 1 if $rc_vp;
  }
  return $rc;
}

#----------------------------------------------------------------------
# Function: verify_package_installed
#
# Verify if specified package is installed and install optionally.
# This is called only when the program or kernel header directory does not exist.
#
# INPUT     pkgname
#           distribution
#           modulename 
# RETURN    1 package is already installed
#           2 package is not installed, no --quiet
#           0 package is installed successfully
#           3 package installation fail
#----------------------------------------------------------------------
sub verify_package_installed
{
  my $rc = 0;
  my $pkgname = shift;
  my $dist = shift;
  my $modulename = shift;
  my $cmd;
  my @cmdout;

  $pkg_mgr = $package_manager{$dist};
  $cmd = "$pkg_mgr->[2] $pkgname";
  print ("  Verifying $pkgname is installed ...\n");
  print ("    Command: $cmd  \n");
  @cmdout = `$cmd`;
  $rc = $?;
  if ($rc == 0)
  {
    chomp($cmdout[0]);
    if($modulename ne "")
    {
      print ("    The required package $pkgname ($cmdout[0]) is already installed but $modulename missing in the expected location.\n");
      print ("    Verify that correct version of $pkgname is properly installed and try again.\n");
    } else 
    {
      print ("    The required package $pkgname is installed\n");
    }
    return 1;
  }
  print ("    The required package $pkgname is missing.\n");
  $cmd = "$pkg_mgr->[1] $pkgname";
  $missing_packages = $missing_packages . $pkgname . " ";
  if ($installReq == FALSE)
  {
    print ("    Try again after installing $pkgname.\n");
    return 2;
  }
  print ("    Installing $pkgname...\n");
  print ("      Command: $cmd  \n");
  `$cmd`; 
  $rc = $?;
  if ($rc == 0)
  {
    print ("    $pkgname is succssfully installed.\n"); 
    return 0;
  }
  # install fail
  print ("    Tried to install missing package $pkgname using $pkg_mgr->[0] but failed.\n");  
  return 3;
}

#----------------------------------------------------------------------
# Function: getKernelVersion
#
# INPUT     distribution
# RETURN    kernel versions
#----------------------------------------------------------------------
sub getKernelVersion
{
  my ($distribution, $dist_version, $dist_patchlevel, $linuxkernelrelease) = @_;
  my $kernel = "";
  my $kernel_verbose = "";
  my $kernel_actual = "";
  my $kernel_string = "";
  my $rc = 0;
  my ($version, $release, $modification, $fix, $fix_verbose);
  my $fix_minor1 = 0;
  my $fix_minor2 = 0;

  # $fix_actual is used for SLES
  # to truncate trailing extra string from $kernel_string.
  # For example, SLES11 SP2, need to truncate "-default"
  # from $kernel-string to find "/usr/src/linux-3.0.13-0.27"
  #   > uname -r
  #   3.0.13-0.27-default
  #   > ls /usr/src
  #   linux-3.0.13-0.27
  my $fix_actual;

  # Obtain the running kernel version string
  if ($bgp_io_gpl_build)
  {
    # On BG io node, "uname -r" gives service node level,
    # Hence, need to obtain kernel info from source file for ppc io node
    $kernel_string = `head -1 $bgp_io_kernel_release | cut -d\\" -f2`;
  }
  elsif (($linuxkernelrelease ne "RUNNING_KERNEL") and ($linuxkernelrelease ne ""))
  {
    $kernel_string = $linuxkernelrelease;
  }
  else
  {
    $kernel_string = `uname -r`;
  }
  chomp($kernel_string);

  # some examples for kernel_string
  #   SLES11 SP2: 3.0.13-0.27-default
  #   SLES11 SP3: 3.0.76-0.11-default
  #   SLES12:     3.12.61-52.111-default
  #   SLES12 SP1: 3.12.74-60.64.69-default
  #   RHEL 6.1:   2.6.32-131.0.15.el6.ppc64
  #   RHEL 6.2:   2.6.32-220.el6.x86_64
  #   RHEL 6.3:   2.6.32-279.el6.x86_64
  #   Debian 6:   2.6.32-5-amd64
  #   Ubuntu 14.04.04: 4.2.0-27-generic
  if ($kernel_string =~ /([23456])\.(\d+)\.(\d+)(.*)$/)
  {
    # Obtain the version, release, modification and fix level.
    $version = $1;
    $release = $2;
    $modification_actual = $modification = $3;
    $fix = $4;
    $fix_verbose = $4;

    if ($modification > 99)
    {
      # Reduce modification to two digits to fit format string.
      $modification = 99;
    }

    if ($fix =~
        /^([.-])(\d+)([.-])(\d+)([.-])(\d+)([._])(\d+)([.-])(\d+)([.-])(\d+)/)
    {
      # Fix level utilizes a multi-numeric format "." or "-" separated.
      # example:
      #    $fix is 131 in RHEL 6.1, will be 131
      $fix = $2;
      if (is_CrayOS($distribution, $kernel_string))
      {
        # e.g., CrayOS with kernel string value is
        # 5.14.21-150400.24.11_12.0.52-cray_shasta_c
        $fix_actual = $1 . $2 . $3 . $4 . $5 . $6 . $7 . $8 . $9 . $10 . $11 . $12;
      }
      else
      {
         $fix_actual = $1 . $2 . $3 . $4 . $5 . $6;
      }
      $fix_minor1 = $4;
      $fix_minor2 = $6;
    }
    elsif ($fix =~ /^([.-])(\d+)([.-])(\d+)/)
    {
      # Fix level utilizes a multi-numeric format "." or "-" separated.
      # example: 
      #     $fix is 0 in SLES11 SP2 or SPES11 SP3
      $fix = $2;
      $fix_actual = $1 . $2 . $3 . $4;
      $fix_minor1 = $4;
    }
    elsif ($fix =~ /^([-])(\d+)(-amd64)$/)
    {
      # Only for Debian on x86 64 bit:
      # Fix level utilizes a single-numeric format, 
      # suffixed with -amd64
      # example:
      #     $fix is 5 in Debian 6
      $fix = $2;
      $fix_actual = $2 . $3;
    }
    elsif ($fix =~ /^([.-])(\d+)/)
    {
      # Fix level utilizes a single-numeric format.
      # example:
      #     $fix is 220 in RHEL 6.2, will be 220
      #     $fix is 279 in RHEL 6.3, will be 279
      $fix = $2;
      $fix_actual = $1 . $2;
    }
    else
    {
      # No fix level in format; assign one to fit format string.
      $fix_actual = $fix = "000";
      $fix_verbose = "0000";
    }

    if ($fix ne "000")
    {
      $fix_verbose = $fix;
      if ($fix_verbose > 9999)
      {
        $fix_verbose = 9999;
      }
    }

    if ($fix > 999)
    {
      # Reduce fix to three digits to fit format string.
      $fix = 999;
    }
    if ($fix_minor1 > 999)
    {
      $fix_minor1 = 999;
    }
    if ($fix_minor2 > 999)
    {
      $fix_minor2 = 999;
    }

    $kernel = sprintf("%01d%02d%02d%03d", $version, $release,
                         $modification, $fix);
    $kernel_verbose = sprintf("%01d%02d%02d%04d%03d%03d", $version, $release,
                                 $modification, $fix_verbose, $fix_minor1, $fix_minor2);
    $kernel_actual = sprintf("%d.%d.%d%s", $version, $release,
                                $modification_actual, $fix_actual);
  }
  else
  {
    die("Cannot parse kernel version $kernel_string\n");
  }

  if ($linuxkernelrelease ne "RUNNING_KERNEL")
  {
    print "  kernel version = $kernel ($kernel_verbose, $kernel_string, $kernel_actual) \n" if($verifyReq);
  }
  return ($rc, $kernel, $kernel_verbose, $kernel_string, $kernel_actual);
}

#----------------------------------------------------------------------
# Function: findKernelHeader
#
# INPUT     distribution, kernel versions
# RETURN    rc (1: version.h or autoconf.h mismatch beteen kernel src/bld dir
#                  only on SuSE
#               0: no configuration problem found)
#           kernel_hdr_dir
#           kernel_bld_dir
#----------------------------------------------------------------------
sub findKernelHeader
{
  my ($distribution, $dist_version, $kernel_string, $kernel_actual) = @_;

  my $rc = 0;
  my $kernel_bld_dir = "";
  my $kernel_hdr_dir = "";

  my $lib_mod_bld_dir = "/lib/modules/$kernel_string/build";
  my $lib_mod_inc_dir = "$lib_mod_bld_dir/include";
  my $src_dir_sub = ($distribution eq "SUSE_LINUX") ?
                   "linux-$kernel_actual" : "linux-$kernel_string";
  my $usr_src_bld_dir = "/usr/src/$src_dir_sub";
  my $usr_src_inc_dir = "$usr_src_bld_dir/include";
  my $version_in_lib_mod_dir = "$lib_mod_inc_dir/linux/version.h";
  my $version_in_usr_src_dir = "$usr_src_inc_dir/linux/version.h";
  my $version_in_lib_mod_uapi_dir = "$lib_mod_inc_dir/generated/uapi/linux/version.h";

  if($verifyReq)
  {
    print "  module include dir = $lib_mod_inc_dir \n"; 
    print "  module build dir   = $lib_mod_bld_dir \n"; 
    print "  kernel source dir  = $usr_src_inc_dir \n"; 
  }

  # If the requested build is for the local machine, 
  # then we can determine the following information from local files. 
  # Otherwise, the user will have to provide that information.
  if ($bgp_io_gpl_build)
  { 
    $kernel_hdr_dir = $bgp_io_kernel_hdr_dir;
    $kernel_bld_dir = $bgp_io_kernel_bld_dir;
  }
  elsif ($distribution eq "SUSE_LINUX")
  {
    if ($dist_version eq "11" || $dist_version eq "12" || $dist_version eq "15")
    {
      if (-f $version_in_lib_mod_dir)
      {
        $kernel_hdr_dir = $lib_mod_inc_dir;
        $kernel_bld_dir = $lib_mod_bld_dir;
      }
      elsif (-f $version_in_lib_mod_uapi_dir)
      {
        $kernel_hdr_dir = $lib_mod_inc_dir;
        $kernel_bld_dir = $lib_mod_bld_dir;
      }
      elsif (-f $version_in_usr_src_dir)
      {
        $kernel_hdr_dir = $usr_src_inc_dir;
        # cannot guess the value in this case - let the user specify this
        $kernel_bld_dir = "";
      } 
      else
      {
         print ("  Cannot find a valid kernel header file. One of these files should exist.\n");
         print ("      $version_in_lib_mod_dir\n");
         print ("      $version_in_lib_mod_uapi_dir\n");
         print ("      $version_in_usr_src_dir\n");
      }
    }
    else
    {
      die ("unsupported version : SLES $dist_version\n");
    }

    # ensure that the source directory has the right autoconf.h and version.h
    # copied to it.  Due to some distro bugs this may not always be the case.
    if ($kernel_hdr_dir ne "" && $kernel_bld_dir ne "")
    {
      my $version_in_kern_hdr_dir = "$kernel_hdr_dir/linux/version.h";
      my $version_in_kern_bld_dir = "$kernel_bld_dir/include/linux/version.h";
      # from SLES 12, version.h moved to generated/uapi
      if (-f $version_in_lib_mod_uapi_dir)
      {
        $version_in_kern_hdr_dir = "$kernel_hdr_dir/generated/uapi/linux/version.h";
        $version_in_kern_bld_dir = "$kernel_bld_dir/include/generated/uapi/linux/version.h";
      }
      if (system("/usr/bin/diff -q $version_in_kern_hdr_dir $version_in_kern_bld_dir") != 0)
      {
        print ("  Kernel source tree does not have the correct version.h file.\n");
        print ("  $version_in_kern_hdr_dir and $version_in_kern_bld_dir should be identical\n");
        $rc = 1;
      }

      my $conf_in_kern_hdr_dir;
      my $conf_in_kern_bld_dir;
      if (($dist_version  > 11) || 
        ($dist_version == 11 && $dist_patchlevel >= 2))
      {
        $conf_in_kern_hdr_dir =  "$kernel_hdr_dir/generated/autoconf.h";
        $conf_in_kern_bld_dir =  "$kernel_bld_dir/include/generated/autoconf.h";
      } 
      else
      {
        $conf_in_kern_hdr_dir =  "$kernel_hdr_dir/linux/autoconf.h";
        $conf_in_kern_bld_dir =  "$kernel_bld_dir/include/linux/autoconf.h";
      }
      if (system("/usr/bin/diff -q $conf_in_kern_hdr_dir $conf_in_kern_bld_dir") != 0)
      {
        print ("  Kernel source tree does not have the correct autoconf.h file.\n");
        print ("  $conf_in_kern_hdr_dir and $conf_in_kern_bld_dir should be identical\n");
        $rc = 1;
      }
      if($rc)
      {
        print ("  See /usr/lpp/mmfs/src/README for further information\n");
        return($rc, $kernel_hdr_dir, $kernel_bld_dir);
      }
    }
  }
  elsif ($distribution eq "REDHAT_AS_LINUX" || 
         $distribution eq "KERNEL_ORG_LINUX" || 
         $distribution eq "FEDORA_LINUX" ||
         $distribution eq "DEBIAN_LINUX" ||
         $distribution eq "UBUNTU_LINUX" ||
         $distribution eq "ALICLOUD_LINUX" ||
         $distribution eq "IBM_PKVM_LINUX")
  {
    my $usr_src_uapi_bld_dir = "/usr/src/kernels/$kernel_string";
    my $usr_src_uapi_hdr_dir = "$usr_src_uapi_bld_dir/include"; 
    my $version_in_uapi = "$usr_src_uapi_hdr_dir/generated/uapi/linux/version.h";

    if (-f $version_in_lib_mod_dir)
    {
      $kernel_hdr_dir = $lib_mod_inc_dir;
      $kernel_bld_dir = $lib_mod_bld_dir;
    }
    elsif (-f $version_in_usr_src_dir)
    {
      $kernel_hdr_dir = $usr_src_inc_dir;
      $kernel_bld_dir = $usr_src_bld_dir;
    }
    elsif (-f $version_in_uapi)
    {
      $kernel_hdr_dir = $usr_src_uapi_hdr_dir;
      $kernel_bld_dir = $usr_src_uapi_bld_dir;
    }
    elsif (-f $version_in_lib_mod_uapi_dir)
    {
      $kernel_hdr_dir = $lib_mod_inc_dir;
      $kernel_bld_dir = $lib_mod_bld_dir;
    }
    else
    {
       print ("  Cannot find a valid kernel header file. One of these files should exist.\n");
       print ("      $version_in_lib_mod_dir\n");
       print ("      $version_in_usr_src_dir\n");
       print ("      $version_in_uapi\n");
       print ("      $version_in_lib_mod_uapi_dir\n");
    }
  }
  else 
  {
    #   should not happen
    die ("Invalid Linux distribution : $distribution.\n");
  }
  return ($rc, $kernel_hdr_dir, $kernel_bld_dir);
}

sub verify_SystemHeader
{
  # Obtain input parameters
  my $distribution = shift;
  my $dversion = shift;
  my $pkg_to_be_installed = $system_header{$distribution};

  if ($distribution eq "SUSE_LINUX" && $dversion >= 12)
  {
    $pkg_to_be_installed = "linux-glibc-devel";
  }
  if ($distribution eq "SUSE_LINUX" || $distribution eq "REDHAT_AS_LINUX")
  {
    print ("Verifying Additional System Headers...\n");
    verify_package_installed($pkg_to_be_installed, $distribution, "");
  }
}

#----------------------------------------------------------------------
# function: verify_kernelheader
#
# This function verifies if debug kernel header files 
# that needed to build gpl are installed
#
# INPUT  distribution
#        dist_version
#        dist_patchlevel : only used in SLES
#
# RETURN
#
#----------------------------------------------------------------------
sub verify_kernelheader
{
  my $rc = 0;
  my $rcf = 0;
  my ($kernel, $kernel_verbose, $kernel_actual, $kernel_string);
  my ($kernel_hdr_dir, $kernel_bld_dir);
  my @pkg_mgr = ();

  # Obtain input parameters
  my $distribution = shift;
  my $dist_version = shift;
  my $dist_patchlevel = shift;
  my $linuxkernelrelease = shift;
  my $contbuild = shift;
  my $pkg_to_be_installed = $kernel_header{$distribution};
  my $host_kernel_verbose = "";

  if ($linuxkernelrelease ne "")
  {
    # get the node running kernel release $host_kernel_verbose, and other
    # variables will be overwritten in the second call of getKernelVersion,
    # which are used to do the actual configuration works.
    ($rc, $kernel, $host_kernel_verbose, $kernel_string, $kernel_actual) =
        getKernelVersion($distribution, $dist_version, $dist_patchlevel, "RUNNING_KERNEL");
  }

  ($rc, $kernel, $kernel_verbose, $kernel_string, $kernel_actual) =
      getKernelVersion($distribution, $dist_version, $dist_patchlevel, $linuxkernelrelease);

  if ($contbuild ne "")
  {
      $host_kernel_verbose=$kernel_verbose;
  }

  if ($linuxkernelrelease ne "")
  {
    if ($distribution ne "REDHAT_AS_LINUX") # Only RHEL currently supported
    {
      if ($host_kernel_verbose != $kernel_verbose)
      {
        die ("Generating a kernel ($kernel_verbose) configuration from different ",
             "kernel ($host_kernel_verbose) node is not supported for $distribution\n");
      }
    }
    else
    {
      if ($host_kernel_verbose < $kernel_verbose)
      {
        die ("Generating a new kernel configuration ($kernel_verbose) from old ",
             "kernel node ($host_kernel_verbose) is not supported\n");
      }
      else
      {
=start comment
        Only same RHEL major and minor release kernel versions are supported.
        Major and minor release kernel version is the first 9 digits in the
        $kernel_verbose string.

        An example for RHEL 7.8:
        # uname -r
        3.10.0-1127.10.1.el7.x86_64

        $host_kernel_verbose=310001127010001
        The first 9 digits are "3 10 00 1127"

        For all of RHEL 7.8, the kernel release will be: 3.10.0-1127.x.y.el7.ARCH
=cut
        if (substr($host_kernel_verbose, 0, 9) ne substr($kernel_verbose, 0, 9))
        {
          die ("Generating a kernel configuration ($kernel_verbose) from different ",
               "major and minor release kernel node (".substr($host_kernel_verbose, 0, 9).") ",
               "is not supported\n");
        }
      }
    }
  }

  ($rc, $kernel_hdr_dir, $kernel_bld_dir) =
      findKernelHeader($distribution, $dist_version, $kernel_string, $kernel_actual);

  if( $kernel_hdr_dir ne "")
  {
    if($rc == 0)
    {
       return(0, $kernel, $kernel_verbose, $kernel_hdr_dir, $kernel_bld_dir);
    }
    # else
    #   kernel include directory exists but something is wrong.
    #   since proper error message is displayed inside findKernelHeader(),
    #   no more error message here. Let's check if package is properly installed.
  } 
  # else
  #   can not find kernel header file.
  #   since proper error message is displayed inside findKernelHeader(),
  #   no more error message here. Let's check if package is properly installed.
  return(1, $kernel, $kernel_verbose, $kernel_hdr_dir, $kernel_bld_dir) if ($verifyReq == FALSE);
  if($pkg_to_be_installed ne "")
  {
    if($distribution eq "DEBIAN_LINUX")
    {
      $pkg_to_be_installed = $pkg_to_be_installed . "-" . $kernel_string; 
    }
    $rc = verify_package_installed($pkg_to_be_installed, $distribution, "headers are");
    if($rc == 0)
    {
      ($rc, $kernel_hdr_dir, $kernel_bld_dir) = 
        findKernelHeader($distribution, $dist_version, $kernel_string, $kernel_actual);
    } 
    return($rc, $kernel, $kernel_verbose, $kernel_hdr_dir, $kernel_bld_dir);
  } else
  {
    print ("  Cannot find proper package name for $distribution.\n");
    return(1, $kernel, $kernel_verbose, $kernel_hdr_dir, $kernel_bld_dir);
  }
}

#-------------------------------------------------------------------------------
# function: get_OsLevel
#
# This function determines the target AIX level.
#-------------------------------------------------------------------------------
sub get_OsLevel
{
  local ($os_string);
  local ($version, $release, $modification);
  local ($oslvl);

  $os_string = `oslevel`;
  chomp($os_string);

  if ($os_string =~ /(\d).(\d).(\d).(\d)/)
  {
    # Obtain the version, release, modification.
    $version = $1;
    $release = $2;
    $modification = $3;

    $oslvl = sprintf("%d%d%d", $version, $release, $modification);
  }
  else
  {
    die ("Cannot find a valid AIX level\n");
  }

  return($oslvl);
}
#-------------------------------------------------------------------------------
# function: get_KernelMode
#
# This function determines whether AIX kernel is 32 or 64 bit
#-------------------------------------------------------------------------------
sub get_KernelMode
{
  local ($kernel_string);
  local ($kernel_mode);

  $kernel_string = `/usr/sbin/prtconf -k`;
  chomp($kernel_string);

  if ($kernel_string =~ /Kernel Type: (\d\d)-bit/)
  {
    # Obtain bits setting
    $kernel_mode = sprintf("%d", $1);
  }
  else
  {
    die ("Cannot determine AIX kernel mode\n");
  }

  return($kernel_mode);
}


sub get_ExceptionTableVersion
{
  my ($os, $dist, $dist_version, $dist_patchlevel, $dist_sublevel, $kernel, $kernel_verbose) = @_;

  my $ex_table_ver = "";
  my $ex_table_def = "";
  my $ex_alt_ver = "";

  # Avoid "Argument "" isn't numeric" warning in numeric comparisons if
  # $dist_version, $dist_patchlevel, or $dist_sublevel are empty
  if ($dist_version eq "")    { $dist_version = 0; }
  if ($dist_patchlevel eq "") { $dist_patchlevel = 0; }
  if ($dist_sublevel eq "")   { $dist_sublevel = 0; }

  #  Linux kernel has several versions of exception table:
  #  V4: relative exception table with exception type
  #      since kernel version 5.16 or SLES 15 SP4 or RHEL9 or
  #      Ubuntu 22.04 kernel 5.15.0-30
  #  V3: since kernel version 4.6 or SLES 12.2, relative exception table
  #      and with an exception handler.
  #  V2: since kernel version 3.5, relative exception table.
  #  V1: absolute exception table  */

  if ($kernel >= 51600000) {
    $ex_table_ver = "V4";
  } elsif  (($kernel >= 40600000) || ($dist_version == 12 && $dist_patchlevel >= 2)) {
    $ex_table_ver = "V3";
    $ex_table_def = "-DHAVE_EX_HANDLER_DEFAULT";
  } elsif  ($kernel >= 30500000) {
    $ex_table_ver = "V2";
  } else {
    $ex_table_ver = "V1";
  }

  if ($dist eq "REDHAT_AS_LINUX") {
    if ($kernel >= 51400038) {
      $ex_table_ver = "V4";
      $ex_table_def = "";
    }
  }

  if ($dist eq "SUSE_LINUX") {
    if ($kernel >= 51421000) {
      $ex_table_ver = "V4";
      $ex_table_def = "";
    }
  }

  if ($dist eq "UBUNTU_LINUX") {
    if ($kernel >= 51500030) {
      $ex_table_ver = "V4";
      $ex_table_def = "";
    }
  }

  if ($dist eq "ALICLOUD_LINUX") {
    $ex_table_ver = "V3";
    $ex_table_def = "";
  }

  #  Linux alternatives method version. Support is started from relative
  #  exception table, so, V3 version is since the version of relative
  #  exception table. */

  #  commit 23c1ad538f4f371bdb6  in kernel 5.13 removes 'padlen' from
  #  alt_instr struct. Making it similar to V3. */

  #  commit 5d1dd961e7433 changes the format of alt_instr structure
  #  from kernel version 6.3, version V6 is used.

  if ($kernel >= 60300000) {
    $ex_alt_ver = "V6";
  } elsif ($kernel >= 51300000) {
    $ex_alt_ver = "V3";
  } elsif  ($kernel >= 40100000) {
    $ex_alt_ver = "V4";
  } elsif  ($kernel >= 30500000) {
    $ex_alt_ver = "V3";
  } else {
    $ex_alt_ver = "";
  }

  if ($dist eq "REDHAT_AS_LINUX"){
    if ($kernel >= 51400503) {
      $ex_alt_ver = "V6";
    }
  }

  if ($dist eq "SUSE_LINUX")
  {
    if ($dist_version == 12 && $dist_patchlevel == 1 &&
        $kernel_verbose >= 312740060064069 && $kernel_verbose < 312740060064082) {
      $ex_alt_ver = "V4";
    } elsif  ($dist_version == 12 && $dist_patchlevel == 0 &&
                $kernel_verbose >= 312610052111000 && $kernel_verbose < 312610052119000) {
      $ex_alt_ver = "V4";
    }
  }

  if ($dist eq "UBUNTU_LINUX")
  {
    if ($dist_version == 14 && $dist_patchlevel == 4 && $kernel >= 31300139 && $kernel < 31600030) {
      $ex_alt_ver = "V4";
    }
  }

  if ($dist eq "DEBIAN_LINUX")
  {
    # Debian 8: from version 3.16.51-3+deb8u1 (3.16.0-5), previous one is 3.16.51-3 (3.16.0-4) */
    if ($kernel >= 31600005) {
      $ex_alt_ver = "V4";
    }

    # Debian 7: from version 3.2.96-3 (3.2.0-5), previous one is 3.2.96-2 (3.2.0-4) */
    if ($kernel >= 30200005 && $kernel < 31600000) {
      $ex_alt_ver = "V4";
    }
  }

  if ($dist eq "ALICLOUD_LINUX")
  {
    if ($kernel >= 51000000) {
      $ex_alt_ver = "V3";
    }
  }

  return ($ex_table_ver, $ex_table_def, $ex_alt_ver)
}

#----------------------------------------------------------------------
# Function: getKernelCryptoSkcipher
#
# This function gets the kernel cipher type for cbc(aes). Please note
# that this routine uses /proc/crypto, which provides information abt
# the cipher algs already loaded by the kernel. Considering that cbc-aes
# and xts-aes are very popular algs, they are either compiled in or have
# already been loaded by the time we do this check. However, in the
# unlikely case that they are not available in /proc/crypto, the sys
# admin must load the kernel modules that provide that functionality.
#
# This function reads /proc/crypto in its entirety and looks for algs
# of type cbc(aes). There may be multiple entries, as it happens on
# s390. If that is the case, select the algorithm type for the hardware
# accelearted algorithm. Currently, only s390 has such an algo, which
# is blkcipher. There is also an skcipher implementation of cbc(aes)
# that is not hardware accelerated. However, we will use the type of
# the hardware accelerated algorithm.
#
# INPUT     machine architecture, kernel header path
# RETURN    true if skcipher, false otherwise
#----------------------------------------------------------------------
sub getKernelCryptoSkcipher
{
  my $arch = shift;
  my $hdrdir = shift;
  my ($line, @drivername, @ciphertype);
  my $cbcaes = 0;
  my $idx = 0;
  my $hwaccld_idx = -1;
  my $skcipher_idx = -1;
  my $has_crypto_skcipher_encrypt = 0;

  # Before looking at /proc/crypto check whether the kernel header files
  # to be used by the build have the necessary declarations of kernel
  # crypto functions.  This is necessary for a containerized build
  # environment, where the target kernel we want to build for does not
  # match the kernel that the container is running on, and therefore,
  # looking at /proc/crypto may give the wrong answer, causing the build
  # to fail.
  # If the kernel header files do not have the necessary declarations,
  # we return with false, as we cannot build for skcipher algs anyway.
  # If we find the necessary declarations in the kernel header files, we
  # go and check /proc/crypto for runtime support for the skcipher algs.
  # However, if the kernel header files don't exist in the specified
  # location, just continue to check /proc/crypto, as it may be an
  # OS distro that is not used in the containerized build.

  if (open(SKCIPHER, "$hdrdir/crypto/skcipher.h"))
  {
    foreach $line (<SKCIPHER>)
    {
      if ($line =~ /crypto_skcipher_encrypt/)
      { 
        $has_crypto_skcipher_encrypt = 1;
        last;
      }
    }
    close(SKCIPHER);

    if (!$has_crypto_skcipher_encrypt)
    {
	  return 0;
    }
  }

  open(PROCCRYPTO, "/proc/crypto") || die "Cannot open /proc/crypto";
  foreach $line (<PROCCRYPTO>)
  {
    if ($line =~ m/name *: *cbc\(aes\)/)
    {
      if ($cbcaes == 1)
      {
        die "Unexpected format of /proc/crypto";
      }

      # this is the beginning of a new entry
      $cbcaes = 1;

      # initialize the arrays entries
      $drivername[$idx] = "";
      $ciphertype[$idx] = "";
    }

    if ($cbcaes == 1 && $line =~ m/driver *: /)
    {
      # this is the driver line of the current entry
      $drivername[$idx] = (split(/: /, $line))[1];
      chomp($drivername[$idx]);

      # check for the hardware accelerated driver--thus far, only
      # s390 has such a driver, but that may change in the future
      if ($arch eq "GPFS_ARCH_S390X")
      {
        if ($drivername[$idx] eq "cbc-aes-s390")
        {
          $hwaccld_idx = $idx;
        }
      }
    }

    if ($cbcaes == 1 && $line =~ m/type *: /)
    {
      # this is the type line of the current entry
      $ciphertype[$idx] = (split(/: /, $line))[1];
      chomp($ciphertype[$idx]);

      if ($ciphertype[$idx] eq "skcipher")
      {
        $skcipher_idx = $idx;
      }

      # we are done with this entry
      $cbcaes = 0;
      
      # increment the index for the driver and type arrays
      $idx++;
    }
  }

  close(PROCCRYPTO);

  if ($hwaccld_idx >= 0)
  {
    return($ciphertype[$hwaccld_idx] eq "skcipher");
  }

  if ($skcipher_idx >= 0)
  {
    return(1);
  }

  return(0);
}

#-------------------------------------------------------------------------------
# function: write_env
#
# This function writes the build env.mk file by reading system environment.
#-------------------------------------------------------------------------------
sub write_env()
{
  my ($os, $architecture, $dist, $dist_version, $dist_patchlevel, $dist_sublevel,
      $gcc_version, $kernel, $kernel_verbose, $kernel_hdr_dir, $kernel_bld_dir,
      $config_dir, $oslvl, $kernel_mode, $kernel_crypto_skcipher) = @_;

  my ($ex_table_ver, $ex_table_def, $ex_alt_ver);
  my $line = "";

  if ($os eq "GPFS_LINUX")
  {
    ($ex_table_ver, $ex_table_def, $ex_alt_ver) =
        get_ExceptionTableVersion($os, $dist, $dist_version,
                                  $dist_patchlevel, $dist_sublevel,
                                  $kernel, $kernel_verbose);
  }

  #-----------------------------
  # Generate the env.mk file which is editable
  #-----------------------------

  open(CONFIG, ">$config_dir/env.mk") ||
    die "Unable to open configuration file env.mk\n";

  $line .= "$architecture = Y\n";
  $line .= "$os = Y\n";

  if ($os eq "GPFS_AIX")
  {
    $line .= "AIX_VERSION_DEPS = -DSPAIXLVL=$oslvl\n";
  }
  elsif ($os eq "GPFS_LINUX")
  {
    if ($gcc_version >= 80000)
    {
      $line .= "RUNNING_GCC_8 = Y\n";
    }
    $line .= "LINUX_DISTRIBUTION = $dist\n";
    if (-f $suse_release)
    {
        $line .= "SUSE_VERSION = $dist_version\n";
        $line .= "SUSE_PATCHLEVEL = $dist_patchlevel\n";
    }
    if ((-f $debian_release) && (-f $lsb_release))
    {
        $line .= "UBUNTU_VERSION = $dist_version\n";
        $line .= "UBUNTU_PATCHLEVEL = $dist_patchlevel\n";
        $line .= "UBUNTU_SUBLEVEL = $dist_sublevel\n";
    }
    $line .= "LINUX_KERNEL_VERSION = $kernel\n";
    $line .= "LINUX_KERNEL_VERSION_VERBOSE = $kernel_verbose\n";
    $line .= "KERNEL_BUILD_DIR  = $kernel_bld_dir\n";
    $line .= "KERNEL_HEADER_DIR = $kernel_hdr_dir\n";
    if ($bgp_io_gpl_build == TRUE)
    {
        if (-f $redhat_release)
        {
            $line .= "GPFS_BLUEGENE_Q = Y\n";
        }
        $line .= "INSTALL_MOD_PATH = $BGP_IO_ROOT\n";
        $line .= "export INSTALL_MOD_PATH\n";
    }
    if ($kernel_crypto_skcipher == TRUE)
    {
        $line .="DEFINE_GPFS_ENC = -DGPFS_CRYPTO_KERNEL_SKCIPHER\n";
    }

    $line .= "GPFS_EX_TABLE_VER = $ex_table_ver\n";
    $line .= "EXCEPTION_TABLE = $ex_table_def\n";
    $line .= "GPFS_ALTERNATIVE_VER = $ex_alt_ver\n";
  }

  print CONFIG "$line";
  close(CONFIG);
}

#------------------------------------------------------
# Function: is_CrayOS
#
# Determines if OS is CrayOS
# INPUT     distribution string, kernel version string
# RETURN    true if OS is CrayOS
#------------------------------------------------------
sub is_CrayOS($$)
{
   $dist = shift;
   $kernel_string = shift;
   return ($dist eq "SUSE_LINUX" && $kernel_string =~ m/cray/i) ? TRUE : FALSE;
}


#-------------------------------------------------------------------------------
# function: configure
#
# This function obtains the full set of build configuration values, generating
# the configuration file from the template.
#-------------------------------------------------------------------------------
sub configure()
{
  my ($sharkcloneroot, $linuxdistribution, $linuxkernelrelease, $contbuild) = @_;

  my $sharkcloneroot_prefix;
  my ($architecture, $dist, $os, $oslvl, $kernel_mode);
  my ($kernel, $kernel_verbose, $kernel_hdr_dir, $kernel_bld_dir);
  my $kernel_crypto_skcipher = TRUE;
  my $rc = 0;

  #------------------------
  # Verify source location
  #------------------------
  if ($sharkcloneroot =~ m/^[^\/]/)
  {
    # Source path not fully qualified
    print STDERR "Source path not fully qualified: $sharkcloneroot\n";
    return(-1);
  }

  #----------------------------------------------------------------
  # Verify build type (GPL or development) from template location.
  #----------------------------------------------------------------
  # Check if there is a prefix on SHARKCLONEROOT
  if ($sharkcloneroot =~ /\/usr\/lpp\/mmfs\/src/ )
  {
    $sharkcloneroot_prefix = $`;
    if ($sharkcloneroot_prefix eq $BGP_IO_ROOT || 
        $sharkcloneroot_prefix =~ /\/bgsys\//)
    {
      $bgp_io_gpl_build = TRUE;	  
    }
  }

  # Find the config directory by looking for the "kdef.mk" file
  # For GPL build:
  #   sharkcloneroot = /usr/lpp/mmfs/src
  #   config_dir = /usr/lpp/mmfs/src/config = $sharkcloneroot/gconfig
  # For development build:
  #   sharkcloneroot = $gitcloneroot/ts
  #   config_dir = $gitcloneroot/gconfig = $sharkcloneroot/../gconfig
  if (-e "$sharkcloneroot/config/kdef.mk")
  {
    $config_dir = "$sharkcloneroot/config";
  }
  elsif (-d "$sharkcloneroot/../gconfig")
  {
    $config_dir = "$sharkcloneroot/../gconfig";
  }
  else
  {
    print STDERR "Missing or incomplete source at: $sharkcloneroot/xxx. Cannot find kdef.mk\n";
    print STDERR "sharkcloneroot = $sharkcloneroot\n";
    return(-1);
  }

  #-------------------------
  # Determine the target OS
  #-------------------------
  $os = get_OS();
  $gcc_version = 0;
  if ($os eq "GPFS_LINUX")
  {
    #---------------------------------------
    # Determine Linux specific settings
    #---------------------------------------

    # Determine the Architecture
    $architecture = get_Architecture();

    # Get GCC version
    $gcc_version = get_gcc_version();

    if ($architecture eq "UNSUPPORTED")
    {
      print STDERR "Unsupported Architecture: $architecture\n";
      $rc = -1;
      return($rc);
    }
    if ($architecture eq "NOLONGER")
    {
      print STDERR "This Architecture is no longer supported\n";
      $rc = -1;
      return($rc);
    }

    #------------------------------------------------------
    # Determine the Distribution, Kernel, Kern Header Directory
    #------------------------------------------------------

    ($dist, $dist_version, $dist_patchlevel, $dist_sublevel) = get_DistValues($linuxdistribution, $linuxkernelrelease);

    if ($dist eq "UNSUPPORTED")
    {
      print STDERR "Unsupported Distribution: $linuxdistribution\n";
      $rc = -1;
      return($rc);
    }

    print ("Verifying Kernel Header...\n") if $verifyReq;
    ($rc_kh, $kernel, $kernel_verbose, $kernel_hdr_dir, $kernel_bld_dir) =
          verify_kernelheader($dist, $dist_version, $dist_patchlevel, $linuxkernelrelease, $contbuild);
    if($verifyReq && $rc_kh == 0 )
    {
      print ("  Found valid kernel header file under $kernel_hdr_dir\n");
    }
    my $is_cray_os = is_CrayOS($dist, $kernel_hdr_dir);
    my $ker_ver_major = substr($kernel, 0, 3);
    print "Getting Kernel Cipher mode...\n" if $verifyReq;
    if ($ker_ver_major >= 505 || $is_cray_os)
    {
      # For Kernel >= 5.5 (removed blkcipher)
      # or CrayOS, use 'skcipher' by default
      $kernel_crypto_skcipher = TRUE;
    }
    else
    {
      $kernel_crypto_skcipher = getKernelCryptoSkcipher($architecture, $kernel_hdr_dir);
    }
    if ( $verifyReq )
    {
      print ("   Will use skcipher routines\n") if $kernel_crypto_skcipher == TRUE;
      print ("   Will use blkcipher routines\n") if $kernel_crypto_skcipher == FALSE;
    }

    $rc_ccp = 0;
    if ($verifyReq)
    {
      print ("Verifying Compiler...\n");
      $rc_ccp = &verify_compiler($dist);
      if($buildrpm)
      {
        print ("Verifying rpmbuild...\n");
        $rc_ccp += &verify_rpmbuild($dist, $dist_version);
      }
      $rc_ccp += &verify_libelf_dev($dist);
      &verify_SystemHeader($dist, $dist_version);
    }
    if($rc_kh || $rc_ccp)
    {
      print ("\nContact IBM Service if you still encounter this problem after you install all the required packages.\n"); 
      if ($missing_packages ne "")
      {
        print "  Summary of missing packages: $missing_packages \n";
        $pkg_mgr = $package_manager{$dist};
        print "  You can install missing packages using package manager. \n";
        print "    example: $pkg_mgr->[1] $missing_packages\n\n";
      }
      exit 1;
    }
  }
  elsif ($os eq "GPFS_AIX")
  {
    #---------------------------------
    # Determine AIX specific settings
    #
    # NOTE: Only require architecture
    #---------------------------------
    $architecture = "GPFS_ARCH_POWER";

    #--------------------------------
    # Determine AIX level
    #--------------------------------
    $oslvl = get_OsLevel();

    #----------------------------------------
    # Determine Kernel mode (32 vs 64 bit)
    #----------------------------------------
    $kernel_mode = get_KernelMode();
  }
  else
  {
    #------------------------------
    # Unsupported Operating System
    #------------------------------

    print STDERR "Unsupported Operating System: $os\n";
    $rc = -1;
    exit($rc);
  }

  if (-e '/usr/include/readline') {
    $have_readline = 1;
  } else {
    $have_readline = 0;
  }

  # If we have gotten to this point without an error occurrence, then process
  # the configuration file according to arguments.
  &write_env($os, $architecture, $dist, $dist_version, $dist_patchlevel, $dist_sublevel,
             $gcc_version, $kernel, $kernel_verbose, $kernel_hdr_dir, $kernel_bld_dir,
             $config_dir, $oslvl, $kernel_mode, $kernel_crypto_skcipher);

  return(0);
}

#-------------------------------------------------------------------------------
# Main program
#-------------------------------------------------------------------------------

# Initialize utility variables
$rc = 0;

# Initialize core variables
$linuxdistribution = undef;
$linuxkernelrelease = undef;
$contbuild = "";
$genenvonly = FALSE;

# Process command-line arguments
foreach $arg (sort @ARGV)
{
  if ($arg =~ m/^-/)
  {
    # Option specified
    if ($arg eq "--help")
    {
      # Help requested ==> Display usage, exit success
      usage();
      exit(0);
    }
    elsif ($arg eq "--installreq")
    { 
        $installReq = TRUE;
        $verifyReq = TRUE;
    }
    elsif ($arg eq "--verifyreq")
    { 
        $verifyReq = TRUE;
    }
    elsif ($arg eq "--buildrpm")
    { 
        $buildrpm = TRUE;
    }
    elsif ($arg eq "--genenvonly")
    {
        $genenvonly = TRUE;
    }
    else
    {
      # Unknown option ==> Display usage, exit failure
      usage();
      exit(1);
    }
  }
  else
  {
    # Attribute/value pair specified
    ($attr, $value) = split("=", $arg);
    if ($attr eq "sharkcloneroot")
    {
      # Source override specified
      if (!$sharkcloneroot)
      {
        $sharkcloneroot = $value;
      }
      else
      {
###     prtMsg(36, "source");
        usage();
        exit(1);
      }
    }
    elsif ($attr eq "linuxdistribution")
    {
      # Source override specified
      if (!$linuxdistribution)
      {
        $linuxdistribution = $value;
      }
      else
      {
        usage();
        exit(1);
      }
    }
    elsif ($attr eq "linuxkernelrelease")
    {
      # Source override specified
      if (!$linuxkernelrelease)
      {
        $linuxkernelrelease = $value;
      }
      else
      {
        usage();
        exit(1);
      }
    }
    elsif ($attr eq "contbuild")
    {
      $contbuild = 1;
    }
    else
    {
      # Invalid override
      print STDERR "Invalid override: $attr\n";
      usage();
      exit(1);
    }
  }
}

#--------------------------------------
# Assign program defaults as necessary
#--------------------------------------

# Define the source directory. The default value may be overridden with the
# following priority utilized:
#     1) sharkcloneroot attribute setting supplied via command line
#     2) SHARKCLONEROOT environment variable
#     3) /usr/lpp/mmfs/bin default
if (!$sharkcloneroot)
{
  # sharkcloneroot attribute unspecified
  if (defined $ENV{SHARKCLONEROOT})
  {
    # SHARKCLONEROOT environment variable specified
    $sharkcloneroot = $ENV{SHARKCLONEROOT};
  }
  else
  {
    # SHARKCLONEROOT environment variable unspecified
    $sharkcloneroot = $ENV{PWD};
  }
}

if (!$linuxdistribution)
{
  if (defined $ENV{LINUX_DISTRIBUTION})
  {
    $linuxdistribution = $ENV{LINUX_DISTRIBUTION};
  }
  else
  {
    $linuxdistribution = "";
  }
}

if (!$linuxkernelrelease)
{
  if (defined $ENV{LINUX_KERNEL_RELEASE})
  {
    $linuxkernelrelease = $ENV{LINUX_KERNEL_RELEASE};
  }
  else
  {
    $linuxkernelrelease = "";
  }
}

#----------------------------------------------------------
# Process the configuration; Return success/failure status
#----------------------------------------------------------
$rc = &configure($sharkcloneroot, $linuxdistribution, $linuxkernelrelease, $contbuild);
exit($rc);

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