#!/bin/bash
###############################################################################
# IBM (C) Copyright 2013 Eclipse Public License                               # 
# http://www.eclipse.org/org/documents/epl-v10.html                           #
###############################################################################
# COMPONENT: unpackdiskimage                                                  #
#                                                                             #
# Deploys a disk iamge to the disk at the specified channel ID on the         #
# specified z/VM guest system.                                                #
###############################################################################

source /opt/zhcp/lib/zhcpshellutils

###############################################################################
### FUNCTIONS #################################################################
###############################################################################

function printCMDUsage {
  : SOURCE: ${BASH_SOURCE}
  : STACK:  ${FUNCNAME[@]}
  # @Description:
  #   Prints usage help text using a list generated by previous requests made
  #   to parse the command-line argument list.
  # @Override in zhcpshellutils
  # @Code:
  echo "USAGE: $CMDNAME [OPTIONS] USER_ID CHANNEL_ID IMAGE_FILE"
  echo "       $CMDNAME [OPTIONS] CHANNEL_ID WWPN LUN IMAGE_FILE"

  echo "${optionHelp}"
} #printCMDUsage{}

###############################################################################

function printCMDDescription {
  : SOURCE: ${BASH_SOURCE}
  : STACK:  ${FUNCNAME[@]}
  # @Description:
  #   Prints a short description of this command.
  # @Overrides:
  #   printCMDDescription{} in "ovffunctions".
  # @Code:
  echo -n "Deploys a disk image to the disk at the specified channel ID on "
  echo    "the specified z/VM guest system."
} #printCMDDescription{}

###############################################################################

function parseArgs {
  : SOURCE: ${BASH_SOURCE}
  : STACK:  ${FUNCNAME[@]}
  # @Description:
  #   Parses and checks command-line arguments.
  # @Code:
  # Non-local variables in this function are intentionally non-local.
  isOption -h --help "     Print this help message."   && printHelp='true'
  isOption -v --verbose "  Print verbose output."      && verbose='-v'
  isOption -x --debug "    Print debugging output."    && debug='-x'

  if [[ ${#args[@]} == 4 ]]; then
    getPositionalArg 1 fcpChannel 
    getPositionalArg 2 wwpn
    getPositionalArg 3 lun
    getPositionalArg 4 imageFile

    # Make channel, WWPN, and LUN lower case
    fcpChannel=$(echo ${fcpChannel} | tr '[:upper:]' '[:lower:]')
    wwpn=$(echo ${wwpn} | tr '[:upper:]' '[:lower:]')
    lun=$(echo ${lun} | tr '[:upper:]' '[:lower:]')
  else
    getPositionalArg 1 userID
    getPositionalArg 2 channelID
    getPositionalArg 3 imageFile
  fi

  if [[ $printHelp ]]; then
    printHelp
    exit 0
  fi
  local badOptions=$(getBadOptions)
  if [[ $badOptions ]]; then
    echo "ERROR: ${badOptions}"
    printCMDUsage
    exit 1
  fi

  if [[ ${#args[@]} == 4 && (! $fcpChannel || ! $wwpn || ! lun || ! $imageFile) ]]; then
    echo 'ERROR: Missing required parameter.'
    printCMDUsage
    exit 1
  elif [[ ${#args[@]} == 3 && (! $userID || ! $channelID || ! $imageFile) ]]; then
    echo 'ERROR: Missing required parameter.'
    printCMDUsage
    exit 1
  fi

  timestamp=$(date -u --rfc-3339=ns | sed 's/ /-/;s/\.\(...\).*/.\1/')
  logfile=/var/log/zhcp/unpackdiskimage_trace_${timestamp}.txt
  if [[ $debug ]]; then
    exec 2> >(tee -a $logfile)
    set -x
  else
    exec 2> $logfile
    set -x
  fi

  if [[ ${#args[@]} == 4 && (${#wwpn} < 18 || ${#lun} < 18) ]]; then
    echo 'ERROR: WWPN and LUN must be a 16 byte number.'
    exit 1
  fi

  if [[ ${#args[@]} == 4 ]]; then
    echo "FCP CHANNEL:    \"$fcpChannel\""
    echo "WWPN:           \"$wwpn\""
    echo "LUN:            \"$lun\""
    echo "IMAGE FILE:     \"$imageFile\""
    echo ""
  else
    echo "SOURCE USER ID: \"$userID\""
    echo "DISK CHANNEL:   \"$channelID\""
    echo "IMAGE FILE:     \"$imageFile\""
    echo ""
  fi
} #parseArgs{}

###############################################################################

function checkSanity {
  : SOURCE: ${BASH_SOURCE}
  : STACK:  ${FUNCNAME[@]}
  # @Description:
  #   Performs basic checks to ensure that a successful deploy can reasonably
  #   be expected.
  # @Code:
  # Make sure the specified image file exists.
  if [[ ! -f $imageFile ]]; then
    printError 'The specified image file was not found.'
    exit 3
  fi
  local diskTag="$(dd if=$imageFile bs=1 count=20 2>/dev/null)"
  if [[ $diskTag = 'xCAT FCP Disk Image:' ]]; then
    # Intentionally non-local variable.
    format='FCP'
  elif [[ $diskTag = 'xCAT FBA Disk Image:' ]]; then
    # Intentionally non-local variable.
    format='FBA'
  elif [[ $diskTag = 'xCAT CKD Disk Image:' ]]; then
    # Intentionally non-local variable.
    format='CKD'
  else
    printError "Disk image type not recognized"
    exit 3
  fi
  if [[ $userID && $(isSystemActive $userID) ]]; then
    printError 'The specified target system is currently running.'
    exit 3
  fi
  # Intentionally non-local variable.
  passedSanityChecks='true'
} #checkSanity{}

###############################################################################
### DEPLOY FUNCTIONS ##########################################################
###############################################################################

function deployDiskImage {
  : SOURCE: ${BASH_SOURCE}
  : STACK:  ${FUNCNAME[@]}
  # @Description:
  #   Deploy the disk image.
  # @Code:
  function deployCKDImage {
    : SOURCE: ${BASH_SOURCE}
    : STACK:  ${FUNCNAME[@]}
    # @Description:
    #   Deploy the specified count-key-data disk image.
    # @Code:
    connectDisk $userID $channelID 'w' 1
    if (( $? )); then
      printError "Failed to connect disk: ${userID}:${channelID}"
      exit 3
    fi
    local alias=$(getDiskAlias $userID $channelID)
    if [[ $(vmcp q v $alias | grep 'CYL ON DASD') ]]; then
      local diskSize="$(dd if=$imageFile bs=1 count=16 skip=20 2>/dev/null |
                        awk '{print $1}')"
      if [[ ${diskSize} -le $(vmcp q v $alias | awk '{print $6}') ]]; then
        dd if=$imageFile bs=36 skip=1 |
          zcat |
            ckddecode /dev/disk/by-path/ccw-0.0.${alias}
        if (( $? )); then
          printError "Failed deploying disk image: $imageFile"
          exit 3
        fi
      else
        printError "Target disk is too small for specified image."
        exit 3
      fi
    else
      printError "Specified image is of a Count-Key-Data volume, but specified
                  disk is not a Count-Key-Data volume."
      exit 3
    fi
  } #deployCKDImage{}
  function deployFBAImage {
    : SOURCE: ${BASH_SOURCE}
    : STACK:  ${FUNCNAME[@]}
    # @Description:
    #   Deploy the specified fixed-block disk image.
    # @Code:
    connectDisk $userID $channelID 'w'
    if (( $? )); then
      printError "Failed to connect disk: ${userID}:${channelID}"
      exit 3
    fi
    local alias=$(getDiskAlias $userID $channelID)   
    if [[ $(vmcp q v $alias | grep 'BLK ON DASD') ]]; then
      local diskSize="$(dd if=$imageFile bs=1 count=16 skip=20 2>/dev/null |
                        awk '{print $1}')"
      if [[ ${diskSize} -le $(vmcp q v $alias | awk '{print $6}') ]]; then
        dd if=$imageFile bs=36 skip=1 |
          zcat > /dev/disk/by-path/ccw-0.0.${alias}
        if (( $? )); then
          printError "Failed deploying disk image: $imageFile"
          exit 3
        fi
      else
        printError "Target disk is too small for specified image."
        exit 3
      fi
    else
      printError "Specified image is of a fixed-block volume, but specified
                  disk is not a fixed-block volume."
      exit 3
    fi
  } #deployFBAImage{}
  function deployFCPImage {
    : SOURCE: ${BASH_SOURCE}
    : STACK:  ${FUNCNAME[@]}
    # @Description:
    #   Deploy the specified fixed-block disk image.
    # @Code:
    connectFcp ${fcpChannel} ${wwpn} ${lun}
    if (( $? )); then
      printError "Failed to connect disk: ccw-0.0.${fcpChannel}-${wwpn}:${lun}."
      exit 3
    fi

    local targetDiskSize="$(/usr/bin/sg_readcap /dev/disk/by-path/ccw-0.0.${fcpChannel}-zfcp-${wwpn}:${lun} | 
    egrep -i "Device size:" | 
      awk '{printf("%0.0f", $3/512)}')"

    local diskSize="$(dd if=$imageFile bs=1 count=16 skip=20 2>/dev/null |
                      awk '{print $1}')"
    if [[ $diskSize -le $targetDiskSize ]]; then
      dd if=$imageFile bs=36 skip=1 |
        zcat > /dev/disk/by-path/ccw-0.0.${fcpChannel}-zfcp-${wwpn}:${lun}
      if (( $? )); then
        printError "Failed deploying disk image: $imageFile"
        exit 3
      fi
    else
      echo "$targetDiskSize-$diskSize"
      printError "Target disk is too small for specified image."
      exit 3
    fi
  } #deployFCPImage{}

  if [[ $userID ]]; then
    inform "Deploying image to ${userID}'s disk at channel ${channelID}."
  else
    inform "Deploying image to ${wwpn}/${lun} disk at channel ${fcpChannel}."
  fi
  
  if [[ $format = 'FCP' ]]; then
    deployFCPImage
  elif [[ $format = 'FBA' ]]; then
    deployFBAImage
  elif [[ $format = 'CKD' ]]; then
    deployCKDImage
  else
    # It should not be possible to get here after our sanity checks, but in
    # any case...
    printError "Disk image type not recognized."
    exit 3
  fi
} #deployDiskImage{}


###############################################################################
### SET TRAP FOR CLEANUP ON EXIT ##############################################
###############################################################################

function cleanup {
  : SOURCE: ${BASH_SOURCE}
  : STACK:  ${FUNCNAME[@]}
  # @Description:
  #   Clean up lock files, disk links, and (if we passed the sanity check but
  #   failed after creating one or more z/VM user IDs, those newly-created
  #   user IDs).
  # @Code:
  if [[ $successful ]]; then
    inform "Image deployment successful."
    # Only keep traces of failed disk-image deployment attempts.
    [[ $logFile ]] && rm -f $logFile
  else
    if [[ ! $printHelp ]]; then
      echo -e '\nIMAGE DEPLOYMENT FAILED.'
      [[ $logFile ]] && echo "A detailed trace can be found at: ${logFile}"
    fi
  fi

  # Make sure we've released our connection to the target disk.
  if [[ $userID ]]; then
    disconnectDisk $userID $channelID 0
  else
    disconnectFcp ${fcpChannel} ${wwpn} ${lun}
  fi
} #cleanup{}

trap 'cleanup' EXIT

trap "echo -e '\nExecution interrupted. Exiting...\n'; exit" SIGINT

###############################################################################
### START EXECUTION ###########################################################
###############################################################################

parseArgs
checkSanity
deployDiskImage
successful='true'

###############################################################################
### END OF SCRIPT #############################################################
###############################################################################

