#!/bin/sh
# IBM(c) 2013 EPL license http://www.eclipse.org/legal/epl-v10.html

### BEGIN INIT INFO
# Provides: xcatconf4z
# Default-Start: 2 3 5
# Default-stop: 0 1 4 6
# Required-Start: $syslog
# Should-Start: 
# Required-Stop:
# Short-Description: xCAT disk initialization and configuration
# Description: Reads class x files from the reader and acts based on the type of file.
#              Files of filetype "disk" cause SCSI disks be configured.
#              Other files are used to generate an ISO9660 disk for transporting xCAT configurations.
### END INIT INFO

###############################################################################
# Authorized senders of configuration files listed here.  Specify a list a
# blank delimited list of userids with each userid in uppercase
# (e.g. "ZHCP" or "ZHCP ZHCP2"), or '*' to indicate all are authorized.
# If nothing is specified then this function will not process any
# configuration files in the reader.
###############################################################################
authorizedSenders=''

function getOsVersion {
  # @Description:
  #   Returns the Linux distro version in an easy to parse format.
  # @Input:
  #   None
  # @Output:
  #   os - Variable set with OS and version information.  For example:
  #        "rhel62" or "sles11sp2"
  # @Code:
  release=`cat /etc/*release`
  
  if echo $release | grep -i "SUSE Linux Enterprise Server" > /dev/null ; then
    os='sles'
    version=`echo "$release" | grep "VERSION =" | sed \
      -e 's/^.*VERSION =//' \
      -e 's/\s*$//' \
      -e 's/.//' \
      -e 's/[^0-9]*([0-9]+).*/$1/'`
    os=$os$version;
    
    # Append service level
    level=`echo "$release" | grep "LEVEL =" | sed \
      -e 's/^.*LEVEL =//' \
      -e 's/\s*$//' \
      -e 's/.//' \
      -e 's/[^0-9]*([0-9]+).*/$1/'`
    os=$os'sp'$level
    
  elif version=`echo $release | grep -i "Red Hat Enterprise Linux Server"`; then
    os='rhel'
    version=`echo $version | sed \
      -e 's/[A-Za-z\/\.\(\)]//g' \
      -e 's/^ *//g' \
      -e 's/ *$//g' \
      -e 's/\s.*$//'`
    os=$os$version
  fi
  return
}

function onlineDevice {
  # @Description:
  #   Brings a Linux device online.
  # @Input:
  #   Device number, e.g. "0.0.000c"
  # @Output:
  #   Return code indicates success or failure
  # @Code:
  device=$1
  local funcName="onlineDevice"
  rc=$(/sbin/chccwdev -e $device > /dev/null; echo $?)
  if (( rc != 0 )); then
    if [[ -e /sbin/cio_ignore ]]; then
      rc=$(/sbin/cio_ignore -r 0.0.$device > /dev/null; echo $?)
      which udevadm &> /dev/null && udevadm settle || udevsettle
    fi
    rc=$(/sbin/chccwdev -e $device > /dev/null; echo $?)
    if (( rc != 0 )); then
      echo "xcatconf4z $funcName (Error) Could not activate the virtual reader"
      return 1
    fi
  fi
  which udevadm &> /dev/null && udevadm settle || udevsettle
  return 0
}

function pullReader {
  # @Description:
  #   Reads class x spool files from the reader if sent by an authorized sender.
  #   Drives special processing functions for files of a specific type.
  #   Files with a filetype of:
  #     tgz are unpacked into the transport directory
  #     disk files are read and cause the setupDisk function to be driven
  #     all other files are unpacked into the transport directory
  # @Input:
  #   None
  # @Output:
  #   Return code indicates success if reader was brought online.
  # @Code:
  local funcName="pullReader"
  /sbin/modprobe vmcp
   
  # Online reader
  rc= onlineDevice "000c"
  if (( rc != 0 )); then
      return $rc
  fi
  
  # Grab the spool Id, class file name, and file type
  eval records=($(/usr/sbin/vmur li | tail -n +2 | cut -c 1-72 | awk '{print $1":"$2":"$3":"$10":"$11"\n"}' ))
  
  # Process each spool file that is class "x"
  for record in "${records[@]}"
  do
    record=$(echo $record | tr ":" " ")
    set $record
    originid=$1
    spoolid=$2
    class=$3
    filename=$4
    type=$5
        
    if [[ $class != "X" ]]; then
      # Spool file is not of the class required for processing by this script.
      continue
    fi
    
    if [[ $authorizedSenders != "*" ]]; then
      if [[ " $authorizedSenders " != *" $originid "* ]]; then
        # Originator is not authorized to send configuration files.
        continue
      fi
    fi
    
    if [[ -n $type ]]; then
      file="$filename.$type"
    else
      file=$filename
    fi
    
    # Receive the spool file
    echo "Downloading record $spoolid: $file"
    
    if [[ $type == "txt" ]] || [[ $type == "sh" ]]; then
      # Receiving text
      rc=$(/usr/sbin/vmur re -t $spoolid $file)
    elif [[ $type == "tgz" ]]; then
      rc=$(/usr/sbin/vmur re $spoolid $file)
      /bin/tar xzf $file -C $transportdir
      rm $file
    elif [[ $type == "disk" ]]; then
      rc=$(/usr/sbin/vmur re $spoolid $file)
      if (( rc == 0 )); then
        setupDisk $transportdir'/'$file
        rc=0
      fi
    else
      # Receive block
      rc=$(/usr/sbin/vmur re $spoolid $file)
    fi
    
    if (( rc != 0 )); then
      echo "xcatconf4z funcName (Error) Failed to download record $spoolid"
    fi
  done
  return 0
}

function setupIso {
  # @Description:
  #   Makes an ISO filesystem using the contents of the transport directory and
  #   creates a loop device pointing to the ISO image.  If an "init.sh" script
  #   exists in the transport directory then it is driven.
  # @Input:
  #   None
  # @Output:
  #   None
  # @Code:
  # Create ISO based on transport directory
  iso="/var/opt/xcat/transport.iso"
  /usr/bin/mkisofs -l -o $iso $transportdir
  
  # Create loop back device pointing to ISO9660 image
  nextLoopDev=`/sbin/losetup -f`
  if [[ -n $nextLoopDev ]]; then
    /sbin/losetup $nextLoopDev $iso
  else
    return
  fi
  
  # Execute init script (if one exists)
  if [[ -e ${transportdir}/init.sh ]]; then
    chmod 755 ${transportdir}/init.sh
    ${transportdir}/init.sh
  fi
}

function setupDisk {
  # @Description:
  #   Processes a disk file for the following functions:
  #     create a file system node
  #     Setup a SCSI volume
  #     Removes a SCSI volume
  # @Input:
  #   Location and name of the disk file.
  # @Output:
  #   None
  # @Code:
  diskFile=$1
  local funcName="setupDisk"
  
  # Read the file and verify we want to handle it
  if ! grep -q "# xCAT Init" "$diskFile"; then
    # File is not one that we handle.  Leave it alone.
    return
  fi
  
  # Read the file now that we know it is our file
  oldIFS=$IFS
  IFS=$'\n'
  for line in $(cat "$diskFile"); do
    if [[ $line == \#* ]]; then
      # Ignore comment lines
      continue
    fi
    keyName=${line%\=*}
    value=${line#*\=}
    value=$(echo ${value} | sed -e 's/^ *//g')
    newKey='xcat_'$keyName
    eval $newKey=$value
  done
  IFS=$oldIFS
  
  # Remove the disk file after we have read it
  rm $diskFile
  
  ##########################################################################
  # Handle creating a file system node
  # Disk file input parameters:
  #   action - "createfilesysnode"
  #   srcFile - location/name of the source file for the mknod command
  #   tgtFile - location/name of the target file for the mknod command
  ##########################################################################
  if [[ $xcat_action == "createfilesysnode" ]]; then
    echo "Creating a file system node, source: $xcat_srcFile, target: $xcat_tgtFile"
    
    if [[ ! -n $xcat_srcFile ]]; then
      echo "xcatconf4z $funcName (Error) Source file for creating a file system node was not specified"
      return
    fi
    
    if [[ ! -n $xcat_tgtFile ]]; then
      echo "xcatconf4z $funcName (Error) Target file for creating a file system node was not specified"
      return
    fi
    if [[ -e $xcat_tgtFile ]]; then
      echo "xcatconf4z $funcName (Error) Target file for creating a file system node already exists"
      return
    fi
    
    out=`stat -L --printf=%t:%T $xcat_srcFile`
    if (( $? != 0 )); then
      echo "xcatconf4z $funcName (Error) Unable to stat the source file: $xcat_srcFile"
      return
    fi
    
    major=${out%:*}
    major=$(echo ${major} | sed -e 's/^ *//g')
    minor=${out#*:}
    minor=$(echo ${minor} | sed -e 's/^ *//g')
    
    mknod $xcat_tgtFile b 0x$major 0x$minor
  
  ##########################################################################
  # Handle removing a file system node
  # Disk file input parameters:
  #   action - "removefilesysnode"
  #   tgtFile - location/name of the target file for the mknod command
  ##########################################################################
  elif [[ $xcat_action == "removefilesysnode" ]]; then
    echo "Removing a file system node, target: $xcat_tgtFile"
    if [[ ! -n $xcat_tgtFile ]]; then
      echo "xcatconf4z $funcName (Error) Target file for creating a file system node was not specified"
      return
    fi
    
    umount "$xcat_tgtFile"
    rm -f "$xcat_tgtFile"
  
  ##########################################################################
  # Handle adding a SCSI volume
  # Disk file input parameters:
  #   action - "addScsiVolume"
  #   fcpAddr - FCP device address
  #   wwpn - WWPN number
  #   lun - LUN number
  ##########################################################################
  elif [[ $xcat_action == "addScsiVolume" ]]; then
    echo "Adding a SCSI Volume, FCP addr: $xcat_fcpAddr, WWPN: $xcat_wwpn, LUN: $xcat_lun"
    
    # Validate the input
    if [[ ! -n $xcat_fcpAddr ]]; then
      echo "xcatconf4z $funcName (Error) FCP address was not specified"
      return
    fi
    xcat_fcpAddr=`echo $xcat_fcpAddr | tr '[A-Z]' '[a-z]'`
    
    if [[ ! -n $xcat_wwpn ]]; then
      echo "xcatconf4z $funcName (Error) WWPN was not specified"
      return
    fi
    xcat_wwpn=`echo $xcat_wwpn | tr '[A-Z]' '[a-z]'`
    
    if [[ ! -n $xcat_lun ]]; then
      echo "xcatconf4z $funcName (Error) LUN was not specified"
      return
    fi
    xcat_lun=`echo $xcat_lun | tr '[A-Z]' '[a-z]'`
    
    # Online the device
    rc= onlineDevice $xcat_fcpAddr
    if (( rc != 0 )); then
      return
    fi
    
    # Set WWPN and LUN in sysfs
    if [[ -e /sys/bus/ccw/drivers/zfcp/0.0.$xcat_fcpAddr/port_add ]]; then
      echo 0x$xcat_wwpn > /sys/bus/ccw/drivers/zfcp/0.0.$xcat_fcpAddr/port_add
    fi
    echo 0x$xcat_lun > /sys/bus/ccw/drivers/zfcp/0.0.$xcat_fcpAddr/0x$xcat_wwpn/unit_add
    
    # Set WWPN and LUN in configuration files
    #   RHEL: /etc/zfcp.conf
    #   SLES 10: /etc/sysconfig/hardware/hwcfg-zfcp-bus-ccw-*
    #   SLES 11: /etc/udev/rules.d/51-zfcp*
    if [[ $os == sles10* ]]; then
      /sbin/zfcp_host_configure 0.0.$xcat_fcpAddr 1
      /sbin/zfcp_disk_configure 0.0.$xcat_fcpAddr $xcat_wwpn $xcat_lun 1
      echo "0x$xcat_wwpn:0x$xcat_lun" >> /etc/sysconfig/hardware/hwcfg-zfcp-bus-ccw-0.0.$xcat_fcpAddr
    elif [[ $os == sles11* ]]; then
      /sbin/zfcp_host_configure 0.0.$xcat_fcpAddr 1
      /sbin/zfcp_disk_configure 0.0.$xcat_fcpAddr $xcat_wwpn $xcat_lun 1
      
      # Configure zFCP device to be persistent                
      touch /etc/udev/rules.d/51-zfcp-0.0.$xcat_fcpAddr.rules
      
      # Check if the file already contains the zFCP channel
      out=`cat "/etc/udev/rules.d/51-zfcp-0.0.$xcat_fcpAddr.rules" | egrep -i "ccw/0.0.$xcat_fcpAddr]online"`
      if [[ ! $out ]]; then
        echo "ACTION==\"add\", SUBSYSTEM==\"ccw\", KERNEL==\"0.0.$xcat_fcpAddr\", IMPORT{program}=\"collect 0.0.$xcat_fcpAddr %k 0.0.$xcat_fcpAddr zfcp\"" \
          | tee -a /etc/udev/rules.d/51-zfcp-0.0.$xcat_fcpAddr.rules
        echo "ACTION==\"add\", SUBSYSTEM==\"drivers\", KERNEL==\"zfcp\", IMPORT{program}=\"collect 0.0.$xcat_fcpAddr %k 0.0.$xcat_fcpAddr zfcp\"" \
          | tee -a /etc/udev/rules.d/51-zfcp-0.0.$xcat_fcpAddr.rules
        echo "ACTION==\"add\", ENV{COLLECT_0.0.$xcat_fcpAddr}==\"0\", ATTR{[ccw/0.0.$xcat_fcpAddr]online}=\"1\"" \
          | tee -a /etc/udev/rules.d/51-zfcp-0.0.$xcat_fcpAddr.rules
      fi
      
      echo "ACTION==\"add\", KERNEL==\"rport-*\", ATTR{port_name}==\"0x$xcat_wwpn\", SUBSYSTEMS==\"ccw\", KERNELS==\"0.0.$xcat_fcpAddr\", ATTR{[ccw/0.0.$xcat_fcpAddr]0x$xcat_wwpn/unit_add}=\"0x$xcat_lun\"" \
        | tee -a /etc/udev/rules.d/51-zfcp-0.0.$device.rules
    elif [[ $os == rhel* ]]; then
      echo "0.0.$xcat_fcpAddr 0x$xcat_wwpn 0x$xcat_lun" >> /etc/zfcp.conf
      
      if [[ $os == rhel6* ]]; then
        echo "add" > /sys/bus/ccw/devices/0.0.$xcat_fcpAddr/uevent
      fi
    fi
    
    # Settle the file system so when we are done the device is fully available
    if [[ $(which udevadm 2> /dev/null) != '' ]]; then
        udevadm settle
    else
        udevsettle
    fi
    if [[ ! -e "/dev/disk/by-path/ccw-0.0.${xcat_fcpAddr}-zfcp-0x${xcat_wwpn}:0x${xcat_lun}" ]]; then
      # Sometimes the file takes longer to appear.  We will wait up to 3 minutes.
      maxTime=0
      for time in 1 2 2 5 10 10 30 60 60
      do
        if [[ -e "/dev/disk/by-path/ccw-0.0.${xcat_fcpAddr}-zfcp-0x${xcat_wwpn}:0x${xcat_lun}" ]]; then
          # Leave the loop now that the file exists
          break
        fi
        maxTime=$maxTime+$time
        echo "Sleeping for $time seconds to allow /dev/disk/by-path/ccw-0.0.${xcat_fcpAddr}-zfcp-0x${xcat_wwpn}:0x${xcat_lun} to be created"
        sleep $time
      done
    fi
    if [[ ! -e "/dev/disk/by-path/ccw-0.0.${xcat_fcpAddr}-zfcp-0x${xcat_wwpn}:0x${xcat_lun}" ]]; then
      echo "/dev/disk/by-path/ccw-0.0.${xcat_fcpAddr}-zfcp-0x${xcat_wwpn}:0x${xcat_lun} did not appear in $maxTime seconds, continuing."
    fi
    
  ##########################################################################
  # Handle removing a SCSI volume
  # Disk file input parameters:
  #   action - "removeScsiVolume"
  #   fcpAddr - FCP device address
  #   wwpn - WWPN number
  #   lun - LUN number
  ##########################################################################
  elif [[ $xcat_action == "removeScsiVolume" ]]; then
    echo "Removing a SCSI Volume, FCP addr: $xcat_fcpAddr, WWPN: $xcat_wwpn, LUN: $xcat_lun"
    
    # Validate the input
    if [[ ! -n $xcat_fcpAddr ]]; then
      echo "xcatconf4z $funcName (Error) FCP address was not specified"
      return
    fi
    xcat_fcpAddr=`echo $xcat_fcpAddr | tr '[A-Z]' '[a-z]'`
    
    if [[ ! -n $xcat_wwpn ]]; then
      echo "xcatconf4z $funcName (Error) WWPN was not specified"
      return
    fi
    xcat_wwpn=`echo $xcat_wwpn | tr '[A-Z]' '[a-z]'`
    
    if [[ ! -n $xcat_lun ]]; then
      echo "xcatconf4z $funcName (Error) LUN was not specified"
      return
    fi
    xcat_lun=`echo $xcat_lun | tr '[A-Z]' '[a-z]'`
    
    # Delete the SCSI device
    scsiDevice=`lszfcp -l 0x$xcat_lun | grep 0x$xcat_lun | cut -d " " -f2`
    if [[ -n $scsiDevice ]]; then
      echo 1 > "/sys/bus/scsi/devices/$scsiDevice/delete"
    fi
    
    # Delete WWPN and LUN from sysfs
    if [[ -e /sys/bus/ccw/drivers/zfcp/0.0.$xcat_fcpAddr/0x$xcat_wwpn/unit_remove ]]; then
      if [[ $(which udevadm 2> /dev/null) != '' ]]; then
        udevadm settle
      else
        udevsettle
      fi
      echo 0x$xcat_lun > /sys/bus/ccw/drivers/zfcp/0.0.$xcat_fcpAddr/0x$xcat_wwpn/unit_remove
    fi
    
    # Delete WWPN and LUN from configuration files
    #   RHEL: /etc/zfcp.conf
    #   SLES 10: /etc/sysconfig/hardware/hwcfg-zfcp-bus-ccw-*
    #   SLES 11: /etc/udev/rules.d/51-zfcp*
    if [[ $os == sles10* ]]; then
      expression="/$xcat_lun/d"
      sed --in-place -e $expression /etc/sysconfig/hardware/hwcfg-zfcp-bus-ccw-0.0.$xcat_fcpAddr
    elif [[ $os == sles11* ]]; then
      expression="/$xcat_lun/d"
      sed --in-place -e $expression /etc/udev/rules.d/51-zfcp-0.0.$xcat_fcpAddr.rules
    elif [[ $os == rhel* ]]; then
      expression="/$xcat_lun/d"
      sed --in-place -e $expression /etc/zfcp.conf
    fi
  fi
  
  return
}

############################################################################
# Main Code Section
############################################################################
case "$1" in  
  start) 
    if [[ -z "$authorizedSenders" ]]; then
      echo "xcatconf4z is disabled.  There are no authorized senders of configuration files."
    else
      echo "xcatconf4z is starting"
      transportdir="/var/opt/xcat/transport"
      rm -Rf $transportdir
      /bin/mkdir -p $transportdir
      cd $transportdir
      
      # Get Linux version
      getOsVersion
      
      pullReader
      setupIso
    fi 
  ;;  
  stop|status|restart|reload|force-reload)  
    # Do nothing  
  ;;  
esac