#!/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 
# @(#)36 1.7.5.2 src/avs/fs/mmfs/ts/admin/mmexportfs.sh, mmfs, avs_rgpfs24, rgpfs24s010a 2/22/07 03:17:55
##############################################################################
#
# Usage:
#
#   mmexportfs {Device | all} -o ExportfsFile [-C nodesetId] [-P]
#
# where
#
#   Device        is the file system to be exported.
#                 If "all" is specified, then all file systems in
#                 the cluster are exported.  Free disks, if any,
#                 are exported as well.
#
#   -o ExportfsFile   is the name of a file where the file system
#                     export information is to be placed.
#
#
#  Obsolete option (for use when migrating previous releases):
#
#   -C NodesetId  is the nodeset from which file systems are to be
#                 exported.  This parameter is used to limit the
#                 scope of the "all" keyword to all file systems
#                 within a given nodeset rather than all file
#                 systems in the cluster.
#
#  Undocumented option:
#
#   -P            Do not remove the file system information from
#                 the current version of the mmsdrfs.
#
##############################################################################

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

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


# Local work files.  Names should be of the form:
#   fn=${tmpDir}fn.${mmcmd}.$$

# Note that the following nodesetMember declaration is the prefix for
# the file names that will contain the member nodes of each nodeset.
# The actual file names will be of the form $nodesetMembers.<nodesetId>
nodesetMembers=${tmpDir}nodesetMembers.${mmcmd}.$$

LOCAL_FILES=" $nodesetMembers.* "

# Local variable declarations
usageMsg=570
oflag=""
Pflag=""
fsBeingExportedList=""

newNodesetIdFound=no
oldNodesetId=""
fsMountPoint=""
existingMountPoints=""
existingMinorNumbers=""


# Local functions


####################################################################
#
# Function:  Based on the cluster type, guess at what the disk type
#            for a given disk may be.
#
# Input:     None.
#
# Output:    The type of the disk:  vsd, lv, nsd, etc.
#
# Returns:   Always zero.
#
####################################################################
function assignDiskType  #
{
  [[ -n $DEBUG || -n $DEBUGassignDiskType ]] && set -x
  $mmTRACE_ENTER "$*"
  typeset diskType wait4RVSD

  # Determine the value of the wait4RVSD parameter.
  [[ $MMMODE = rpd ]] && wait4RVSD=$(showCfgValue wait4RVSD)

  case $MMMODE in
    sp)     diskType=vsd ;;
    hacmp)  diskType=lv  ;;
    rpd)    [[ $wait4RVSD = yes ]] && diskType=vsd || diskType=lv ;;
    lc)     diskType=nsd ;;
    single) diskType=disk ;;
    *)      diskType=unknown ;;
  esac

  print -- $diskType
  return 0

}  #--------- end of function assignDiskType --------------------



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


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

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

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

while getopts :C:o:P OPT
do
  case $OPT in
    C) # Export the file systems from the specified nodeset.
       [[ -n $Cflag ]] && syntaxError "multiple" $noUsageMsg "-$OPT"
       sourceNodesetId=$OPTARG
       Cflag=yes
       ;;

    o) # Name of the output file.
       [[ -n $oflag ]] && syntaxError "multiple" $noUsageMsg "-$OPT"
       fsExportData=$OPTARG
       oflag=yes
       ;;

    P) # Leave the fs information in place.
       [[ -n $Pflag ]] && syntaxError "multiple" $noUsageMsg "-$OPT"
       Pflag=yes
       ;;

    :) syntaxError "missingValue" $usageMsg $OPTARG
       ;;

    +[CoP]) syntaxError "invalidOption" $usageMsg $OPT
       ;;

    *) # Invalid option specified.
       syntaxError "invalidOption" $usageMsg $OPTARG
       ;;
  esac
done

shift OPTIND-1
[[ $# != 0 ]] && syntaxError "extraArg" $usageMsg $1

# Verify an output file is specified.
[[ -z $oflag ]] && syntaxError "missingArgs" $usageMsg

# Be careful not to inadvertently destroy some other file.
if [[ -e $fsExportData ]]
then
  # The file already exists.
  printErrorMsg 42 $mmcmd $fsExportData
  cleanupAndExit
fi

# Temporarily add the user specified output file to the list of files
# to be removed automatically upon exit.  We do this to assure that
# there wil be no partial data in case of a failure.  Once the data is
# complete, we will restore the original LOCAL_FILES value.
LOCAL_FILES_SV="$LOCAL_FILES"
LOCAL_FILES="$LOCAL_FILES $fsExportData"


#######################################################################
# 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.
#
# Note:   We are using a variation of gpfsInit - gpfsInitGeneric,
# which allows the command to still run on old GPFS cluster types.
# If the cluster type is lc or single, things work as they always do.
# But if the cluster type is sp, rpd, or hacmp, we are dealing with
# an obsolete GPFS cluster environment.  The daemon will never
# be allowed to start under these circumstances, nor will be the bulk
# of the mm commands allowed to work.  The only exception are commands
# (mmexportfs, mmdelnode) needed by the user to migrate to a supported
# environment.  Under such conditions it is acceptable to assume that
# the daemon is indeed not runing anywhere (i.e., there is no need to
# run verifyDaemonInactive) and to ignore the true commit processing
# and the rebuilding of the mmfs environment.  The idea is to allow
# the user to run "mmexportfs all", followed by "mmdelnode -a", and
# then create a new cluster of type lc.
#
# Note:   If the -P option is specified, we are interested only
# in retrieving the file system data without changing the current
# mmsdrfs file.  There is no need to lock the mmsdrfs file because
# it will not be changed.
#######################################################################
trap pretrap HUP INT QUIT KILL
[[ -n $Pflag ]] && lockId=nolock
gpfsInitOutput=$(gpfsInitGeneric $lockId)
setGlobalVar $? $gpfsInitOutput

# If the current nodeset is requested,
# use the nodeset id returned from gpfsInit.
[[ $sourceNodesetId = "." ]] && sourceNodesetId=$nsId

if [[ $sourceNodesetId = 0 ]]
then
  print -u2 "$mmcmd: The nodeset can not be determined.  Use the -C <nodesetId> option,"
  print -u2 "or issue the command from a node that belongs to the desired nodeset."
  cleanupAndExit
fi

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


###################################################
# Make sure that the specified file system exists.
###################################################
if [[ $device = all ]]
then
  # The scope of the keyword all depends on whether
  # -C was specified or not.
  if [[ -z $sourceNodesetId ]]
  then
    nodesetId=$GLOBAL_ID
  else
    nodesetId=$sourceNodesetId
  fi
else
  # Obtain the information about the filesystem from the mmsdrfs file.
  findFSoutput=$(findFS "$device" $mmsdrfsFile)
  [[ -z $findFSoutput ]] && cleanupAndExit

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

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

  if [[ -n $sourceNodesetId && $nodesetId != $sourceNodesetId ]]
  then
    print -u2 "$mmcmd: File system $device does not exist in nodeset $sourceNodesetId".
    cleanupAndExit
  fi
fi  # end if [[ $device = all ]]


######################################################################
# Create the new version of the mmsdrfs file and collect the relevant
# information for the file systems being exported.
######################################################################
$rm -f $newsdrfs $fsExportData $allClusterNodes $nodesetMembers.*
IFS=":"
exec 3<&-
exec 3< $mmsdrfsFile
while read -u3 sdrfsLine
do
  # Parse the line.
  set -f ; set -A v -- - $sdrfsLine ; set +f

  IFS="$IFS_sv"    # Restore the default IFS settings.
  printLine=true   # Assume the line will be printed.

  # Change some of the fields depending on the type of line.
  case ${v[$LINE_TYPE_Field]} in

    $VERSION_LINE )  # this is the global header line
       # If this is an sp cluster and it does not already
       # have a cluster Id, assign it one now.
       if [[ -z ${v[CLUSTERID_Field]} ]]
       then
         timeStamp=$($perl -e 'print time')
         v[$CLUSTERID_Field]="gpfs$timeStamp"
       fi

       # Put the line into the fs export file.
       print_newLine >> $fsExportData
       checkForErrors "writing to file $fsExportData" $?

       # Increment the generation number
       newGenNumber=${v[$SDRFS_GENNUM_Field]}+1
       v[$SDRFS_GENNUM_Field]=$newGenNumber
       ;;

    $NODESET_HDR )  # this is the header line for a nodeset
       # If this is the nodeset from which we are exporting,
       # put the line into the fs export file.
       if [[ ${v[$NODESETID_Field]} = $nodesetId ||
             $nodesetId = $GLOBAL_ID ]]
       then
         print_newLine >> $fsExportData
         checkForErrors "writing to file $fsExportData" $?
       fi
       ;;

    $MMFSCFG )     # this line contains mmfs.cfg information for some nodeset
       # If this is the nodeset from which we are exporting,
       # put the line into the fs export file.
       if [[ ${v[$NODESETID_Field]} = $nodesetId ||
             $nodesetId = $GLOBAL_ID ]]
       then
         print_newLine >> $fsExportData
         checkForErrors "writing to file $fsExportData" $?
       fi
       ;;

    $MEMBER_NODE )  # this line describes a node that belongs to some nodeset
       # Create a file with the names of all nodes in the cluster.
       # Create separate files with the names of the nodes in each nodeset.
       if [[ ${v[DESIGNATION_Field]} != $DELETED_NODE ]]
       then
         print -- "${v[$REL_HOSTNAME_Field]}" >> $allClusterNodes
         checkForErrors "writing to file $allClusterNodes" $?

         print -- "${v[$REL_HOSTNAME_Field]}" >> $nodesetMembers.${v[$NODESETID_Field]}
         checkForErrors "writing to file $nodesetMembers.${v[$NODESETID_Field]}" $?
       fi
       ;;

    $SG_HEADR )  # this is the header line for some file system
       # Starting the processing of a new file system.  If the file
       # system should be exported, put the line into the fs export
       # file and remove the line from the mmsdrfs file.
       #
       # Note:  Unless explicitly requested by their local name,
       #        remote file systems are not exported.
       if [[ ${v[$DEV_NAME_Field]} = $deviceName || $device = all &&
           ( $nodesetId = $GLOBAL_ID ||
             $nodesetId = ${v[$NODESETID_Field]} ) &&
             ${v[$FS_TYPE_Field]} = $localfs ]]
       then
         # This file system should be exported.
         exportThisFileSystem=true

         # Put an informational message:  processing file system ...
         print -- ""  # put a blank separator line
         printInfoMsg 250 $mmcmd ${v[$DEV_NAME_Field]}

         # Check whether the "odd state" flag is set.
         if [[ -n ${v[$ODD_STATE_Field]} && ${v[$ODD_STATE_Field]} != no ]]
         then
           # Tell the user to bring the filesystem data up to date.
           printErrorMsg 190 $mmcmd ${v[$DEV_NAME_Field]} ${v[$DEV_NAME_Field]}
           cleanupAndExit
         fi

         # Save the line in the fs export file.
         print_newLine >> $fsExportData
         checkForErrors "writing to file $fsExportData" $?
         printLine=false
       else
         # This file system should not be exported.
         exportThisFileSystem=""
       fi  # end if [[ ${v[$DEV_NAME_Field]} = $deviceName || $device = all &&
       ;;

    $SG_ETCFS )  # this line is a stanza line for one of the filesystems
       # If this is the first line in the stanza for a file system
       # that is being exported, save the device name, mount point
       # and nodeset to which the file system belongs.  All stanza
       # lines for file systems that are being exported are put into
       # the fs export file and removed from the mmsdrfs file.
       if [[ -n $exportThisFileSystem ]]
       then
         if [[ ${v[$LINE_NUMBER_Field]} = $MOUNT_POINT_Line ]]
         then
           fsInfo="${v[$DEV_NAME_Field]}:${v[$ETCFS_TEXT_Field]}:${v[$NODESETID_Field]}"
           fsBeingExportedList="$fsInfo $fsBeingExportedList"
         fi

         # Save the line in the fs export file.
         print_newLine >> $fsExportData
         checkForErrors "writing to file $fsExportData" $?
         printLine=false
       fi  # end if [[ -n $exportThisFileSystem ]]
       ;;

    $SG_MOUNT )
       # If the line belongs to a file system that is being exported, put the
       # line into the fs export file and remove the line from the mmsdrfs file.
       if [[ -n $exportThisFileSystem ]]
       then
         # Save the line in the fs export file.
         print_newLine >> $fsExportData
         checkForErrors "writing to file $fsExportData" $?
         printLine=false
       fi
       ;;

    $SG_DISKS )
       # Decide if this disk is to be exported.  If the disk belongs to some
       # file system, the exportThisFileSystem flag already has the correct
       # value.  If this is a free disk, it will be exported only if all file
       # systems are being exported.  Note that the exportThisFileSystem flag
       # is used to control the exporting of disks that are part of some file
       # system as well as the exporting of free disks.
       if [[ ${v[$NODESETID_Field]} = $FREE_DISK ]]
       then
         if [[ $device != all ]]
         then
           exportThisFileSystem=""
         else
           exportThisFileSystem=true
           # If not done already, put an informational message.
           if [[ -z $freeDiskStartMsg && -n $fsBeingExportedList ]]
           then
             # Put an informational message:  processing free disks ...
             print -- ""  # put a blank separator line
             printInfoMsg 98 $mmcmd
             freeDiskStartMsg=issued
           fi
         fi  # end if [[ $device = all ]]
       fi  # end if [[ ${v[$NODESETID_Field]} = $FREE_DISK ]]

       # If exporting this disk, put the SG_DISKS line into the fs export
       # file and remove it from the mmsdrfs file.
       if [[ -n $exportThisFileSystem ]]
       then
         # Fail the command if the disk is excluded.
         # The user needs to repair the file system first.
         # The only exception is if the -P option is specified.
         if [[ ${v[$EXCLUDE_Field]} = $excludedDisk && -z $Pflag ]]
         then
           print -u2 "$mmcmd: File system ${v[$DEV_NAME_Field]} contains excluded disks."
           cleanupAndExit
         fi

         # Make sure the disk type field is set.
         [[ -z ${v[$DISK_TYPE_Field]} || ${v[$DISK_TYPE_Field]} = unknown ]] &&  \
            v[$DISK_TYPE_Field]=$(assignDiskType)

         # Save the line in the fs export file.
         print_newLine >> $fsExportData
         checkForErrors "writing to file $fsExportData" $?
         printLine=false
       fi  # end if [[ -n $exportThisFileSystem ]]
       ;;

    * )  # Pass all other lines without a change.
       ;;

  esac  # end Change some of the fields

  # Build and write the line to the new mmsdrfs file.
  if [[ $printLine = true || -n $Pflag ]]
  then
    print_newLine >> $newsdrfs
    checkForErrors "writing to file $newsdrfs" $?
  fi

  IFS=":"  # Change the separator back to ":" for the next iteration.

done  # end while read -u3 sdrfsLine

IFS="$IFS_sv"  # Restore the default IFS settings.


# Make sure that everything went OK.

if [[ $device = all && -z $fsBeingExportedList ]]
then
  # No file systems were found.
  printErrorMsg 200 $mmcmd
  cleanupAndExit
fi


############################################
# Sort the new version of the mmsdrfs file.
############################################
LC_ALL=C $SORT_MMSDRFS $newsdrfs -o $newsdrfs
checkForErrors "sorting $newsdrfs" $?


##############################################
# If the -P option is specified, we are done.
##############################################
if [[ -n $Pflag ]]
then
  # Remove the output file from the list of files to be deleted upon exit.
  LOCAL_FILES="$LOCAL_FILES_SV"
  cleanupAndExit 0
fi


################################################################
# If this is an old cluster type, we managed to extract the
# file system data.  Perform a fake commit and take an early
# exit.  The rest of the mm infrastructure does not know how to
# handle a real commit, plus the user is expected to tear down
# the obsolete environment right away and create a new cluster.
################################################################
if [[ $MMMODE != lc && $MMMODE != single ]]
then
  # Perform a pseudo commit of the changes on this node only.
  $cp $newsdrfs $mmsdrfsFile
  $rm -f ${mmfsEnvLevel}+([0-9])
  $touch ${mmfsEnvLevel}$newGenNumber

  # Remove the output file from the list of files to be deleted upon exit.
  LOCAL_FILES="$LOCAL_FILES_SV"

  cleanupAndExit 0

fi  # end if [[ $MMMODE != lc && $MMMODE != single ]]


############################################
# Lock the gpfs object to prevent daemons
# from starting during the commit phase.
############################################
[[ $getCredCalled = no ]] && getCred
setRunningCommand "$mmcmd" $primaryServer
checkForErrors setRunningCommand $?
gpfsLocked=yes


################################################################
# Make sure the file systems being exported are not mounted
# and that the currently-appointed fs managers have resigned.
################################################################
for expFs in $(print -- "$fsBeingExportedList")
do
  IFS=':'
  set -f ; set -- $expFs ; set +f
  IFS="$IFS_sv"
  deviceName=$1
  fqDeviceName="/dev/$deviceName"
  fsMountPoint=$2
  nodesetId=$3

  if [[ -z $daemonDownVerified ]]
  then
    preferredNode=0
    $mmcommon onactive $preferredNode $nodesetMembers.$nodesetId        \
                       $NO_FILE_COPY $fqDeviceName $CHECK_ALL $NO_LINK  \
                       $MOUNT_CHECK_ONLY $RESIGN_FSMGR 2>$errMsg
    rc=$?
    if [[ $rc -eq $MM_FsMounted ]]
    then
      # The file system is still mounted; messages were issued by mmcommon.
      cleanupAndExit
    elif [[ $rc -eq $MM_DaemonDown ]]
    then
      # GPFS is down on all nodes that we care about; that's just fine.
      rc=0
      daemonDownVerified=yes
    elif [[ $rc -eq $MM_ConnectionReset ]]
    then
      # An internode connection was reset.
      printErrorMsg 257 $mmcmd
      cleanupAndExit
    elif [[ $rc -ne 0 ]]
    then
      # An unexpected error occurred during the mount check.
      if [[ -s $errMsg ]]
      then
        # Show the error messages from the daemon.
        $cat $errMsg 1>&2
      else
        # The mount check failed and there were no messages from the daemon.
        printErrorMsg 171 $mmcmd "mount check for $fqDeviceName" $rc
      fi
      # The command was unable to determine whether the file system is mounted.
      printErrorMsg 564 $mmcmd $fqDeviceName
      cleanupAndExit
    fi
    $rm -f $errMsg
  fi  # end if [[ -z $daemonDownVerified ]]

  # If exporting a single file system, remove all traces
  # of that file system from the nodes.  Ignore errors.
  # If exporting all file systems, we will bypass this
  # time-consuming process; the cluster will most likely
  # be destroyed anyway.  Plus, we will propagate the
  # changes at the end of the command like we always do.
  [[ $device != all ]] &&  \
    $mmcommon onall $nodesetMembers.$nodesetId $unreachedNodes  \
                      rmfs $fqDeviceName $fsMountPoint >/dev/null 2>&1

done  # end for expFs in $(print -- "$fsBeingExportedList")


############################################################
# Replace the mmsdrfs file in the sdr with the new version.
############################################################
trap "" HUP INT QUIT KILL
gpfsObjectInfo=$(commitChanges  \
   $nsId $nsId $gpfsObjectInfo $newGenNumber $newsdrfs $primaryServer)
rc=$?
if [[ $rc -ne 0 ]]
then
  # Cannot replace file in the sdr.
  printErrorMsg 381 $mmcmd
  cleanupAndExit
fi


###################################################################
# Unlock the sdr.
###################################################################
freeLockOnServer $primaryServer $ourNodeNumber >/dev/null
sdrLocked=no
trap posttrap HUP INT QUIT KILL

# Remove the output file from the list of files to be deleted upon exit.
LOCAL_FILES="$LOCAL_FILES_SV"


#################################################################
# Propagate the new mmsdrfs file.  This process is asynchronous.
#################################################################
propagateSdrfsFile async $allClusterNodes $newsdrfs $newGenNumber

cleanupAndExit 0

