#!/bin/ksh
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
#  
#  
# Licensed Materials - Property of IBM 
#  
# (C) COPYRIGHT International Business Machines Corp. 2003,2007 
# 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 
# @(#)33 1.30.1.2 src/avs/fs/mmfs/ts/admin/mmfileid.sh, mmfs, avs_rgpfs24, rgpfs24s009a 1/14/07 03:50:17
##############################################################################
#
# Usage:
#
#   mmfileid Device
#     {-d [NodeName]:{DiskName|DiskNum|BROKEN}:[PhysAddr1[-PhysAddr2]] | -F DescFile}
#     [-o OutputFile] [-f NumThreads] [-t Directory]
#
# where
#
#   Nodename:   specifies a node in the GPFS node set that has access to
#                 the specified disk
#   DiskName:   specifies the physical volume name
#               if no nodename, this is the GPFS diskname or disk number
#               (mmlsdisk $fsname -i)
#   PhysAddr1:  the disk physical sector number
#               if no nodename, this is the GPFS logical sector; if omitted, 0
#   PhysAddr2:  the last sector number; if omitted, same as PhysAddr1
#
#   If PhysAddr1 and PhysAddr2 are both 0, the entire disk is searched.
#
#   If BROKEN is used instead of a disk name, files that have disk addresses
#   marked as "broken" will be found.  Broken addresses are left when a disk
#   has been deleted, but the data could not be salvaged.
#
#   -F DescFile   specifies a file containing the physical volume names
#                 and the corresponding disk physical address.
#                 Each line of the file should have the following format:
#      [NodeName]:{DiskName|DiskNum|BROKEN}:[PhysAddr1[-PhysAddr2]]
#
#   -o outFile    format: <inode number> <file name>
#
#   -f num        number of threads that need to be created
#                   (used by tsfindinode utility)
#
#   -t tempDirName  Override by using /var/mmfs/tmp for temporary files
#                   and sorting.
#
##############################################################################

# Include global declarations and service routines.
. /usr/lpp/mmfs/bin/mmglobfuncs
. /usr/lpp/mmfs/bin/mmsdrfsdef
. /usr/lpp/mmfs/bin/mmfsfuncs

sourceFile="mmfileid.sh"
[[ -n $DEBUG || -n $DEBUGmmfileid ]] && set -x
$mmTRACE_ENTER "$*"

# Local work files.  Names should be of the form:
#   fn=${tmpDir}fn.${mmcmd}.$$
inputPVDesc=${tmpDir}inputPVDesc.${mmcmd}.$$  # file of pvs from the input list
newLVDesc=${tmpDir}newLVDesc.${mmcmd}.$$      # LV list passed to tsfileid
outFidu=${tmpDir}outFidu.${mmcmd}.$$          # tsfileid output goes here
localLspvList=${tmpDir}lspvList.${mmcmd}.$$   # local lspv output list
inInodeList=${tmpDir}inInodeList.${mmcmd}.$$  # input to the tsfindinode cmd

LOCAL_FILES=" $inputPVDesc $newLVDesc $outFidu $localLspvList $inInodeList"

# Local variables

usageMsg=560                   # mmfileid usage message
integer rc=0                   # return code default to success
integer numThreads=16          # default is 16 threads

newLVList=""
pvDescFile=""
inodeDesc=""
specialInode=""
fiduDevicename=NULL

dflag=""                       # indicator that -d was specified
diskPVList=""                  # arguments following the -d argument
Farg=""                        # DescFile following -F
Fflag=""                       # indicator that -F was specified
fflag=""                       # indicator that -f was specified
oflag=""                       # indicator that -o was specified
tflag=""                       # indicator that -t was specified

# Local routines


##############################################################################
#
# Preprocessor function
#
# mmfileidPreprocess scans each line in the DescFile and tokenizes the line.
#
# 1. Find PVID for hdisk.  If error, stop processing.
# 2. Get gpfsDiskName.  If error, stop processing.
# 3. Convert ranges.  If error, stop processing.
# 4. Echo new values to $newLVDescFile. (Format:  gpfsDiskName:lvnum1-lvnum2)
#
##############################################################################
function mmfileidPreprocess
{
  typeset sourceFile="mmfileid.sh"
  [[ -n $DEBUG || -n $DEBUGmmfileidPreprocess ]] && set -x
  $mmTRACE_ENTER "$*"

  typeset addr1 addr2 pvRange pvDesc lvRange hostResult
  typeset diskName=""
  typeset gpfsDiskName=""
  typeset nodeName=""
  typeset nodeset1=""
  typeset nodeset2=""
  typeset pvID=""
  typeset relNodeName=""
  integer lineNumber=0
  typeset ec=0

  # Loop through the physical volume descriptors file.
  exec 3<&-
  exec 3< $pvDescFile
  while read -u3 pvDesc
  do
    IFS=':'
    set -f ; set -- $pvDesc ; set +f
    nodeName=$1
    diskName=$2
    pvRange=$3
    IFS='-'
    set -f ; set -- $pvRange ; set +f
    addr1=$1
    addr2=$2
    IFS="$IFS_sv"

    lineNumber=lineNumber+1

    # Skip empty and comment lines.
    [[ $pvDesc = *([$BLANKchar$TABchar])   ]] && continue
    [[ $pvDesc = *([$BLANKchar$TABchar])#* ]] && continue

    # Make sure the input arguments are all defined.
    [[ -z $addr1 ]] && addr1=-1
    [[ -z $addr2 ]] && addr2=$addr1
    if [[ -z $diskName ]]
    then
      if [[ -n $Fflag ]]
      then
        printErrorMsg 567 $mmcmd $lineNumber $pvDescFile
        print -u2 "  $pvDesc"
        syntaxError "incorrectSyntax" 0 "[NodeName]:{DiskName|DiskNum|BROKEN}:[PhysAddr1[-PhysAddr2]]"
      else
        printErrorMsg 560 $mmcmd
        cleanupAndExit 1
      fi
    fi

    # Check for address range correctness.
    if [[ $addr1 -gt $addr2 ]]
    then
      if [[ -n $Fflag ]]
      then
        printErrorMsg 567 $mmcmd $lineNumber $pvDescFile
        print -u2 "  $pvDesc"
      fi
      syntaxError "incorrectRange" 0 $addr1 $addr2
    fi

    if [[ -z $nodeName ]]
    then
      gpfsDiskName=$diskName
      if [[ $addr1 = -1 && $addr2 = -1 ]]
      then
        lvRange="0-0"
      else
        lvRange="$addr1-$addr2"
      fi
    else
      # Find the reliable hostname for specified node.
      relNodeName=$(checkAndConvertNodeValue $nodeName $REL_HOSTNAME_Field)
      [[ $? -ne 0 ]] && cleanupAndExit

      # Get our nodeset id.
      nodeset1=$(getNodeInfo $NODESETID_Field $REL_HOSTNAME_Field $relNodeName $GLOBAL_ID $mmsdrfsFile)
      if [[ -z $nodeset1 ]]
      then
        printErrorMsg 282 $mmcmd $relNodeName
        cleanupAndExit 1
      fi

      # Get the GPFS disk name.
      if [[ $relNodeName = $ourNodeName ]]
      then
        gpfsDiskName=$(getGpfsDiskName $diskName 2>$errMsg)
      else
        gpfsDiskName=$($mmcommon on1 $relNodeName getGpfsDiskName $diskName 2>$errMsg)
      fi
      ec=$?
      if [[ $ec -ne 0 || -z $gpfsDiskName ]]
      then
        # We were unable to acquire the pvid.
        [[ -s $errMsg ]] && $cat $errMsg 1>&2
        printErrorMsg 585 $mmcmd $diskName $relNodeName
        $rm -f $errMsg
        cleanupAndExit $ec
      fi

      $rm -f $errMsg

      nodeset2=$(getDiskInfo $NODESETID_Field $gpfsDiskName $mmsdrfsFile)

      # Check whether the node and diskName belong to the same GPFS nodeset.
      if [[ $nodeset1 != $nodeset2 ]]
      then
        print -u2 "$mmcmd: Disk $diskName and node $relNodeName do not belong to the same nodeset."
        cleanupAndExit 1
      fi

      # Invoke tsaddrmap on the specified node to convert
      # the physical disk address to a logical disk address.
      if [[ $relNodeName = $ourNodeName ]]
      then
        lvRange=$($tsaddrmap $diskName -p1 $addr1 -p2 $addr2 2>$errMsg)
      else
        lvRange=$($mmcommon on1 $relNodeName adminCmd tsaddrmap $diskName -p1 $addr1 -p2 $addr2 2>$errMsg)
      fi
      ec=$?
      if [[ $ec -ne 0 || -z $lvRange ]]
      then
        # Range error.  Show any tsaddrmap errors and issue an error message.
        [[ -s $errMsg ]] && $cat $errMsg 1>&2
        printErrorMsg 587 $mmcmd $diskName $relNodeName
        $rm -f $errMsg
        cleanupAndExit $ec
      fi
      $rm -f $errMsg
    fi  # end of if [[ -z $nodeName ]]

    # Add the converted data that needs to be passed to the tsfileid command.
    print -- "${gpfsDiskName}:${lvRange}" >> $newLVDesc

  done  # end of while read -u3 pvDesc do

  return 0

}  # ----- end of function mmfileidPreprocess ---------------------


##############################################################################
#
# Function:  mmfileidPostprocess
#
# This function merges the tsfileid output with the tsfindinode output.
#
# Format of tsfileid output:     {inodeNumber address snapshotId}
# Format of tsfindinode output:  {inodeNumber fileName}
# Format after post processing:  {inodeNumber address snapshotId fileName}
#
# Note:  Ideally, it would have been better for tsfindinode to do this.
#
##############################################################################
function mmfileidPostprocess
{
  typeset sourceFile="mmfileid.sh"
  [[ -n $DEBUG || -n $DEBUGmmfileidPostprocess ]] && set -x
  $mmTRACE_ENTER "$*"

  integer indexCnt=0
  integer i=0
  typeset inode inodeName lvAddress tsInode snapId
  typeset inodeLine inodeArrayData
  set -f ; set -A inodeArray ; set +f

  # Reuse the localLspvList file.
  $tsfindinode -i $inInodeList $fiduDevicename -t $numThreads > $localLspvList
  rc=$?
  if [[ $rc -ne 0 ]]
  then
    $cat $inInodeList $localLspvList
    cleanupAndExit $rc
  fi

  sortdir="/tmp"
  [[ -n $tflag ]] && sortdir=$tmpdir
  $sort -T $sortdir -b -k 1,1n $localLspvList -o $localLspvList
  rc=$?
  [[ $rc = 0 ]] && $sort -T $sortdir -b -k 1,1n $inInodeList -o $inInodeList && rc=$?
  if [[ $rc -ne 0 ]]
  then
    $cat $inInodeList $localLspvList
    printErrorMsg 171 "$mmcmd" "sort" $rc
    cleanupAndExit $rc
  fi

  $join -1 1 -2 1 $inInodeList $localLspvList
  rc=$?
  if [[ $rc -ne 0 ]]
  then
    $cat $inInodeList $localLspvList
    printErrorMsg 171 "$mmcmd" "mmfileidPostprocess: join " $rc
    cleanupAndExit $rc
  fi

  return 0

}  # ----- end of function mmfileidPostprocess ---------------------



#######################
# Mainline processing
#######################


##############################################################################
# Process the input arguments.
##############################################################################
[[ $arg1 = '-?' || $arg1 = '-h' || $arg1 = '--help' || $arg1 = '--' ]] &&  \
  syntaxError "help" $usageMsg

[[ $argc -lt 2  ]] &&  \
  syntaxError "missingArgs" $usageMsg

device=$arg1     # Save stripe group device (always the first parameter).
shift 1

while getopts :d:F:f:o:t: OPT
do

  case $OPT in
    d) # Only one disk address needs to be searched.
       [[ -n $dflag ]] && syntaxError "multiple" $noUsageMsg "-$OPT"
       [[ -n $Fflag ]] &&  \
         syntaxError "invalidCombination"  $usageMsg "-d" "-F"
       diskPVList=$OPTARG
       dflag=yes
       ;;

    F) # DescFile file
       [[ -n $Fflag ]] && syntaxError "multiple" $noUsageMsg "-$OPT"
       [[ -n $dflag ]] &&  \
          syntaxError "invalidCombination"  $usageMsg "-d" "-F"
       Farg=$OPTARG
       Fflag=yes
       ;;

    o) # output filename
       [[ -n $oflag ]] && syntaxError "multiple" $noUsageMsg "-$OPT"
       outFname=$OPTARG
       oflag=yes
       ;;

    f) # number of threads to be spawned
       [[ -n $fflag ]] && syntaxError "multiple" $noUsageMsg "-$OPT"
       numThreads=$(checkIntRange "-f" $OPTARG 1)
       [[ $? -ne 0 ]] && cleanupAndExit
       fflag=yes
       ;;

    t) # temp working dir name
       [[ -n $tflag ]] && syntaxError "multiple" $noUsageMsg "-$OPT"
       tmpDir=${OPTARG}/
       tflag=yes
       # Reset local work files.
       inputPVDesc=${tmpDir}inputPVDesc.${mmcmd}.$$  # file of physical volumes
                                                     #   from the input list
       newLVDesc=${tmpDir}newLVDesc.${mmcmd}.$$    # LV list passed to tsfileid
       outFidu=${tmpDir}outFidu.${mmcmd}.$$        # tsfileid output goes here
       localLspvList=${tmpDir}lspvList.${mmcmd}.$$   # local lspv output list
       inInodeList=${tmpDir}inInodeList.${mmcmd}.$$  # input to the tsfindinode cmd
       LOCAL_FILES=" $inputPVDesc $newLVDesc $outFidu $localLspvList $inInodeList"

       ;;

    +[dFoft]) # invalid option specified
       syntaxError "invalidOption" $usageMsg $OPT
       ;;

    :) # missing required value after an option
       syntaxError "missingValue" $usageMsg $OPTARG
       ;;

    *) # invalid option specified
       syntaxError "invalidOption" $usageMsg $OPTARG
       ;;

  esac

done

shift OPTIND-1   # Drop the processed options from the parameter list.
[[ $# != 0 ]] && syntaxError "extraArg" $usageMsg $1


# Finish the parameter checking.

#####################################################################
# If -d was specified, create a file that contains the physical
# disk address information of the specified nodes.  If the -F option
# was used, just use its operand as the file for disk addresses.
#####################################################################
if [[ -n $dflag ]]
then
  # Add the physical volume descriptor to the descriptor file.
  print -- $diskPVList > $inputPVDesc
  checkForErrors "writing to file $inputPVDesc" $?
  pvDescFile=$inputPVDesc
fi

if [[ -n $Fflag ]]
then
  # The -f option was specified.  Check whether the file exists.
  if [[ ! -f $Farg || ! -r $Farg ]]
  then
    # Can't read the input disk address file.
    printErrorMsg 43 $mmcmd $Farg
    cleanupAndExit
  fi

  # Check that the physical volume descriptors file is not empty.
  if [[ ! -s $Farg ]]
  then
    # The descriptors file is empty.
    printErrorMsg 561 $mmcmd $Farg
    cleanupAndExit
  fi
  pvDescFile=$Farg
fi


######################################################################
# Set up trap exception handling and call the gpfsInit function.
# It will ensure that the local copy of the mmsdrfs and the rest of
# the GPFS system files are up-to-date and will obtain the sdr lock.
######################################################################
trap pretrap HUP INT QUIT KILL
gpfsInitOutput=$(gpfsInit $lockId)
setGlobalVar $? $gpfsInitOutput

[[ -z $ourNodeName ]] && getLocalNodeData    # sanity check


############################################################
# Make sure the specified file system exists and is local.
############################################################
findFSoutput=$(findFS "$device" $mmsdrfsFile)
[[ -z $findFSoutput ]] && cleanupAndExit

# Parse the output from the findFS function.
set -f ; set -- $findFSoutput ; set +f
fqDeviceName=$1
deviceName=$2
fsHomeCluster=$3

# Exit with a message if the command was invoked for a remote file system.
if [[ $fsHomeCluster != $HOME_CLUSTER ]]
then
  # Command is not allowed for remote file systems.
  printErrorMsg 106 $mmcmd $device $fsHomeCluster
  cleanupAndExit 1
fi

# Determine the lookup order for resolving host names.
[[ $osName != AIX ]] && resolveOrder=$(setHostResolveOrder)

# mmfileid requires that the device be mounted on the node on which
# mmfileid is executed.  If this is not true, tsfindinode will fail.
if [[ $osName = Linux ]]; then
  fiduDevicename=$($awk '{if ($1 == fs) print $2}' fs="/dev/$deviceName" $etcFilesystems)
else
  fiduDevicename=$($lsfs -c "/dev/$deviceName" 2>/dev/null | $tail -n +2 | $awk -F: '{print $1}')
fi

# Check whether the filesystem is locally mounted.
# This is required or the later invocation of tsfindinode will not work.
$tsfindinode -i 1 $fiduDevicename > /dev/null 2>&1
rc=$?
if [[ $rc -ne 0 ]]
then
  printErrorMsg 563 $mmcmd $deviceName $ourNodeName
  cleanupAndExit 1
fi

# Create a file containing the local output of the lspv command.
if [[ $osName = AIX ]]
then
  $(LC_ALL=C $lspv > $localLspvList)
  rc=$?
  if [[ $rc -ne 0 ]]
  then
    cleanupAndExit $rc
  fi
else   # Use the output of the mmlsnsd command in the non-AIX case.
  $mmlsnsd -f $deviceName -m > $localLspvList
  rc=$?
  if [[ $rc -ne 0 ]]
  then
    cleanupAndExit $rc
  fi
fi



# Pre-process the input.
mmfileidPreprocess


#################################################
# If the file system is in a different nodeset,
# or the local daemon is not available, find an
# active node and send the command there.
#################################################

# Create a file with the reliable names that form
# the cluster to which the file system belongs.
nodeCount=$(getNodeFile $REL_HOSTNAME_Field $fsHomeCluster $mmsdrfsFile $nodefile)
if [[ $nodeCount -eq 0 ]]
then
  # The cluster is empty; there is no node to run the command.
  printErrorMsg 171 $mmcmd "getNodeFile (nodeCount=0)" 1
  cleanupAndExit
fi

preferredNode=0
$mmcommon onactive $preferredNode $nodefile                  \
                   $newLVDesc $NO_MOUNT_CHECK NULL $NO_LINK  \
                   tsfileid $fqDeviceName -F $newLVDesc > $outFidu
rc=$?
if [[ $rc -ne 0 ]]
then
  cleanupAndExit $rc
fi


#######################################################################
# Check whether the output from tsfileid is empty.  If so, it means
# that tsfileid returned successfully (rc already checked above)
# but could not find any disk addresses that matched the input.
#######################################################################
if [[ ! -s $outFidu ]]
then
  # tsfileid did not find any addresses that matched the input.
  printErrorMsg 562 $mmcmd $outFidu
  cleanupAndExit $rc
fi

# If oflag is true, then create an empty output filename.
[[ -n $oflag ]] && $rm -f $outFname


#####################################################################
# Look for special inodes (line starts with "Address").
# tsfindinode cannot understand these special inode comments.
# Convert the inode numbers returned by tsfileid to file names.
#####################################################################
# If an output file name was specified, redirect stdout to the file.
[[ -n $oflag ]] && exec 1>> $outFname

exec 3<&-
exec 3< $outFidu
while read -u3 inodeDesc
do
  set -f ; set -- $inodeDesc ; set +f
  specialInode=$1

  # Don't put lines for special inodes (i.e., those that start with
  # "Address") into the file that will be passed to tsfindinode.
  if [[ $specialInode = "Address" ]]
  then
    print $inodeDesc
  else
    print $inodeDesc >> $inInodeList
  fi
done

# Call mmfileidPostprocess only if the inInodeList file is not empty.
if [[ ! -s $inInodeList ]]
then
  rc=$?
  cleanupAndExit $rc
fi

mmfileidPostprocess

cleanupAndExit 0

