#!/bin/bash
###############################################################################
# IBM (C) Copyright 2013 Eclipse Public License                               # 
# http://www.eclipse.org/org/documents/epl-v10.html                           #
###############################################################################
# COMPONENT: creatediskimage                                                  #
#                                                                             #
# Creates an image of 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 "xcatshellutils".
  # @Code:
  echo -n "Creates an image of the disk at the specified channel ID on the "
  echo    "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/creatediskimage_trace_${timestamp}.txt
  mkdir -p /var/log/zhcp
  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 capture can reasonably
  #   be expected.
  # @Code:
  # Make sure the specified image file name is not already taken.
  if [[ -e $imageFile ]]; then
    printError 'The specified image file already exists.'
    exit 3
  fi
  if [[ $userID && $(isSystemActive $userID) ]]; then
    printError 'The specified source system is currently running.'
    exit 3
  fi
  # Intentionally non-local variable.
  passedSanity='true'
} #checkSanity{}

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

function createDiskImage {
  : SOURCE: ${BASH_SOURCE}
  : STACK:  ${FUNCNAME[@]}
  # @Description:
  #   Creates an image of the specified disk.
  # @Code:
  if [[ $userID ]]; then
    inform "Creating image of ${userID}'s disk at channel ${channelID}."
  else
    inform "Creating image of ${wwpn}/${lun} disk at channel ${fcpChannel}."
  fi
  function captureFCP {
    : SOURCE: ${BASH_SOURCE}
    : STACK:  ${FUNCNAME[@]}
    # @Description:
    #   Capture the specified SCSI/FCP disk with `dd`.
    # @Code:
    # First 20 bytes of image file are tag identifying it as an image of a CKD
    # disk produced by xCAT:
    echo -n 'xCAT FCP Disk Image:' > $imageFile
    # Next 16 bytes of image file are an ASCII representation of the disk's
    # size and the unit (BLK or CYL) used for this measurement:
    /usr/bin/sg_readcap /dev/disk/by-path/ccw-0.0.${fcpChannel}-zfcp-${wwpn}:${lun} | 
      egrep -i "Device size:" | 
        awk '{printf("%0.0f BLK", $3/512)}' |
          sed 's/^/                /;s/.*\(.\{16\}\)$/\1/' >> $imageFile
    # Now read and compress the data on this fixed-block disk and write the
    # result to the end of our image file.
    gzip < /dev/disk/by-path/ccw-0.0.${fcpChannel}-zfcp-${wwpn}:${lun} >> $imageFile
    if (( $? )); then
      printError "An error was encountered while creating disk image."
      exit 3
    fi
  } #captureFCP{}
  function captureFBA {
    : SOURCE: ${BASH_SOURCE}
    : STACK:  ${FUNCNAME[@]}
    # @Description:
    #   Capture the specified FBA disk with `dd`.
    # @Code:
    # First 20 bytes of image file are tag identifying it as an image of a CKD
    # disk produced by xCAT:
    echo -n 'xCAT FBA Disk Image:' > $imageFile
    # Next 16 bytes of image file are an ASCII representation of the disk's
    # size and the unit (BLK or CYL) used for this measurement:
    vmcp q v $alias |
      awk '{printf $6" "$7}' |
        sed 's/^/                /;s/.*\(.\{16\}\)$/\1/' >> $imageFile
    # Now read and compress the data on this fixed-block disk and write the
    # result to the end of our image file.
    gzip < /dev/disk/by-path/ccw-0.0.${alias} >> $imageFile
    if (( $? )); then
      printError "An error was encountered while creating disk image."
      exit 3
    fi
  } #captureFBA{}
  function captureCKD {
    : SOURCE: ${BASH_SOURCE}
    : STACK:  ${FUNCNAME[@]}
    # @Description:
    #   Capture the specified CKD disk with `ckdencode`.
    # @Code:
    # First 20 bytes of image file are tag identifying it as an image of a CKD
    # disk produced by xCAT:
    echo -n 'xCAT CKD Disk Image:' > $imageFile
    # Next 16 bytes of image file are an ASCII representation of the disk's
    # size and the unit (BLK or CYL) used for this measurement:
    vmcp q v $alias |
      awk '{printf $6" "$7}' |
        sed 's/^/                /;s/.*\(.\{16\}\)$/\1/' >> $imageFile
    # Now read, encode, and compress the data on this count-key-data disk and
    # write the result to the end of our image file.
    ckdencode /dev/disk/by-path/ccw-0.0.${alias} | gzip >> $imageFile
    if (( $? )); then
      printError "An error was encountered while creating disk image."
      exit 3
    fi
  } #captureCKD{}

  if [[ ${userID} ]]; then
    connectDisk $userID $channelID 'rr'
  else
    connectFcp ${fcpChannel} ${wwpn} ${lun}
    if (( $? )); then
      printError "An error was encountered while attaching disk."
      exit 3
    fi
  fi

  local alias=$(getDiskAlias $userID $channelID)
  if [[ ${fcpChannel} && $(vmcp q v ${fcpChannel} | grep 'ON FCP') ]]; then
    # Device is an FCP disk. Capture with `dd`. 
    captureFCP
  elif [[ $(vmcp q v $alias | grep 'BLK ON DASD') ]]; then
    # Device is an FBA disk. Capture with `dd`. 
    captureFBA
  elif [[ $(vmcp q v $alias | grep 'CYL ON DASD') ]]; then
    if [[ -e /sys/bus/ccw/devices/0.0.${alias}/raw_track_access ]]; then
      # Device is a CKD disk with raw_track_access as an available option.
      # Reconnect in raw_track_access mode and capture with ckdencode.
      disconnectDisk $userID $channelID
      connectDisk $userID $channelID 'rr' 1
      captureCKD
    else
      printError "Attempting to capture CKD disk failed; the raw_track_access
                  DASD driver option is unavailable on this system." 
      exit 3
    fi
  else
    printError "Specified disk does not exist or is of an unknown type." 
    exit 3
  fi
} #createDiskImage{}


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

function cleanup {
  : SOURCE: ${BASH_SOURCE}
  : STACK:  ${FUNCNAME[@]}
  # @Description:
  #   Clean up lock files, disk links, and (if we're exiting with an error)
  #   the unfinished package.
  # @Code:
  if [[ $successful ]]; then
    inform "Image creation successful."
    # Only keep traces of failed disk-image creation attempts.
    [[ $logFile ]] && rm -f $logFile
  else
    if [[ ! $printHelp ]]; then
      echo -e '\nIMAGE CREATION FAILED.'
      [[ $logFile ]] && echo "A detailed trace can be found at: ${logFile}"
    fi
    # If our image-creation wasn't successful, we should't leave a broken
    # image sitting around in the staging repository.
    [[ $passedSanity ]] && rm -f $imageFile
  fi

  # Also, let's make sure we've released our connection to the source disk.
  if [[ $userID ]]; then
    disconnectDisk $userID $channelID 0
  else
    disconnectFcp ${fcpChannel} ${wwpn} ${lun}
  fi
} #cleanupProperties{}

trap 'cleanup' EXIT

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

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

parseArgs
checkSanity
createDiskImage
successful='true'

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