#!/bin/bash
# by John Romanowski 2/2007, New York State Office for Technology
# BMRtool - a bare machine restore tool  (BMR)
# BMRtool prepares for and later helps with bare machine restore of a linux server.
# Its scan mode prepares for BMR by generating into one file all the commands it'll
# need someday at BMR-time to activate the disks, format, partition, LVM, multipath, mkfs,
# mount, mkswap them so you can quickly get the disks ready to restore from backups. 
# It generates the restore commands too. At BMR-time you use a rescue system to run 
# this script to read and execute the generated command file to prepare your disks
# and restore their data. You run mkinitrd, zipl and boot your restored server.
# 
# This script does not backup the contents of your disks, you run backups for that.
# If needed, it backs up LVM metadata, partition tables, lvm.conf and multipath.conf.
# I wrote this tool for my site's needs and didn't code support for features we're not
# using or didn't have at the time.
#------------
#+ SUPPORTED as of 3/16/07:    (but review UNSUPPORTED too for possible exceptions)
#------------
#+ SLES9 31- or 64-bit on a z/VM guest;
#+ ECKD dasd dasdfmt-ed in compatible disk layout (cdl) blocksize 4096; 
#+ partitioned and unpartitioned FBA dasd; partitioned and unpartitioned FCP scsi luns;
#+ multipath-tools; LVM2; 
#+ ext2 and ext3 filesystems created with all defaults except for reserved block count (-m), 
#+ maximum mount count (tune2fs -c) and check interval (tune2fs -i) which the script
#  preserves; script preserves a filesystem's mode, uid and gid; 
#+ Swap partitions and volumes, new-style only (mkswap -v1)
#+ sysfs at /sys, procfs at /proc; mkswap with no options; Tivoli Storage Manager (TSM) client
#+ to restore filesystems from backups.
#
#--------------
#- UNSUPPORTED as of 3/16/07:  (mainly because I didn't add the code to handle these)
#--------------
#- SLES9 in an LPAR (didn't have one to test, script will probably work)
#- ECKD dasd in 'CMS disk layout' or 'Linux disk layout'; the DIAG dasd driver; 
#- ECKD dasd non-default volser '-l <volser>': non-interactive fdasd resets it to default.
#- PAV dasd (Parallel Access Volumes) - I didn't code for it. See MPIOptbl_list for hints.
#- Extended and logical disk partitions - I didn't code for them.
#- LVM snapshots, dcss block devices, cdrom, tape, etc... - need to filter out and skip them.
#- LABEL= and UUID= mounts - I didn't code for them.
#- Filesystems other than ext2 or ext3 - I didn't code for them.
#- md (multidisk), EVMS (Enterprise Volume Manager System) - I didn't code for them.
#______________________________________________________________________________________________
function say()
{  echo "$*"
}
#______________________________________________________________________________________________
function errm()
{  echo "$SCRIPT: $*" > /dev/console                       #Message on Linux console
   echo "$SCRIPT: $*" >&2
}
#______________________________________________________________________________________________
function dbg()
{
if [ "$OPTdebug" = "yes" ] ; then say "$*"
fi
}
#______________________________________________________________________________________________
function in_list()
{
#Check if word $1 is already in list named $2. return 0 if $1 in list $2 else return 1.
# ($1 is the word; $2 is the name (not value) of the list)
eval "case \" \$$2 \" in  *\" $1 \"* ) return 0 ;; esac ; return 1"
}
#______________________________________________________________________________________________
function addto_list()
{
#Insert word $1 into list named $2    ($1 is the word; $2 is the name (not value) of the list)
#BMR_umount reles on addto_list prefixing new to list, LIFO
eval "in_list \"$1\" \"$2\" || $2=$1\" \$$2\" "
}
#______________________________________________________________________________________________
function usage_err()
{
cat <<EOFUSAGEERR

Script $SCRIPT is used to prepare for and later assist in Bare Machine Restore of your server.
$SCRIPT [-d BMRdir] [-f BMRcmds] [-v] [-h] [-t] mode
For usage help try $SCRIPT -h
EOFUSAGEERR
exit $1
}
#______________________________________________________________________________________________
function usage()
{ 
cat <<EOFUSAGE

Script $SCRIPT is used to prepare for and later assist in Bare Machine Restore of your server.
$SCRIPT [-d BMRdir] [-f BMRcmds] [-v] [-h] [-t] mode

Options:
-h      Display this helpful text.
-v      Verbose. Display messages tracing progress.
-d      Set the input/output directory for the BMRcmds file and related files.
        BMRdir defaults to $BMRdir and is created if necessary.
-f      Names the file of BMR commands to write/read in BMRdir; default is 'BMRcmds'.
-t      Test. Display but don't do the commands read from file BMRcmds.

mode:   Is one of:      scan    diskinit    restore    mount    umount
         scan  - Scans system, writes file BMRcmds and related files to BMRdir. These files
                 are input files of the other modes which are used at BMR-time.

     diskinit  - Overwrites your disks! Does the commands in BMRcmds to activate, format,
                 partition disks; define LVM PV's, VG's and LV,s, mkfs's, mkdir mount
                 points, mkswap's, and mounts empty filesystems in order at $BMRmnt.

      restore  - After doing mode diskinit or mount, run restore mode to do the TSM restore
                 commands that scan mode put in BMRcmds file. Overwrites your disks!

        mount  - Does the BMRcmds commands to activate and mount the server's filesystems
                 rooted at $BMRmnt in the order scan mode saw. Doesn't overwrite disks.

        umount - umounts the directories that were mounted by modes mount or diskinit.
EOFUSAGE
exit 0
}
#______________________________________________________________________________________________
function get_check_parms()
{    local   s=$SCRIPT    opt
if [ "${1:0:1}" = "-" ] ; then
  while getopts :d:f:hvt  opt ; do
    case $opt in
      \:|\?) case $OPTARG in
               d) say "-d requires a directory name" ;;
               f) say "-f requires a file name"      ;;
               *) say "Unknown option: -$OPTARG"     ;;
             esac
             usage_err 1
             ;;
         d)  BMRdir=$OPTARG     ;;
         f)  fBMRcmds=$OPTARG   ;;
         h)  usage              ;;
         v)  OPTdebug="yes"        ;;
         t)  OPTtest="yes"        ;;
         *) say "Error in script $s: Not coded for option $opt $OPTARG"
            exit 13             ;;
    esac
  done
  shift $(expr $OPTIND - 1)
fi
if [ -z "$1" ]; then
  say "Missing the mode parameter."
  usage_err 2
elif ! in_list "$1" mode_list ; then
  say "Not a valid mode: $1"
  usage_err 3
elif [ $# -gt 1 ] ; then
  say "Too many parameters after the mode: $*"
  usage_err 4
else mode=$1
fi

if [ "$BMRdir" != "${BMRdir%/}" ] ; then 
  say "Error: Give a BMRdir without an ending /"
  usage_err 5
elif [ "${BMRdir:0:1}" != "/" ] ; then
  say "Error: Use an absolute BMRdir"
  usage_err 5
fi

fBMRcmds=$BMRdir/$fBMRcmds
if [ "$mode" = "scan" ] ; then
  if ! mkdir -p $BMRdir ; then
    say "Error doing mkdir -p $BMRdir"
    usage_err 6
  fi
  if ! cat /dev/null > $fBMRcmds ; then
    say "Error writing to file $fBMRcmds"
    usage_err 7
  fi
elif [ ! -d $BMRdir -o ! -r $fBMRcmds ] ; then                         #For the other modes   
  say "Error- finding directory $BMRdir or reading file $fBMRcmds"
  usage_err 8
fi
}
#______________________________________________________________________________________________
function lsMaj_min()
{
#Given a device filename or symlink to one, use ls command to return its major:minor
set -- $(ls -lL $1)
echo -n ${5%,}:${6}
}
#______________________________________________________________________________________________
function fBMR()
{    local   ftype=$1  id=$2
#Form and return a filename.
#Want to make relative filenames so they reference the BMRdir.
echo -n "./$ftype-$id"
}
#______________________________________________________________________________________________
function word()
{   local n=$1
#echo word # $1 of words $2-* example: word 2 abc de fg       is the 2nd word: de
(( n += 1 ))
eval "echo -n \$$n"
}
#______________________________________________________________________________________________
function putA_()
{   local aname=$1
#Given $1, an array's name, put $2-* into its next element.
shift 1
eval "$aname[\${#$aname[@]}]=\"$@\""
}
#______________________________________________________________________________________________
function write_array()
{   local aname=$1  prefix="$2"   tprefix="$2"   r  name
#Given an array's name, append its prefixed elements to the output file
if [ "$prefix" = "AS-IS" ] ; then
  prefix=                               #The array's builder already prefixed the elements
  tprefix="$pM"                         #Use the mount prefix for the header lines
elif [ -z "$prefix" ] ; then
  prefix="??" ;  tprefix="$prefix"      #Missing the prefix parameter.
  errm "$SCRIPT ERROR- missing prefix for $aname"
fi
echo "${tprefix}#" >> $fBMRcmds
name=${aname#A_}
echo "${tprefix}#----------------- $name -----------------" >> $fBMRcmds
eval "for r in \"\${$aname[@]}\"; do echo \"\$prefix\$r\" >> \$fBMRcmds ; done"
}
#______________________________________________________________________________________________
function write_file_BMRcmds()
{
say "Writing commands to $fBMRcmds"
 
write_array A_info       "$pM"
write_array A_drivers    "$pM"
write_array A_preFmtPart "$pD"         #Not really data-destructive; do prior to MPIO ptabling.
write_array A_actDASD    "$pM"
write_array A_actHBA     "$pM"
write_array A_actLUN     "AS-IS"
write_array A_dasdfmt    "$pD"
write_array A_partition  "$pD"
write_array A_actMPIO    "$pM"
write_array A_pvcreate   "$pD"
write_array A_vgcfgrest  "$pD"
write_array A_actLVM     "$pM"
write_array A_mkfs       "$pD"
write_array A_mount      "AS-IS"               
write_array A_mkswap     "$pD"

write_array A_umount     "$pU"         # mode umount's records 
write_array A_restore    "$pR"         # mode restore's records
}
#______________________________________________________________________________________________
function devmap_name()
{    local   mm=$1   r
#Given a device-mapper major:minor, echo its dmsetup name. See build_devmapname()
for r in $(seq 0 $((${#devmapname[@]} - 1))) ; do 
  set -- ${devmapname[$r]}                                           # $1=MM  $2=name  
  if [ "$mm" = "$1" ] ; then
    echo -n $2
    return 0
  fi
done
errm "ERROR in $FUNCNAME: didn't find a name for $mm"
echo -n "??devmap_name??"
} 
#______________________________________________________________________________________________
function DM_deps_of_dmname()
{
#Convert output of dmsetup deps dmname from parenthesized (major, minor)'s to major:minor's
# "n dependencies  : (MM, mm) (MM, mm) (MM, mm)" to "MM:mm MM:mm MM:mm"
#Ex: 8 dependencies  : (8, 240) (8, 160) (8, 96) (8, 16) (65, 16) (8, 192) (8, 128) (8, 48)
echo -n $(dmsetup deps $1 | cut -d: -f 2 | sed 's/, /:/g' | tr \(\)[:blank:] - | sed 's/-/ /g')
}
#______________________________________________________________________________________________
function VGname()
{    local   dmname  r
#Given a LVM logical vol's major:minor, echo its Volume Group name.
dmname=$(devmap_name $1)                    # !! expecting name format is: VGname-LVname
for r in $(seq 0 $((${#lvsRecs[@]} - 1))) ; do 
  set -- ${lvsRecs[$r]}                                           # $1=LVname  $2=VGname  
  if [ "$dmname" = "$2-$1" ] ; then
    echo -n $2
    return 0
  fi
done
errm "ERROR in $FUNCNAME: didn't find a name for $mm"
echo -n "??LVM-VG??"
} 
#______________________________________________________________________________________________
function deviceClass()
{    local     mm
# Given a major:minor of a disk-based device either physical or device-mapper, echo a word to
# classify it.
# !! 1/2007 jrr: we're using only LVM and multipath-tools; redo the logic when we use more.
if [ "${1%:*}" = "$DM_MAJOR" ] ; then
  if in_list ${1#*:} LVminor_list ; then       
    echo -n "DM.LV"                  #It's an LVM logical vol
  else                               # !! update logic when we use more than LVM & MPIO
    mm=$(word 1 $(DM_deps_of_dmname $(devmap_name $1) ) )       #1st/only major:minor dep
    if [ "${mm%:*}" = "$DM_MAJOR" ] ; then
      echo -n "DM.MPIOp"             #a DM MPIO partition depends on a DM MPIO device
    else
      echo -n "DM.MPIO"              #a DM MPIO device depends on real, non-DM device(s).
    fi
  fi
else
  echo -n "REAL"                   #A physical device
fi
}
#______________________________________________________________________________________________
function BMR_drivers()
{    local   r  
declare -a lsmodRecs
#Brute force method; modprobe every driver the running system's using.
putA_ A_drivers "#"
lsmodRecs=( $(lsmod | tail -n+2 | awk '{print $1}') )
for r in $(seq $((${#lsmodRecs[@]} - 1)) -1 0) ; do   #seems listed in reverse loading seq
  putA_ A_drivers "modprobe ${lsmodRecs[$r]}"
done
if [ -n "$DM_MAJOR" ] ; then putA_ A_drivers "/sbin/devmap_mknod.sh"  #for device-mapper
fi
}
#______________________________________________________________________________________________
function BMR_restore()
{   local   BMRmntpt   BMRmntptx   orig_mntpt
#This generates all the mode 'restore' commands.
#Generate commands to restore file sytems from backup media. Very site-specfic.
# We use IBM's Tivoli Storge Manager (TSM) and its dsmc command.
#Use umount_list to fill in A_restore[]; the restore order of filesystems doesn't matter.
putA_ A_restore "# . . . These are 'restore' mode commands . . ."
putA_ A_restore "#  dsmc is the IBM Tivoli Storage Manager (TSM) command line client"
putA_ A_restore "#"
putA_ A_restore "df -i    #Show inode counts before restores"

for BMRmntpt in $umount_list ; do
  orig_mntpt=${BMRmntpt#$BMRmnt}
  if [ "$orig_mntpt" = "/" ] ; then 
    BMRmntptx=$BMRmntpt
  else 
    orig_mntpt=$orig_mntpt"/"              #To dsmc, "/dir" & "/dir/" aren't same thing.
    BMRmntptx=$BMRmntpt"/"                                              #for dsmc use
  fi
  if [ "$orig_mntpt" = "/tmp/" ] ; then
    putA_ A_restore "#OFT excludes backup of /tmp fs; Ok if its restore fails."
  fi
  putA_ A_restore "dsmc restore $orig_mntpt $BMRmntptx -subd=yes  -replace=all -quiet"
done
putA_ A_restore "df -i    #Show inode counts after restores"
putA_ A_restore "#"
}
#______________________________________________________________________________________________
function BMR_umount()
{   local   mntpt
#This generates all the mode 'umount' commands.
#Use umount_list to fill in A_umount[] ; we depend on addto_list added LIFO 
putA_ A_umount "# . . . These are 'umount' mode commands . . ."
putA_ A_umount "#"
putA_ A_umount "umount $BMRmnt/{sys,proc}  #In case needed after chroot mkinitrd, zipl"
for mntpt in $umount_list ; do
  putA_ A_umount "umount $mntpt"
done
}
#______________________________________________________________________________________________
function BMR_dasd_ptable()
{    local   dasdname=$1   busid=$2  discipline=$3  fdasdLn  _IFS  r  fptbl
declare -a fdasdLn
#Handle query/dump/restoring a dasd partition table: fdasd for ECKD, sfdisk for FBA.
#Some output from fdasd -p; "Device" col is blank for unused part; 1 to 3 partitions.
#------------------------------- tracks -------------------------------
#               Device      start      end   length   Id  System
#          /dev/dasdd1          2       12       11    1  Linux native
#                              13      199      187       unused
#          /dev/dasdd2        200      299      100    2  Linux native
#exiting...

putA_ A_partition "#"
fptbl=$(fBMR ptbl $busid)                           #relative path for reading it later
if [ "$discipline" = "ECKD" ] ; then
  #Build fdasd partition table in config file fptbl for restore at BMR-time.
  _IFS=$IFS ; IFS=$'\n'           #Want to split this output on newline, not whitespace.
  #Store the /dev/ lines that follow the "--- tracks ---" line
  fdasdLn=( $(fdasd -p /dev/$dasdname | grep -A 5 -e '-- tracks --' | grep -e '/dev/') )
  IFS=$_IFS

  for r in $(seq 0 $((${#fdasdLn[@]} - 1))) ; do   #get start & end from the /dev/ lines
    set -- ${fdasdLn[$r]}                          #need words 2-3   $2-$3
    if [ "$r" = 0 ] ; then  
      echo "[$2,$3]" >  $BMRdir${fptbl:1}          #Build new fdasd config file
    else 
      echo "[$2,$3]" >> $BMRdir${fptbl:1}          #use absolute, not relative path
    fi
  done
  putA_ A_partition "fdasd -c $fptbl  /dev/\\\$(atBMR $busid 0)"
else                                             #Use sfdisk to dump/restore dasd FBA ptable
  sfdisk -d /dev/$1 > $BMRdir${fptbl:1}
  putA_ A_partition "sfdisk --force -uS /dev/\\\$(atBMR $busid 0) < $fptbl"
fi
}
#______________________________________________________________________________________________
function BMR_prep_dasd()
{    local  dasdname=$1  busid=$2  pnum=$3  discipline=$4  volser  parms
if [ "$pnum" != 0 ] ; then                   #Need to restore the ptable
  BMR_dasd_ptable $dasdname  $busid  $discipline
fi
if [ "$discipline" = "ECKD" ] ; then     #assume 'compatible disk layout'(cdl); do dasdfmt
  putA_ A_dasdfmt "#"
  volser=$(dasdview -j /dev/$dasdname)
  #I haven't coded for a $ in volser; man dasdfmt
  if [ "0X"${busid:4} != "$volser" ] ; then
    putA_ A_dasdfmt "#Non-default volser $volser,  fdasd will change it to default. Sorry."
  fi
  parms="-b 4096 -y -m 1000 -l '$volser' "
  putA_ A_dasdfmt "dasdfmt $parms -f /dev/\\\$(atBMR $busid 0)"
fi  
#BMR_disk_activation_seq() activates disks in correct order.
}
#______________________________________________________________________________________________
function BMR_prep_lun()
{     local   sdname=$1  hba=$2  wwpn=$3  lun=$4  pnum=$5   mpio=$6  scsiid  fptbl
#Handle some partitioning, defer some and activating for BMR_disk_activation_seq().
scsiid=$(scsi_id -gus /block/$sdname)
if ! in_list $scsiid scsiid_list ; then                #Need to check for and handle a ptable
  addto_list $scsiid scsiid_list
  if [ "$pnum" != 0 ] ; then                           #Yes, we found it partitioned
    fptbl=$(fBMR ptbl $scsiid)                         #Unique filename for partition table
    sfdisk -d /dev/$sdname > $BMRdir${fptbl:1}         #Dump the partition table to file
    if [ "$mpio" = yes ] ; then 
      #This is a partitioned multipath lun; remember it's scsiid for
      #later BMR_disk_activation_seq() so ptable's restored once on 1st path available
      #instead of restoring it on each of its paths here.
      if [ -z "$MPIOptbl_list" ] ; then               #Do once before the Tptbl() records
        putA_ A_actLUN "${pD}# Internal function Tptbl() partitions a multipath lun once."
        putA_ A_actLUN "${pD}: rm MPIOptbl_list-* 2> /dev/null  #Tptbl() tests for files."
      fi
      addto_list $scsiid MPIOptbl_list               #For BMR_disk_activation_seq() to handle
    else
      putA_ A_partition "#"
      putA_ A_partition "sfdisk --force -uS /dev/\\\$(atBMR $hba:$wwpn:$lun 0) < $fptbl"
    fi
  fi
fi
}
#______________________________________________________________________________________________
function BMR_pvcreate()
{    local   majmin=$1  fVGcfgbkup=$2  r  uuid   VG
#Case 1: for an LVM PV assigned to a VG 	
#Given a PV's major:minor, VGcfgbkup filename & BMRdevname in $3-* : pvcreate it at BMR-time.
#Case 2: for a PV not in a VG were given major:minor & BMRdevname in $2-*; it's VG is $NO_VG
if ! in_list $majmin PV_list ; then                                    #once per LVM PV
  addto_list $majmin PV_list
  #Use majmin to get uuid from mmpvscan[], Format: path  major:minor uuid  VGname
  uuid="ERROR-in-$FUNCNAME"
  for r in $(seq 0 $((${#mmpvscan[@]} - 1)) ) ; do
    if [ "$majmin" = "$(word 2 ${mmpvscan[$r]})" ] ; then
      uuid=$(word 3 ${mmpvscan[$r]})  ;  VG=$(word 4 ${mmpvscan[$r]})
      break
    fi
  done
  if [ "$VG" != "$NO_VG" ] ; then
    #Note 1/2007 jrr: had to use --uuid, pvcreate rejects -u; also hated -Z
    #Note -ff needed to force restore to a disk with a matching uuid.
    shift 2                                   #The last parms can be multiword \$(atBMR ...
    putA_ A_pvcreate "pvcreate -ffy --uuid $uuid --restorefile $fVGcfgbkup $@"
  else
    shift 1
    putA_ A_pvcreate "#This next PV is not in any VG."
    putA_ A_pvcreate "pvcreate -ffy --uuid $uuid $@"
  fi
  putA_ A_pvcreate "#"
fi
}
#______________________________________________________________________________________________
function BMR_LVM_VG()
{    local   VG=$1   fVGcfgbkup
#Given a VGname and filename to use for its metadata: backup metadata & BMR restore it
if ! in_list $VG VG_list ; then                                    #once per LVM Vol Group
  if [ -z "$VG_list" ] ; then                                         #BMR: init LVM once
    say "Copying LVM config file $fCFGlvm into $BMRdir to use at recovery time."
    cp -a $fCFGlvm  $BMRdir/
    putA_ A_actLVM "#"
    putA_ A_actLVM "vgscan --mknodes"
    putA_ A_preFmtPart "#OK to get message 'Unable to find volume group...' here: "
  fi
  addto_list $VG VG_list
  putA_ A_preFmtPart ": vgchange -a n $VG  #prep before partitioning a 2nd or 3rd time"
  putA_ A_actLVM "vgchange -a y $VG"
  fVGcfgbkup=$(fBMR VG $VG) 
  vgcfgbackup -f $BMRdir${fVGcfgbkup:1} $VG                             #Write absolute path
  putA_ A_vgcfgrest "#"
  putA_ A_vgcfgrest "vgcfgrestore --file $fVGcfgbkup $VG"         #restore metadata to PV's
fi
}
#______________________________________________________________________________________________
function BMR_otherPV()
{    local   mm=$1  VG   
#The parms, $2-, define the device name word(s) to give to BMR_pvcreate.
#otherPV handles PV's of a VG already seen by mount, mkswap processing; or PV of a VG not
#previously seen: the idle VG's online but none of its LV's used at this moment? 
#Also, a PV might not belong to any VG ($NO_VG), in which case we note that in BMR_pvcreate.

shift 1 
VG=$(word 3 $stage)
if [ "$VG" != "$NO_VG" ] ; then
  if in_list $VG idleVG_list || ! in_list $VG VG_list ; then
    addto_list $VG idleVG_list
    putA_ A_pvcreate "#This next PV is from idle VG $VG"
  else
    putA_ A_pvcreate "#This next PV of VG $VG is not part of an LV"
  fi
  BMR_LVM_VG $VG
  BMR_pvcreate $mm  $(fBMR VG $VG)  $@                    #can be multiword $(atBMR ...
else
  BMR_pvcreate $mm  $@                                    #can be multiword $(atBMR ...
fi
}
#______________________________________________________________________________________________
function BMR_tune2fs()
{    local   ext23=$1  path=$2  r  IFSbk  opts  blkcnt=9999999  rzrv_blkcnt  max_mntcnt
     local   chkintN  chkintL
#Given $1 is ext2 or ext3 and $2 is dev path now &  $3-* is device path atBMR-time
#output a tune2fs if some checked-for fs options are not set to defaults.

declare -a tuneRecs
IFSbk=$IFS  ; IFS=$'\n'
tuneRecs=( $(tune2fs -l $path) )
IFS=$IFSbk
opts=
for r in $(seq 0 $((${#tuneRecs[@]} - 1)) ) ; do
  case "${tuneRecs[$r]}" in
   "Block count:"* ) # Block count:              20480
            blkcnt=$(word 3 ${tuneRecs[$r]})
            ;;              # !! expecting it to be listed before "Reserved block count:"
   "Reserved block count:"* ) # Reserved block count:     0
            rzrv_blkcnt=$(word 4 ${tuneRecs[$r]})
            let rzrv_percnt=(rzrv_blkcnt*100+blkcnt-1)/blkcnt
            if [ "$rzrv_percnt" != 5 ] ; then                       #Not the default %
              opts=$opts" -m "$rzrv_percnt
            fi
            ;;
   "Maximum mount count:"* ) # Maximum mount count:      34
            max_mntcnt=$(word 4 ${tuneRecs[$r]})
            #mkfs -t ext2/3 varies the max_mntcnt, no static default; set it to what it was.
            opts=$opts" -c "$max_mntcnt
            ;;
   "Check interval:"* ) # Check interval:           15552000 (6 months)
            chkintN=$(word 4 ${tuneRecs[$r]})
            chkintN=${chkintN:1}                                      #drop leading "("
            chkintL=$(word 5 ${tuneRecs[$r]})
            chkintL=${chkintL:0:1}                      #1st letter of days, weeks, months
            if [ "$chkintN$chkintL" != "6m" ] ; then                          #not default
              opts=$opts" -i "$chkintN$chkintL
            fi
            ;;
  esac
done
if [ -n "$opts" ] ; then
  shift 2
  putA_ A_mkfs "tune2fs $opts $@"
fi
}
#______________________________________________________________________________________________
function BMR_chmod_own()
{   local  mntptnow=$1  mntpt=$2   rec  tmp1  tmp2
#Generate the chmod and chown to make the newly made BMR-time dir match the current dir.
# stat /opt/oracle/ | tail -n+4 | head -n1 | tr -d \(\)       Yields a simpler case:
# Access: 0755/drwxr-xr-x  Uid:   502/  oracle   Gid:  1002/     dba

rec=$(stat $mntptnow | tail -n+4 | head -n1 | tr -d \(\)  )
set -- $rec
tmp1=${2%/*}                                                      #numeric mode
putA_ A_mount "${pD}chmod $tmp1 $mntpt   # $2"      #A_mount is written "AS-IS", needs prefix

tmp1=${4%/*}                                                      #numeric uid
tmp2=${7%/*}                                                      #numeric gid
#Use numeric uid, gid to avoid mis-mappings of the rescue system
putA_ A_mount "${pD}chown  $tmp1:$tmp2 $mntpt   # $3 $4 $5   $6 $7 $8"
}
#______________________________________________________________________________________________
function BMR_mount()
{   local  device=$1  c1  c2  mntpt  orig_mntpt fstype warnmsg
putA_ A_mount "${pM}#"                             #A_mount is written "AS-IS", needs prefix       
orig_mntpt=$(word 3 $stage)                       
mntpt=$BMRmnt$orig_mntpt                       
if [ "$orig_mntpt" = "/" ] ; then        # "diskinit" and "mount" modes need this mount point
  putA_ A_mount "${pM}mkdir -p $mntpt"
else
  putA_ A_mount "${pD}mkdir -p $mntpt"
fi
putA_ A_mount "${pM}mount $device $mntpt"
addto_list $mntpt umount_list              #we rely on addto_list prefixing new to list, LIFO
BMR_chmod_own $(word 3 $stage)  $mntpt     #chmod-own the fs not the underlying mntpt

putA_ A_mkfs "#"
fstype=$(word 4 $stage)
if [ "$fstype" = "ext2" -o "$fstype" = "ext3" ] ; then
  putA_ A_mkfs "mkfs -t $fstype -q $device"                                      #-q quietly
  BMR_tune2fs $fstype $(word 2 $stage) $@
else                                               # 1/2007 jrr: I didn't code for other fs's
  warnmsg="!! WARNING: File system type, $fstype, might not work with this script."
  errm "$warnmsg"
  putA_ A_mkfs "#"$warnmsg
  putA_ A_mkfs "mkfs -t $fstype $device"
fi
}
#______________________________________________________________________________________________
function setStage()
{
dbg ";;; $FUNCNAME: after $1) to [$2]"
stage=$2
}
#______________________________________________________________________________________________
function do_stage()
{   local   mm=$1  cmd   devcl  situ  VG  fVGcfgbkup   devname   
#Given a major:minor and info in stage: output this stage's BMR commands & change stage.
cmd=$(word 1 $stage)
devcl=$(deviceClass $mm)                          # REAL,  DM.LV, DM.MPIO, DM.MPIOp, ...

situ="$cmd,$devcl"          
dbg ";;; $FUNCNAME: situ=$situ mm=$mm   stage=$stage"
case "$situ" in 
  mount,REAL ) #stage is: mount path  mntpt  fstype disktype  busid|lunpath  pnum
               BMR_mount "/dev/\\\$(atBMR $(word 6 $stage) $(word 7 $stage))" 
               setStage "$situ" "NONE" 
               ;;
  mount,DM.MPIOp ) #stage is: mount path  mntpt  fstype 
               BMR_mount $(word 2 $stage)  
               setStage "$situ" "no_op 1"             # 1=partitioned
               ;;
  mount,DM.MPIO ) #stage is: mount path  mntpt  fstype 
               BMR_mount $(word 2 $stage)  
               setStage "$situ" "multipath 0"          # 0=whole
               ;;
  mount,DM.LV ) #stage is: mount path  mntpt  fstype 
               BMR_mount $(word 2 $stage)
               VG=$(VGname $mm)
               BMR_LVM_VG $VG 
               setStage "$situ" "pvcreate $(fBMR VG $VG)"
               ;;
  mkswap,REAL ) #stage is: mkswap path disktype  busid|lunpath  pnum
               putA_ A_mkswap "#"
               putA_ A_mkswap "mkswap /dev/\\\$(atBMR $(word 4 $stage) $(word 5 $stage))"
               setStage "$situ" "NONE" 
               ;;
  mkswap,DM.MPIOp ) #stage is: mkswap path
               putA_ A_mkswap "#"
               putA_ A_mkswap "mkswap $(word 2 $stage)"
               setStage "$situ" "no_op 1" 
               ;;
  mkswap,DM.MPIO ) #stage is: mkswap path
               putA_ A_mkswap "#"
               putA_ A_mkswap "mkswap $(word 2 $stage)"
               setStage "$situ" "multipath 0" 
               ;;
  mkswap,DM.LV ) #stage is: mkswap path
               putA_ A_mkswap "#"
               putA_ A_mkswap "mkswap $(word 2 $stage)" 
               VG=$(VGname $mm)
               BMR_LVM_VG $VG 
               setStage "$situ" "pvcreate $(fBMR VG $VG)"
               ;;
  pvcreate,REAL ) #stage is: pvcreate  fVGcfgbkup  disktype  busid|lunpath  pnum
               devname="/dev/\\\$(atBMR $(word 4 $stage) $(word 5 $stage))"
               BMR_pvcreate  $mm  $(word 2 $stage) $devname
               setStage "$situ" "NONE"  
               ;;
  pvcreate,DM.MPIOp ) #stage is: pvcreate  fVGcfgbkup
               BMR_pvcreate  $mm  $(word 2 $stage) "/dev/mapper/$(devmap_name $mm)"
               setStage "$situ" "no_op 1"
               ;;
  pvcreate,DM.MPIO ) #stage is: pvcreate  fVGcfgbkup
               BMR_pvcreate  $mm  $(word 2 $stage) "/dev/mapper/$(devmap_name $mm)"
               setStage "$situ" "multipath 0" 
               ;;
  multipath,REAL ) #stage is: multipath  partitionedflag
               if [ ${#A_actMPIO[*]} -eq 0 ] ; then   #BMR start multipath-tools once
         say "Copying $fCFGmpio config file into $BMRdir to use at recovery time."
                 cp -a $fCFGmpio  $BMRdir/
                 putA_ A_actMPIO "#"
                 putA_ A_actMPIO "/etc/init.d/boot.multipath start"   # !! SLES 9
                 putA_ A_actMPIO "/etc/init.d/multipathd start"
               fi
               setStage "$situ" "NONE" 
               ;;
  no_op,DM.MPIO ) setStage "$situ" "multipath 1" 
               ;;
  otherPV,REAL ) #stage is: otherPV  path  VGname  disktype  busid|lunpath  pnum
               BMR_otherPV  $mm "/dev/\\\$(atBMR $(word 5 $stage) $(word 6 $stage))"
               setStage "$situ" "NONE"
               ;;
  otherPV,DM.MPIOp ) #stage is: otherPV  path  VGname
               BMR_otherPV $mm "/dev/mapper/$(devmap_name $mm)"
               setStage "$situ" "no_op 1"
               ;;
  otherPV,DM.MPIO ) #stage is: otherPV  path  VGname
               BMR_otherPV $mm "/dev/mapper/$(devmap_name $mm)"
               setStage "$situ" "multipath 0"
               ;;
  * ) errm " !!! $FUNCNAME: ERROR- undefined case->$situ"
esac 
}
#______________________________________________________________________________________________
function work_back_from_real_dev()
{    local    mm=$1   dirwhole   dev_attr   pnum   basep   tmp1   dasd_bus_id  
     local    discipline   hba   wwpn   lun   path   mpio=no
#Given a physical device's major:minor process it. Haven't coded for extended partitions.
if in_list $mm mm_list ; then
  dbg ";;; $FUNCNAME: Already did $mm - stage=$stage"
  return 0
fi
dirwhole=
for dev_attr in $(find /sys/block -name dev) ; do
  if [ "$(cat $dev_attr)" = "$mm" ]; then
    tmp1=$(dirname $dev_attr)
    cd $tmp1
    if [ -L $tmp1/device ]; then                    #has symlink to its /sys/ device branch?
      dirwhole=$tmp1
      pnum=0                                          #partition number; 0 flags whole device
    else                          #I'm a primary partition: go up one level to my parent dir.
      addto_list $mm mm_list
      basep=$(basename $tmp1)                                 #remember the partition's dir 
      cd .. ; tmp1=$(pwd)
      if [ -L $tmp1/device ]; then 
        dirwhole=$tmp1
        pnum=${basep#$(basename $dirwhole)}               #!!- expecting pnum is a number
      else
        errm "!!! $FUNCNAME: ERROR- My coding couldn't handle $mm - stage=$stage"
      fi
    fi
    break          #Stop looking after 1st match
  fi   #if cat dev_attr
done #for dev_attr

if [ -n "$dirwhole" ]; then      
  case $dirwhole in
    /sys/block/dasd* )                                      # S390 or s390x architecture
        tmp1=$(lsdasd | grep "is $(basename $dirwhole) ")
        dasd_bus_id=${tmp1:0:8}
        stage=$stage" dasd "$dasd_bus_id" "$pnum             #append more info
        do_stage $mm 
        if ! in_list $(cat $dirwhole/dev) mm_list ; then     #Do once per whole dasd device
          addto_list $(cat $dirwhole/dev) mm_list
          cd $(readlink -n $dirwhole/device)
          read discipline < ./discipline       #A dasd attribute:  ECKD | FBA | DIAG
          if [ "$discipline" != DIAG ] ; then
            BMR_prep_dasd  $(basename $dirwhole)  $dasd_bus_id  $pnum  $discipline
          else                # We don't use DIAG at my site; shouldn't have this problem.
            errm "!!! $FUNCNAME: ERROR- not coded for dasd DIAG discipline - $dasd_bus_id"
          fi
        fi
        ;;
    /sys/block/sd*   ) 
        cd $(readlink -n $dirwhole/device)
        read hba <  ./hba_id
        read wwpn < ./wwpn
        read lun <  ./fcp_lun
        path=$hba:$wwpn:$lun
        if [ "$(word 1 $stage)" = "multipath" ] ; then
          mpio=yes
          pnum=$(word 2 $stage)                              #0 or 1: whole or partitioned?
        fi
        stage=$stage" sd "$path" "$pnum                      #append more info
        do_stage $mm
        if ! in_list $(cat $dirwhole/dev) mm_list ; then     #Do once per whole lun/path 
          addto_list $(cat $dirwhole/dev) mm_list
          BMR_prep_lun  $(basename $dirwhole)  $hba  $wwpn  $lun  $pnum  $mpio
        fi
        ;;
    * ) errm "!!! $FUNCNAME: ERROR- mm=$mm No code for device $dirwhole"
  esac
else  
  errm "!!! $FUNCNAME: ERROR- Didn't find a /sys/block/ dir for $mm"
fi
}
#______________________________________________________________________________________________
function work_back_from_DM_dev()
{    local    mm=$1  depsList  majmin  _stage 
#Given a DM major:minor, process it and work backwards to process its dependents.
if in_list $mm mm_list ; then
  dbg ";;; $FUNCNAME: Already did $mm - stage=$stage"
  return 0
fi
addto_list $mm mm_list 
do_stage $mm
 
depsList=$(DM_deps_of_dmname $(devmap_name $mm) )               #a list of major:minor words
#note- Deps of an LVM LV are just the PV's the LV is on; not all the PV's of the VG.
_stage=$stage                                             #preserve 
for majmin in $depsList ; do
  if [ "${majmin%:*}" = "$DM_MAJOR" ]; then              #this dependency is also a DM device
    work_back_from_DM_dev $majmin                                   #recursive
  else
    work_back_from_real_dev   $majmin
  fi
  stage=$_stage                                           #restore
done
}
#______________________________________________________________________________________________
function process_recs_of_cmd()
{   local   cmd=$1   stageCmd=$2   record   mm  devfile
#Run cmd, filter out headings and non-/dev/ lines, work backwards from the /dev/ and stageCmd
#to generate all the BMR commands needed to do stageCmd against /dev/ at BMR-time.
while read record ; do
  dbg; dbg "<-> $stageCmd Record=$record"
  set -- $record
  stage=                                  #stage - info about where we are & what we're doing
  #Each case must filter out non /dev/ lines like headings
  case $stageCmd in                                  #massage the fields to a workable format     
    mount ) #Format: path  on  mntpt  type  fstype  (opt,opt,opt)
           if [ "${1:0:5}" = "/dev/" ] ; then
             if [ "${6:0:3}" != "(ro" ]; then                                    # assumed rw
               stage="$stageCmd $1 $3 $5"                           # mount path mntpt fstype
             else
               putA_ A_mount "${pM}#!! scan skipped read-only mount: $@"    #what else to do?
               continue                                             #goto "done" of "while"
             fi
           else #Not a /dev/, just make mntpt if not yet encountered; this is iffy?
             if [ "$5" != "autofs" ] && ! in_list "$3" iffy_list ; then 
               addto_list "$3" iffy_list
               putA_ A_mount "${pD}mkdir -p $BMRmnt$3   #type $5"   #like /sys /proc or ???
             fi  
             continue                                               #goto "done" of "while"
           fi 
           ;;
    mkswap ) #Format: heading line, or, path  type  size  used  priority
            if [ "${1:0:5}" = "/dev/" ] ; then
              stage="$stageCmd $1"                                  # mkswap path
            else continue
            fi
           ;; 
   otherPV ) #Format: path  major:minor uuid  VGname    
            stage="$stageCmd $1 $4"                                # otherPV path VGname
           ;; 
        * ) errm "!!!ERROR $FUNCNAME Case: $stageCmd is not yet coded."
  esac
  
  devfile=$(word 2 $stage)
  #Strategy is disk block devices are either device-mapper or real and DM devices can be
  #traced back to the real devices on which they're built. Generate BMR cmds along the way.
  if [ -b "$devfile" ]; then
    mm=$(lsMaj_min $devfile)
    if [ "${mm%:*}" = "$DM_MAJOR" ]; then
      work_back_from_DM_dev $mm
    else
      work_back_from_real_dev $mm
    fi
  else say "--> $devfile <--  is not a block device"           
  fi
done < <($cmd)         
}
#______________________________________________________________________________________________
function otherPV()
{    local   r
#Want to identify and process any LVM PV's and VG's not yet encountered. 
for r in $(seq 0 $((${#mmpvscan[@]} - 1)) ) ; do
  echo ${mmpvscan[$r]}
done
}
#______________________________________________________________________________________________
function BMR_disk_activation_seq()
{    local   dev_dir   hba   wwpn   lun   dir   discipline   scsiid 
#I adapted this code from s390/s390x SLES 9 mkinitrd.  As it says in mkinitrd: "we need to
#activate all disks in the same order as found in the running system to get the same behaviour
#during booting." The order we do it here is the order mkinitrd will see when the rescuer runs 
#mkinitrd and zipl to make the restored system bootable. Plus+ do deferred MPIO partitioning.

for dev_dir in /sys/class/scsi_device/*; do
  if [ -d "$dev_dir" ] && [ -e "$dev_dir/device" ]; then
    cd $dev_dir
    cd $(readlink device)
    if [ -r ./hba_id ]; then
      read hba  < ./hba_id
      read wwpn < ./wwpn
      read lun  < ./fcp_lun
      putA_ A_actLUN "${pM}#"  
      ##putA_ A_actLUN "echo $wwpn > /sys/bus/ccw/drivers/zfcp/$hba/port_add"    
      ##putA_ A_actLUN  "echo $lun > /sys/bus/ccw/drivers/zfcp/$hba/$wwpn/unit_add"    
      putA_ A_actLUN  "${pM}/sbin/zfcp_disk_configure $hba $wwpn $lun 1"

      scsiid=$(scsi_id -gus /block/$(basename $(readlink block) ) )
      if in_list $scsiid MPIOptbl_list ; then
        #For a partitioned MPIO lun: Restore ptable once on 1st path avail at BMR-time
        putA_ A_actLUN "${pD}Tptbl $hba:$wwpn:$lun $(fBMR ptbl $scsiid)"
      fi

      if ! in_list $hba mkinitrd_hba_list ; then                           #once per hba
        addto_list $hba mkinitrd_hba_list
        ##putA_ A_actHBA "chccwdev --online $hba"
        putA_ A_actHBA "/sbin/zfcp_host_configure $hba 1"
      fi
    fi
  fi
done
for dir in /sys/block/dasd? /sys/block/dasd?? /sys/block/dasd???; do
  if [ -d "$dir" ] && [ -d ${dir}/device ]; then
    pushd $dir > /dev/null
      cd $(readlink device)
      if [ -r ./discipline ]; then
        read type < ./discipline
        if [ "$discipline" = DIAG ] ; then  #what to do w/CMS-formatted dasd using DIAG driver
          errm "$FUNCNAME ERROR- not coded for DIAG discipline for $(basename $PWD)"
        fi
        putA_ A_actDASD "chccwdev --online $(basename $PWD)"
      fi
    popd > /dev/null
  fi
done
}
#______________________________________________________________________________________________
function build_devmapname()
{    local  r   IFSbk
#This builds array devmapname for our internal replacement for device-mapper's devmap_name
#command which doesn't work in device-mapper 1.01.01-1.2 and it's on many of our servers.
#If you have a later fixed level of device-mapper you can just use command devmap_name.
#We need devmap_name major:minor to return the name of the DM device.
# dmsetup info -c --noheadings     (this variation of dmsetup info worked on 1.01.01-1.2)
#  1     2  3     we need only name major minor
#lun01r:253:2:L--w:2:1:2:3600507680181807628000000000002aa
IFSbk=$IFS ; IFS=$'\n'                          #Use newline to store 1 line per array element
   devmapname=( $(dmsetup info -c --noheadings) )
IFS=$IFSbk
if [ "${devmapname:0:16}" = "No devices found" ] ; then
  unset devmapname[@]
  return 0
fi
for r in $(seq 0 $((${#devmapname[@]} - 1)) ) ; do
  IFSbk=$IFS ; IFS=":"
    set -- ${devmapname[$r]}
  IFS=$IFSbk
  #Rebuild as:  m:m  name
  devmapname[$r]=$2:$3" "$1
done
}
#______________________________________________________________________________________________
function build_mmpvscan()
{    local  r   IFSbk   VGname
#This builds array mmpvscan combining items from pvscan -u with their major:minor.
#Sample of pvscan -u output without the trailing summary line.
#  1         2              3   4        5       6    7
#  PV /dev/mapper/lun01o1 with UUID <uuid-value> VG sanvg1   lvm2 [29.94 GB / 0    free]
#  PV /dev/dasde1         with UUID <uuid-value> VG sanvg1   lvm2 [0    / 0    free]
#  PV /dev/dasde2         with UUID <uuid-value>             lvm2 [16.73 MB]
#Note that a PV might not be assigned to a VG yet.
IFSbk=$IFS ;  IFS=$'\n'       #Use newline to store 1 line per array element
mmpvscan=( $(pvscan -u | head -n-1) )  #1 line per element; drop the trailing summary line.
IFS=$IFSbk
for r in $(seq 0 $((${#mmpvscan[@]} - 1)) ) ; do
  set -- ${mmpvscan[$r]}
  if [ "$6" = "VG" ] ; then  VGname=$7
  else VGname=$NO_VG
  fi
  #Rebuild as:  /dev/name    m:m        uuid   VGname
  mmpvscan[$r]=$2" "$(lsMaj_min $2)" "$5" "$VGname
done
}
#______________________________________________________________________________________________
function do_scan_mode()
{
#scan the system to discover which and how disk devices are used, generating the BMRcmds file. 
say "*** Doing- $SCRIPT $mode         $(date)"
say "    BMRdir=$BMRdir    File BMRcmds=$fBMRcmds"
say
putA_ A_info "# BMR commands generated $(date) on the server described below:"
putA_ A_info "# $(uname -snr)"
putA_ A_info "# z/VM userid=$(cat /proc/sysinfo | grep 'VM00 Name' | awk '{print $3}')"

#If not using device-mapper then DM_MAJOR will be null, that's OK.
DM_MAJOR=$(grep -A 999 "Block devices:" /proc/devices|grep -w "device-mapper"|awk '{print $1}')

if [ -n "$DM_MAJOR" ] ; then              #build some device_mapper related variables/arrays
  putA_ A_info "# rpm -q said: $(rpm -q device-mapper)"
  #Do pvscan to make LVM update its cache so we minimize the "open failed: No such
  # device or address" messages from subsequent LVM commands.
  #It's OK if LVM's not used, some LVM-related arrays and variables will be empty.
  build_mmpvscan > /dev/null
  build_devmapname

  LVminor_list=$(echo -n $(lvs -o kernel_minor --noheading) )         #minor's of LVM LV's
  IFSbk=$IFS; IFS=$'\n'                 #set IFS to newline; want 1 line per array element
    lvsRecs=( $(lvs --noheading) )                                    #Used by VGname()
  IFS=$IFSbk
fi
#Use various commands to display which and how disk objects are used and generate BMRcmds
#to make empty disks into those disk objects:
process_recs_of_cmd mount mount
process_recs_of_cmd "swapon -s" mkswap 
process_recs_of_cmd otherPV  otherPV                #otherPV is a function in this script

if [ ${#A_actLVM[*]} -gt 0 ] ; then
  putA_ A_info "# rpm -q said: $(rpm -q lvm2)"
fi
if [ ${#A_actMPIO[*]} -gt 0 ] ; then
  putA_ A_info "# rpm -q said: $(rpm -q multipath-tools)"
fi
putA_ A_info "#"
putA_ A_info "# '$SCRIPT scan' mode generated this file for its other modes to read."
putA_ A_info "# Various $SCRIPT modes read this file and select records based on their"
putA_ A_info "# column 1-2 prefix and do the Bash command that starts in record column 3."
putA_ A_info "# The command might call internal functions in $SCRIPT."
putA_ A_info "# You can use option -t to safely check which records a mode will select."
putA_ A_info "#"
putA_ A_info "# WARNING!! Modes 'diskinit' and 'restore' will overwrite your disks!"  
putA_ A_info "# Mode 'diskinit' does records having prefixes '$pM' or '$pD'"
putA_ A_info "# Mode 'mount' does only the prefix '$pM' records."
putA_ A_info "# Mode 'umount' does only the prefix '$pU' records."
putA_ A_info "# Mode 'restore' does only the prefix '$pR' records."

BMR_disk_activation_seq                               
if [ -n "$MPIOptbl_list" ] ; then            #Cleanup the MPIOptbl_list- files in BMRdir
  putA_ A_actLUN "${pD}rm MPIOptbl_list-*    # Tptbl() made these empty files."
fi

##BMR_mknodes                                                         #future feature?
BMR_drivers
BMR_restore    #Very site-specific: generate commands to restore disks from backup media.
BMR_umount
write_file_BMRcmds
if ! cmp --quiet $pathSCRIPT  $BMRdir/$SCRIPT  ; then
  say "Saving a copy of $SCRIPT in $BMRdir"
  cp -a $pathSCRIPT $BMRdir/
fi
say
say "*** Done- $SCRIPT $mode         $(date)"
}
#______________________________________________________________________________________________
function check_config_files()
{   local   BMRfile=$1   sysfile=$2   choice
#For modes mount and diskinit, compare rescue system's config file to a config file in BMRdir
#and get permission to replace one that differs.  Our Linux rescue system doesn't use LVM or
#multipath-tools, no conflicts from adding these config files.
if [ ! -e "$BMRfile" ] ; then 
  say "Didn't find file $BMRfile, continuing..."
  return 0
elif [ ! -e "$sysfile" ] ; then 
  say "$sysfile doesn't exist. Copying $BMRfile to $sysfile"
  cp $BMRfile $sysfile
  return 
fi
if cmp --quiet "$sysfile" "$BMRfile" ; then
  say "Existing file $sysfile compared  =  to $BMRfile"
  return 0
fi
# The files differ, ask what to do: 
  cat - <<EOFASK   ##########
--------------------------------------------------------------------
Your system's existing $sysfile differs from $BMRfile. 
What should I do? Choose an action number: 

1) Replace $sysfile with $BMRfile
2) Don't replace the file, but continue mode "$mode"
3) Quit now. Abort mode "$mode"

EOFASK
##############
echo -n "Enter action number-> "
read choice
if [ "$choice" = 3 ] ; then exit 99
elif [ "$choice" = 2 ] ; then 
  say " ** Did not replace $sysfile   Continuing to do mode $mode"
  return 0
elif [ "$choice" = 1 ] ; then
  mv $sysfile $sysfile.renamed-at-BMR
  cp $BMRfile $sysfile
  say " ** Renamed old file to $sysfile.renamed-at-BMR and replaced it."
  return 0
else
  say " ??? Unexpected choice $choice  I quit."
  exit 100
fi
exit 101
}
#______________________________________________________________________________________________
function atBMR()
{    local   devaddr=$1   pnum=$2   devname  dev_dir  hba  wwpn  lun
#The BMRcmds file can have records that call this function to resolve a device name at
#BMR-time. For example /dev/sda on the scanned system might be /dev/sdc at BMR-time.
#Called from BMRcmds records for modes mount and diskinit.
#The 2 parms are either: dasd_busid partition#, or lun_path partition#
#A dasd busid is 8 chars, 0.s.nnnn
#A lun_path is busid:wwpn:lun
if [ "$devaddr" = "" ] ; then
  echo "ERROR in $FUNCNAME: no parameters were given." >&2
  echo -n "//atBMR-NO-PARMS//"
  return
elif [ "${#devaddr}" = 8 ] ; then
  devname=$(lsdasd | grep "$devaddr" | awk '{print $7}')       #field 7 if ECKD dasd
  if [ "$devname" = "is" ] ; then                              #field 8 for FBA dasd
    devname=$(lsdasd | grep "$devaddr" | awk '{print $8}')  
  fi
  if [ -z "$devname" ]; then devname="<?$devaddr?>"       
  fi
  if [ "$pnum" != 0 ] ; then devname=$devname$pnum
  fi
  echo -n $devname
else  # Handle hba:wwpn:lun     Find the /dev/sd??? of device (hba:wwpn:lun)
  for dev_dir in /sys/class/scsi_device/*; do
    if [ -d "$dev_dir" ] && [ -e "$dev_dir/device" ]; then
      cd $dev_dir
      cd $(readlink device)
      if [ -r ./hba_id ]; then
        read hba  < ./hba_id
        read wwpn < ./wwpn
        read lun  < ./fcp_lun
        if [ "$hba:$wwpn:$lun" = "$devaddr" ] ; then
          devname=$(basename $(readlink block) )  #should have a symlink to its block xface 
          if [ "$pnum" != 0 ] ; then devname=$devname$pnum      #like sda1 or sdzz2
          fi 
          echo -n $devname
          return  
        fi
      fi
    fi
  done
  echo -n "<?$devaddr?>"
fi 
}  
#______________________________________________________________________________________________
function Tptbl()
{    local   lunpath=$1   fptbl=$2   devname   scsiid   
#This routine is called at BMR-time after an activate LUN record in BMRcmds file. It
#restores the lun's partition table once only on 1st available path of a multipath lun.
#
#Our BMRcmds record is eval-ed: we can't change global variables of the caller; so to
#remember we've already partitoned a lun we create an empty MPIOptbl_list-scsiid file. 
if [ "$OPTtest" = yes ] ; then return                                            #protection
fi
devname=$(atBMR $lunpath 0)
if [ -d /sys/block/$devname ]; then                                          #an active path? 
  scsiid=$(scsi_id -gus /block/$devname)
  if [ -e $BMRdir/MPIOptbl_list-$scsiid ] ; then return          #already restored its ptable
  fi
  say "  Partitioning with: sfdisk --force -uS /dev/$devname < $fptbl"
  sfdisk --force -uS /dev/$devname < $fptbl                          #restore partition table
  touch $BMRdir/MPIOptbl_list-$scsiid                                            #remember me
fi
}
#______________________________________________________________________________________________
function do_a_BMR_mode()
{    local   record   P1  P2  total=0  count=0  exitstats=0  xcode
say "*** Doing- $SCRIPT $mode      option test(-t)=$OPTtest     $(date)"
say "    BMRdir=$BMRdir    File BMRcmds=$fBMRcmds"
say
if [ "$mode" = diskinit ] ; then
   P1=$pM   ;  P2=$pD                                           #diskinit uses 2 prefixes
elif [ "$mode" = mount ] ; then
   P1=$pM   ;  P2=$P1            #mode mount is a harmless subset of diskinit prefixes.
elif [ "$mode" = umount ] ; then
   P1=$pU   ;  P2=$P1
elif [ "$mode" = restore ] ; then
   P1=$pR   ;  P2=$P1
fi

if [ "$OPTtest" = "no" ] ; then
  say "--> ATTENTION <-- Are you sure you want to do mode $mode?"
  echo -n "   Reply  yes  to do it; anything else to quit now: "
  read ans
  if [ "$ans" != "yes" ] ; then exit 13
  fi
  if [ "$mode" = diskinit -o "$mode" = mount ] ; then
    check_config_files  $BMRdir/$(basename $fCFGmpio) $fCFGmpio
    check_config_files  $BMRdir/$(basename $fCFGlvm) $fCFGlvm
  fi
  cd $BMRdir                     #for relative path files in BMRcmds to be found
fi
while read record ; do
  let total=total+1 
  if [ "${record:0:2}" = "$P1" -o "${record:0:2}" = "$P2" ] ; then
    say "${record}"          
    let count=count+1     
    if [ "$OPTtest" = "no" ] ; then
      eval "${record:2}"                      #do the command, omit the prefix
      xcode=$?
      if [ $xcode != 0 ] ; then
        let exitstats=exitstats+1                
        say "** #$exitstats ** Non-zero status code=$xcode from $(word 1 ${record:2})"
        say
      fi
    fi 
  fi
done < $fBMRcmds
say "     Selected $count of $total records.  $exitstats non-zero status codes"
say "*** Done- $SCRIPT $mode      option test(-t)=$OPTtest     $(date)"
}
#______________________________________________________________________________________________
SCRIPT=$(basename $0)
if [ ${0:0:1} = "." ] ; then pathSCRIPT=$(pwd)${0#.}    #want absolute path to this script
else pathSCRIPT=$0
fi

mode_list="scan diskinit restore mount umount"          #valid modes

declare -a  A_info  A_drivers   A_actDASD  A_dasdfmt  A_actHBA  A_actLUN   A_preFmtPart 
declare -a  A_partition   A_actMPIO  A_pvcreate  A_vgcfgrest  A_actLVM  A_mkfs 
declare -a  A_mount  A_mkswap   A_umount   A_restore
declare -a  LVminor_list  lvsRecs   mmpvscan  devmapname 
 
pD="D "       #Prefixes diskinit's command recs that write to your disks, changing them.
pM="M "       #Prefixes diskinit/mount recs that activate but don't write to your disks.
pU="U "       #Prefixes BMR command lines of mode umount.
pR="R "       #Prefixes BMR command lines of mode restore.
   
BMRdir=/etc/BMR                                     #Default 
fBMRcmds=BMRcmds                                    #Default basename 
BMRmnt=/mnt/a                                    #at BMR-time, mount the new filesystems here.
OPTdebug=no
OPTtest=no
mode=

fCFGlvm=/etc/lvm/lvm.conf
fCFGmpio=/etc/multipath.conf

devmapname=                            #array used by internal function devmap_name() 
mmpvscan=                              #array built from pvscan -u with major:minor's
lvsRecs=
stage=
DM_MAJOR=                              #device-mapper driver major number 
mm_list=                               #major:minor list
mkinitrd_hba_list=                     #hba's activated by BMR_disk_activation_seq()
MPIOptbl_list=                         #scsiid's of partitioned multipath luns
scsiid_list=
VG_list=                               #LVM Volgroup's we've handled
NO_VG="//"                             #for a PV that's in no VG
idleVG_list=                           #"unused" LVM Volgroups
PV_list=                               #LVM PV major:minor's we've pvcreate-d
iffy_list=                             # /sys /proc /dev/pts ..., some are iffy? 
umount_list=                           #list of mount points, LIFO

get_check_parms $@

if [ "$mode" = scan ] ; then
  do_scan_mode                            #scan mode writes the BMRcmds file
else 
  do_a_BMR_mode                           #these modes read and do the BMRcmds file
fi

# ----------- Notes ------
# (-) doesn't handle LABEL= and UUID= mounts. Future feature?
# (-) Needs to filter out LVM snapshots, dcss's, cdrom, tape, etc
# (-) !! We assume disks/lvm/multipaths configuration is static while script runs.
# (-) FIX: get_check_parms fails to catch missing dir or file; try -f -d scan
