Cover V12, i12
dec2003.tar

Listing 1 backup.parent script

#!/bin/ksh 

# Version 1.1.0
#
# 01/12/03 RCM    Tivoli Storage Manager Backup for an Oracle database whose
#        files reside in a UFS filesystem
#
#        This "job" is made of two seperate scripts.  The "parent"
#        that performs management and control operations (ie: get
#        oracle file lists, db startup/shutdown, etc.) and the
#        "child" that gets tasked to perform the "dsmc archive"
#        tasks themselves.
#
#        This script will backup an Oracle database using a TSM
#        server. The filenames are retreived from the Oracle
#        database itself at runtime. A set of child processes
#        perform the actual "dsmc archive" tasks to allow multi-
#        streaming operations.
#
#        example syntax to run:
#
#        ./backup.parent database_sid HOT 2 ARC2WEEKS
#
#    Notes:    This script utilizes Solaris filesystem snapshots when
#        backing up the files. thus minimizing the time that the
#        database is down (for COLD) or in backup mode (for HOT)
#        The database is taken down (altered, etc.) and when
#        it is ready to be backed up, a "snapshot" of the filesystem
#        is taken (see fssnap command.) Once the snapshot is done
#        the database can be restarted (etc.)  A seperate filesystem
#        is used to store the snapshot "backing store" files,
#        and the snapshot (ie: /dev/fssnap/3) is mounted (in our case
#        in the /tmp filesystem.)  When the "dsmc archive" task runs
#        it is pointed at the snapshot.  Provided that the amount of
#        changes to the snapshot'ed filesystems do not exceed the
#        fssnap filesystem capacity the archive task should run
#        normally. In the event that the snapshot filesystem is
#        overloaded the snapshot being written to fails, and is deleted. 
#        Please refer to the fssnap and fssnap_ufs manpages.
#
#        The default for a backup is COLD - if you do not want the 
#        database shutdown make sure you specify HOT.
#
#                    -----
#        The default snapshot filesystem will need to be modified
#        see the "snapshot_fs" environment variable below.  You will
#        get a warning message if it is not mounted.
#                    -----
#
#        Since we are using filesystem snapshots and the /tmp filesystem
#        becomes the mount point "holder", all the files (with the
#        exception of the "archive logs" in the case of a HOT backup)
#        will have been backed up from the /tmp filesystem from the 
#        viewpoint of TSM.  Therefore you will often need to prefix
#        pathnames with /tmp to get the proper pathname.
#        
#        A presumption of the "oracle" user environment exists.
#        The user is "profiled/setup" by locally used routines, that
#        may not exist in your environment. The ORACLE_SID, ORACLE_HOME,
#        and PATH (appending $ORACLE_HOME/bin) environment variables 
#        must be properly defined for the SQL*Plus commands to work
#        correctly, if they are not defined in the oracle userid profile
#        you can use the ${prep_script} variable to execute the commands
#        or "source them in".  See your DBA if you have questions.
#
#        The use of "here is" documents (ie: some_cmd << EOF) is 
#        used to instream the connections to the database to perform
#        queries etc. During the processing of this script several
#        times oracle commands and info are handled by using the
#        "su - oracle" command, feeding it the commands (typically
#        "newdb", and "sqlplus" w/sql cmds) this way, often nesting
#        some of them. Normally one can place a hyphen after the 
#        redirection characters to strip out leading tabs and allow
#        indentation for readability, I have deliberately not done so
#        for the benifit of those that would end up copying the info
#        and converting the tabs to blanks.
#
#        the scripts perform the following operations:
#
#        1) get the names of the files needing backup from the
#           owning database.
#        2) separate the files by filesystem into discrete "control"
#           files (to be passed to the "archive" child processes.)
#        3) create a "queue" of tasks for background (child) processes.
#        4) ready the database for backups.
#        5) snapshot the filesystems that hold backup targets.
#        6) ready the database for users.
#        7) run archive task for each filesystem.
#            a) run up to $N tasks so as to utilize all cpus.
#            b) cleanup/delete snapshots
#        8) capture logs
#
#    Note2:
#
#    1) Snapshots (and their backing store files) are not deleted if 
#       archive task fails - and must be manually deleted before the
#       snapshot file system becomes full. (see Tip #1 below)
#
#    2) A "resourceutilization nn" staement may be required in the 
#       dsm.sys (TSM system options) file, and may also require a 
#       change to the TSM server (for the target node) for more
#       "direct tape sessions" if you go direct to tape for large files
#       if needed use "MaxNumMp" setting in the server config.
#
#
# -----------------------------------------------------------------------
#
#    Archive maintenence:
#
#     To delete old copies use the "dsmc delete arch" command
#    passing it each mountpoint that was backed up and the description
#    field corresponding to the level at which you want the deletion 
#    to occur (ie: database.day.time.process - note the time and pid
#    should be unique.)
#    ie:
#        dsmc delete archive /tmp/shark01/ -desc="testdb.d030120.*"
#
#    meaning delete all files that were archived from the /tmp/shark01
#    mount point on 01/20/03.  Note that you will have to do this for
#    each mount point 

# Tips/Notes/Warnings:
#
#    1) If the backup of a file fails during the running of the script
#       you can attempt another archive of the file provided the snapshot
#       still exists. (The child processes attempt to check the return
#       codes from dsmc and not delete the snapshot if the archive 
#       fails.)  Use the "dsmc archive" command manually to finish the
#       failed backup. (see below)
#
#        1) find log of backup
#        2) grep for "Unsuccessful"
#        3) make sure the snapshot is still there and mounted
#        4) do a manual archive(s)
#        5) remove the backing store file
#        6) unmount the snapshot
#        7) delete the snapshot
#
#    find . -name "backup.parent.*.log1" -mtime -1 -print
#    ./backup.parent.criisdb.COLD.d030127.t102713.p8242.log1
#        .
#        .
#        .
#    ./backup.parent.criisdb.COLD.d030128.t040000.p17556.log1
#
#    grep -i unsuccessful ./backup.parent.criisdb.COLD.d030128.t040000.p17556.log1
#    Normal File-->    20,971,536,384 /tmp/shark04/oradata/data_l05.dbf  ** Unsuccessful **
#
#    fssnap -i -o blockdevname | awk -F: '{print "df -k",$2}' | sh
#    Filesystem            kbytes    used   avail capacity  Mounted on
#    /dev/fssnap/2        64225208 52550912 11674296    82%    /tmp/shark04
#
#    dsmc archive /tmp/shark04/oradata/data_l05.dbf -filesonly \
#        -desc="criisdb.COLD.d030128.t040000.p17556"
#
#    fssnap -i -o backing-store | awk -F: '{print "rm",$2}'
#    rm  /fssnap/backup.parent.d030128.t040000.p17556._shark04.bs
#
#    umount /dev/fssnap/2
#
#    fssnap -d /dev/fssnap/2
#    Deleted snapshot 2.
#
#    2) To get a list of files that have been archived (or backed up)
#       may require them to be listed according to the mount point on
#       which they were backed up.  This may be difficult for the
#       snapshot'ed filesystems as they are transitory.  Try doing a
#       "dsmc query filespace" and look for objects that were backed
#       up in the /tmp/* filesystem names:
#
#     dsmc query filespace | grep "/tmp/"
#        ...
#     37   00/00/00   00:00:00   UFS     /tmp/shark01
#        ...
#
#    dsmc query filespace | grep "/tmp/" | awk '{print "dsmc query archive \"" \
#        $5 "/*\" -desc=\"criisdb.COLD.d030128.*\""}'
#    dsmc query archive "/tmp/shark01/*" -desc="criisdb.COLD.d030128.*"
#    dsmc query archive "/tmp/shark02/*" -desc="criisdb.COLD.d030128.*"
#
#    3) To restore a file, will require that you do a "dsmc RETrieve"
#       of the files as they were backed up (ie: /tmp/shark01/...)
#       using the rename option to put them in the proper location.
#       ie:
#
#    dsmc retrieve  -desc="criisdb.COLD.d030128.*" \
#        /tmp/shark04/oradata/temp02.dbf /shark04/oradata/temp02.dbf
#
#      Insure when you do this that there is ONLY ONE file that will be
#      "retrieved" by doing a query first...

#------------------------------------------------------------------------
# Maintenance:
#------------------------------------------------------------------------
#
# 02/11/03 RCM    Added dsmc "mgmtclas" option for specifing backup retention 
#                 You can determine which classes are available for use by
#                 doing a "dsmc query mgmtclas" command.
#
# 04/14/03 RCM    Utilizing "sudo" to run fssnap, mount, and umount commands.
#        Normally these require root authority, and setting the uid
#        bit on in the permissions is a) not good, and b) seems to 
#        create a problem by which $0 becomes the shell name (ie: ksh)
#        While not a crisis, it is not what was desired, therefore
#        we will define $fssnap, $mount, $umount variables that have 
#        the appropriate values. The /etc/sudoers file will need the
#        following (uncommented) lines:
#
# maestro pill=NOPASSWD: /usr/sbin/fssnap, /usr/sbin/mount, /usr/sbin/umount
# maestro pill=NOPASSWD: /usr/bin/su - oracle
# maestro pill=NOPASSWD: /usr/bin/rm /tmp/backup*, /usr/bin/mv /tmp/backup*
# maestro pill=NOPASSWD: /usr/bin/rm /fssnap/backup*
# maestro pill=NOPASSWD: /usr/bin/dsmc query, /usr/bin/dsmc archive
#

#----------------- options passed 

DB=$1
hot_or_cold=$2
children=$3
retention_period=$4

#----------------- verify options and/or set defaults
if [ -z "$DB" ]; then 
    echo "No database specified"
    echo "Syntax is:"
    echo
    echo $0 "database_sid [ COLD | HOT ] [ num_streams ] [ retention_period ]"
    echo
    exit 4
fi

# type of backup COLD or HOT
hot_or_cold=${hot_or_cold:="COLD"}
if [[ $hot_or_cold != "COLD" && $hot_or_cold != "HOT" ]]; then
    echo "Error \"$hot_or_cold\" not valid"
    echo "specify COLD or HOT"
    exit 4
fi

# default number of tasks, when not specified. psrinfo and word counting the
# number of lines returned is used to determine the number of cpus. if this 
# does not work specify the default manually.
#
children=${children:=$(psrinfo | wc -l)}

# MGMTCLAS option for retention information, you can determine available
# classes by doing a "dsmc query mgmtclas" command
retention_period=${retention_period:="ARC5WEEKS"}

export DSM_DIR=/opt/tivoli/tsm/client/ba/bin
export DSM_CONFIG=${DSM_DIR}/dsm.opt

export PATH=/usr/sbin:${PATH}:${DSM_DIR}

#----------------- functions  
function keep_going {
# request permission from operator to continue
echo "reply \"yes\" to continue"
read RESP
if [ "${RESP}" != "yes" ]; then exit; fi
}

#----------------- variables (filenames, etc.)  
# the child script that performs the actual "dsmc archive" task
# (this script should reside in the same directory as this parent script)
child=$(dirname $0)/backup.child

# a set of unique names for temporary files is generated by using the /tmp
# filesystem, name of the script, date & time, and finally process id (pid)

timestamp=d$(date +%y%m%d).t$(date +%H%M%S)
tmpname=/tmp/$(basename $0).$DB.$hot_or_cold.$timestamp.p$$

child_prefix=/tmp/$(basename $child).$DB.$hot_or_cold.$timestamp
# (the child's pid will be appended in the $child script)

# description is the "description" option (field) that is passed to the
# tsm server when the file is archived.  This field can be used as a 
# "keyword" field when listing and/or deleting old backups.
description=$DB.$hot_or_cold.$timestamp.p$$

# Files used by the script
tmpfile1=$tmpname.tmpfile1
tmpfile2=$tmpname.tmpfile2
tmpfile3=$tmpname.tmpfile3
tmpfile4=$tmpname.tmpfile4
log1=$tmpname.log1
log2=$tmpname.log2
filesystems=$tmpname.filesystems
begin_bkup=$tmpname.begin_bkup
end_bkup=$tmpname.end_bkup

# log directory, if it doesn't exist create it
logdir=$(dirname $0)/../logs
if [ ! -d ${logdir} ]; then
    mkdir ${logdir}
fi

# 04/14/03 RCM    Command variables, used for using "sudo" with the following
#        commands: (sudo,) fssnap, mount, umount, su - export them so
#        that child processes, will see them properly (su need not)
export sudo="/usr/local/bin/sudo "
if [ ! -x ${sudo} ]; then
    export sudo=""
fi
export fssnap="${sudo}/usr/sbin/fssnap"
export mount="${sudo}/usr/sbin/mount"
export umount="${sudo}/usr/sbin/umount"
su="${sudo}/usr/bin/su"
rm="${sudo}/usr/bin/rm"
mv="${sudo}/usr/bin/mv"
export dsmc="${sudo}/usr/bin/dsmc"

# Oracle admin id and prep script (profile the environment)
# Note that you will have to decide which approach for profiling the oracle
#      userid works best for your environment.  You may decide to imbed the
#      commands for exporting the database ORACLE_SID, ORACLE_HOME, and PATH
#      environment variables in a .profile, .khrc, etc. file or "source" them
#      in via the prep script if you have multiple databases. (Our "newdb"
#      profiling script/function is much more complex than what is needed for
#      doing just a backup.
oracle="oracle"
prep_script="newdb $DB"

# Make sure we can talk to the server.
${dsmc} query session >> $log1
if [ $? -ne 0 ]; then
    logger -ip local1.notice $0": Error, Can not establish session with server properly"
    logger -ip local1.notice $0": exiting"

    echo "Error, Can not establish session with server properly"
    exit
fi

# Check to make sure we can use the management class specified
${dsmc} query mgmtclas | grep ${retention_period}
rc=$?
if [ $rc -ne 0 ]; then
    logger  -ip local1.notice $0": Error, Storage MGMTCLAS ${retention_period} invalid"
    logger  -ip local1.notice $0": exiting."

    echo "Error, Storage MGMTCLAS ${retention_period} invalid"
    exit
fi

# filesystem where the filesystems of the target files to be backed up, can
# be snapshot'ed (ie: the "backing store" filesystem)
snapshot_fs=/fssnap
snapshot_fs_line=$(expr ${LINENO} - 1)
# Check to make sure that our "snapshot_fs" is mounted.
host_fs=$(df ${snapshot_fs} | cut -f1 -d'(' | tr -d ' ')
rc=$?
if [[ $rc -ne 0 || ${host_fs} != ${snapshot_fs} ]]; then
    echo ""
    echo "Error, the snapshot filesys (${snapshot_fs}) is not mounted."
    echo ""
    echo "       insure that this is the correct file system, and it is mounted"
    echo "       or change the "snapshot_fs" environment variable in the $0"
    echo "       script at line number ${snapshot_fs_line}"
    echo ""
    logger  -ip local1.notice $0": The snapshot filesys: ${snapshot_fs} not mounted."
    logger  -ip local1.notice $0": backup canceled."
    exit
fi

# since the backing store files are stored in a different filesystem,
# they need a different naming convention.
bstmpname=$snapshot_fs/$(basename $0).$timestamp.p$$

# we export these so that awk process (below) can see it.
export tmpfile3 tmpfile4

export CTLFILE_BKUP=$tmpname.ctlfile_bkup
export CTLFILE_BKUP_NAME=\'$CTLFILE_BKUP\'

#----------------- main routines  

logger  -ip local1.notice $0": Oracle database ($DB) backup started"

# "View" names that we will use to gather info from/about the database
# We will need the datafiles for starters, and depending on the type
# of backup (cold/hot) we will need the controlfiles, logfiles, and
# archive logs.

# Note: on processing/passing view names...
# Insure that you escape the dollar sign that oracle needs for the "sysadmin"
# views, by imeadiately prefixing it with a back slash, or the shell will
# think that it is a variable to process. 

CONTROLFILE='v\$controlfile'
LOGFILE='v\$logfile'
DATAFILE='v\$datafile'
TEMPFILE='v\$tempfile'
ARCHIVED_LOG='v\$archived_log'

# To obtain the "critical" database files, we run SQL*Plus and run selects
# to query the database system views (or in the case of the datafiles, at 
# the request of one of our DBAs the sys.dba_data_files table... - feel free
# to set it back to the view.)

# the select statements format the output for later use by using colons for
# field delimiters, and other text for describing the record types.
# the format is:
#
# column
#   1    filetype identifier (ie: CONTROLFILE, TABLESPACE, etc.)
#   2    "tablespace name" if any (only used for tablespaces)
#   3    os filesystem pathname of the data (our backup target files)

${su} - ${oracle} << EOF >> $log1 2>&1
${prep_script}
sqlplus -SILENT "/ as sysdba" << EON >>  $log2
whenever sqlerror exit 1
set pagesize 0
set heading off
set timing off
spool $tmpfile1
select 'CONTROLFILE::'|| name      from $CONTROLFILE;
select 'TABLESPACE:'||tablespace_name||':'||file_name from sys.dba_data_files;
select 'LOGFILE::'||member    from $LOGFILE;
select 'TEMPFILE::'||NAME      from $TEMPFILE;
spool off
set echo off
spool $tmpfile2
SHOW parameter db_block_size
spool off
exit 0
EON
EOF
if [ $? -ne 0 ]; then
    echo "Error getting tablespace info (from "$DB")"
    logger  -ip local1.notice $0": Error getting tablespace info (from "$DB")"
    exit
fi
cat $log2 >> $log1
${rm} $log2

# read in the datafile (logical) block size
read name datatype blksize < $tmpfile2
echo "db_block_size=" $blksize
${rm} $tmpfile2

# go through the path lists, and generate input for the child
# dsmc backup jobs. the pathname is the last field in our temp file - awk's 
# $NF "variable" ("NF" is the "Number of Fields" on the input record, and
# thus if NF=3 then $NF=$3)
#
# the arguments the child tasks will receive are: filesystem name, pathname
# of a file that contains the pathnames within that filesystem of the backup
# targets, and the fssnap "snapshot" device name (ie: /dev/fssnap/3)
# assigned to the filesystem.

# we seperate the list of pathnames to be backed up into seperate files 
# by filesystem name, then pass those file names to each child process child
# started. thus each child process backs up only those files within it's
# designated filesystem.

# we determine the filesystem name by doing a "df" on each file then write
# the "target" pathname to a file named after the "target filesystem", also
# writing the name of the filesystem and it's associated "file lists"
# pathname to a seperate file.  we sort/de-dup that file, and use it to
# schedule the child processes by filesystem.

# Note: we generate discrete files for each filesystem by "echoing" the 
# the pathname of each target file (those being backed up) to a file whose
# name incorporates the filesystem of the target file. thus each time the
# filesystem changes, the pathname of the target file is written to a "new"
# file.
# 
#           +---------->-------------->----------+
#           |                                     \
#       +---+---+                                  \
#      /         \                                  \ 
# ie:     /shark01/oradata/data_l01.dbf                +     
#                                                    |
#               is written to a file named:      +---+---+
#                                         /         \
#    /tmp/backup.parent.d030114.t093015.p1920._shark01
#
# thus when the above files are created all the backup targets in "/shark01"
# filesystem will be in the "*._shark01" file, the targets for "/shark02"
# will be in "*._shark02", etc.  these files then become the file lists for
# the "dsmc archive" child processes.
#
# the underscores ("_") are used to replace the slashes ("/") in the file
# names to create "valid" pathnames.  ie: /opt/oracle becomes _opt_oracle
# this simplifies things considerably.
#
# since we do this for each pathname to be backed up, we wind up with a 
# "control" file (one that we will use for queuing up multiple streams)
# that contains duplicate entries ie: five files being backed up, existing
# in three different filesystems will produce a five line file, with two 
# lines repeated. (see example below)
#
# /shark01/oradata/init_load_01e.dbf
# /shark01/oradata/init_load_02.dbf
# /shark02/oradata/indx_s01.dbf
# /shark02/oradata/init_load_01g.dbf
# /shark03/oradata/data_l03.dbf
#
# would generated the file:
#
# /shark01 /tmp/backup.parent.d030114.t105520.p1920._shark01
# /shark01 /tmp/backup.parent.d030114.t105520.p1920._shark01
# /shark02 /tmp/backup.parent.d030114.t105520.p1920._shark02
# /shark02 /tmp/backup.parent.d030114.t105520.p1920._shark02
# /shark03 /tmp/backup.parent.d030114.t105520.p1920._shark03
#
# sorting/de-duping produces: 
#
# /shark01 /tmp/backup.parent.d030114.t105520.p1920._shark01
# /shark02 /tmp/backup.parent.d030114.t105520.p1920._shark02
# /shark03 /tmp/backup.parent.d030114.t105520.p1920._shark03
#
# now we can spawn one child "dsmc archive" task for each line in the above
# file, thus giving us the ability to multi-stream the archive tasks.

# we use "grep" to get only the desired data, and pipe it to awk to print
# the last field of the output from the oracle selects for filenames.  (ie:
# this is the pathname of the file to be backed up.)  thus pathlist becomes
# a list of the backup targets.

# Note: LOGFILES (redo/archive) have a different structure.  These files
#       must not be backed up using snapshots, and redo logs should not
#       be backed up during a HOT backup. 
#
#       Also TEMP files should not be backed up either.  Prior to Oracle 8
#       the tempfiles were needed to bring up the database. Also they can
#       be "sparse files" that can cause problems with backup products.

if [ $hot_or_cold == "HOT" ]; then
# 07/30/03 RCM  grep'ing out Only the tablespace files (archive logs and
#               control files must be processed differently.)  Temporary
#        files are not to be backed up in any case, but we keep 
#        the names here for documenting the fact they exist.
        pathlist=$(grep ^"TABLESPACE:" $tmpfile1 | nawk -F: '{print $NF}')
else    pathlist=$(grep -v ^"TEMPFILE:" $tmpfile1 | nawk -F: '{print $NF}')
fi
for path in $pathlist; do
    filesys=$(df $path|cut -f1 -d'('|tr -d ' ')
    outfile=$(echo $filesys|tr / _)
    echo $path >> $tmpname.$outfile
    echo $filesys $tmpname.$outfile >> $tmpfile2
done

# sort and de-dup the temp file
sort -u -o $filesystems $tmpfile2
${rm} $tmpfile2

#keep_going

# for COLD backup:
# connect to the database and shut it down
if [ $hot_or_cold = "COLD" ]; then 
    ${su} - oracle << EOF >> $log1 2>&1
    ${prep_script}
    sqlplus "/ as sysdba" << EON >>  $log2
    whenever sqlerror exit 1
    set timing off
    shutdown immediate
    exit 0
EON
EOF
    if [ $? -ne 0 ]; then
        logger  -ip local1.notice $0": Error shutting down database:" $DB
        echo "Error shutting down database:" $DB
        exit
    else     echo "Database shutdown, Ok"
        logger  -ip local1.notice $0": Database ("$DB") shutdown, Ok"
    fi
    cat $log2 >> $log1
    ${rm} $log2
fi

# for HOT backup:
# put the database in backup mode
if [ $hot_or_cold = "HOT" ]; then
    # set up two files, one with the commands to place the database in
    # backup mode, the other to take it out of backup mode
    # Note that where tablespaces occupy multiple files, the duplicate
    # tablespace names need to be removed.
    grep ^TABLESPACE $tmpfile1 | cut -f2 -d: | sort -u -o $tmpfile2 

    # generate BEGIN BACKUP mode commands
    nawk -F: \
    'BEGIN {
        print "whenever sqlerror exit 1"
        print "set timing off"
        print "spool", ENVIRON["tmpfile3"]
        print "show parameter log_archive_dest"
        print "archive log list"
        print "spool off"
    } {
        print "alter tablespace",$1,"begin backup;"
    } END {
        print "exit 0" 
    }' $tmpfile2 > $begin_bkup

    # generate END BACKUP mode commands
    nawk -F: \
    'BEGIN {
        print "whenever sqlerror exit 1"
        print "set timing off"
    } {
        print "alter tablespace",$1,"end backup;"
    } END {
        print "spool", ENVIRON["tmpfile4"]
        print "archive log list"
        print "spool off"
        print "alter system switch logfile;"
        print "alter database backup controlfile to " ENVIRON["CTLFILE_BKUP_NAME"] ";"
        print "exit 0"
    }' $tmpfile2 > $end_bkup
    ${rm} $tmpfile2

    ${su} - ${oracle} << EOF >> $log1 2>&1
    ${prep_script}
    sqlplus "/ as sysdba" < $begin_bkup >>  $log2
EOF
    if [ $? -ne 0 ]; then
        logger  -ip local1.notice $0": Error putting database in backup mode:" $DB
        echo "Error putting database in backup mode:" $DB
        exit
    else     echo "Database placed in backup mode, Ok"
        logger  -ip local1.notice $0": Database ("$DB") placed in backup mode, Ok"
    fi
    # go through the "spool" file that contains the "archive log list" output
    # and determine the archive directory and the sequence number of the 
    # "oldest" archive log file.
    arch_dir=$(grep -i ^"log_archive_dest " $tmpfile3 | awk '{print $NF}')
    echo "archive dir is:" $arch_dir
    oldest=$(grep -i ^"Oldest " $tmpfile3 | awk '{print $NF}')
    echo "oldest sequence number is:" $oldest
    cat $log2 >> $log1
    ${rm} $log2 $begin_bkup
fi

#keep_going

# read the filesystem names and snapshot each one re-writing the $filesystems
# control file with the snapshot device name included to each record.
#
# Note: If doing a HOT backup, the archive logs can not be snapshot'ed and 
#    then backed up, we will add them to the list below with /dev/null as 
#    the name of the snapshot so the child process handling it knows.

# Use db_block_size for chunksize
chunksize=$(expr $blksize / 1024)k
while read filesystem pathnames
do
    outfile=$(echo $filesystem|tr / _)
    snapshot=$(${fssnap} -F ufs \
        -o backing-store=$bstmpname.$outfile.bs,chunksize=$chunksize $filesystem)
    rc=$?
    if [ $rc -ne 0 ]; then
        logger  -ip local1.notice $0": Error" $rc "doing snapshot for:" $filesystem
        echo "Error" $rc "doing snapshot for:" $filesystem
        # error? don't exit here (database status?) see below
        break
    else     echo $filesystem "snapshot taken, Ok"
        logger  -ip local1.notice $0":" $filesystem "snapshot taken, Ok"
    fi
    echo $filesystem $pathnames $snapshot >> $tmpfile2
done < $filesystems
${rm} $filesystems
${mv} $tmpfile2 $filesystems

if [ $rc -ne 0 ]; then
    # some snapshot problem must occurred, delete the ones that we
    # have created so far and bring the database back up before exit
    # while read filesystem pathnames snapshot
    echo "Snapshot error seems to have occured, deleting snapshots"
    df -k >> $log1
    if [ -f $filesystems ]; then
        while read filesystem pathnames snapshot
        do
            logger  -ip local1.notice $0": Removing snapshot" $snapshot
            echo "Removing snapshot" $snapshot
            if [ ! -z "$snapshot" ]; then
                ${fssnap} -i -o mountpoint,backing-store,backing-store-len \
                    $filesystem >> $log1
# 07/24/03 snapshot device name not working so well under Sol8
#                               $snapshot >> $log1
#            ${fssnap} -d $snapshot    
            ${fssnap} -d $filesystem    
            fi
        done < $filesystems
    fi    
fi

#keep_going

# for COLD backup:
# connect to the database and start it back up
if [ $hot_or_cold = "COLD" ]; then 
    ${su} - ${oracle} << EOF >> $log1 2>&1
    ${prep_script}
    sqlplus "/ as sysdba" << EON >>  $log2
    whenever sqlerror exit 1
    set timing off
    startup 
    exit 0
EON
EOF
    if [ $? -ne 0 ]; then
        logger  -ip local1.notice $0": Error restarting database:" $DB
        echo "Error restarting database:" $DB
        exit
    else     echo "Database restarted - users may go back to work"
        logger  -ip local1.notice $0": Database ("$DB") restarted, Ok"
    fi
    cat $log2 >> $log1
    ${rm} $log2
fi

# for HOT backup:
# take the database out of backup/archivelog mode
if [ $hot_or_cold = "HOT" ]; then 
    ${su} - ${oracle} << EOF >> $log1 2>&1
    ${prep_script}
    sqlplus "/ as sysdba" < $end_bkup >>  $log2
EOF
    if [ $? -ne 0 ]; then
        logger  -ip local1.notice $0": Error taking database out of backup mode:" $DB
        echo "Error taking database out of backup mode:" $DB
        exit
    else     echo "Database taken out of backup mode"
        logger  -ip local1.notice $0": Database ("$DB") taken out of backup mode, Ok"
    fi
    # go through the "spool" file that contains the "archive log list" 
    # output and get the "current" archive log file.

    cat $log2 >> $log1
    ${rm} $log2 $end_bkup

    # aurguably we might want to wait for a moment or two in this step to
    # allow time for the the archive log switch command to take effect...
    # I've not seen any problems but if so add a sleep below - like...
    # sleep 10

    # To backup the archive logs according to the Oracle B&R Handbook
    # you need to get all the archive logs that were in use between the
    # the "backup start" and "backup end" commands. We got the "oldest"
    # earlier when we put the database in backup mode by spooling the 
    # "console log" and then doing an archive log list and grep'ing for 
    # "oldest".  Then (just above) after snapshots were taken, we did 
    # another "archive log list" (before switching the logs, but after
    # taking the database out of backup mode), and got the "current" log.
    # (See example of "list" below.)

    # SQL> archive log list
    # Database log mode              Archive Mode
    # Automatic archival             Enabled
    # Archive destination            /oraarch/testdb/
    # Oldest online log sequence     267
    # Next log sequence to archive   269
    # Current log sequence           269

    # ls -lt /oraarch/testdb/ | head
    # total 566912
    # -rw-rw----   1 oracle   dba     10485248 May 16 12:35 0000000268_0001.arch
    # -rw-rw----   1 oracle   dba     10485248 May 15 22:22 0000000267_0001.arch
    # -rw-rw----   1 oracle   dba     10485248 May 15 07:55 0000000266_0001.arch
    # -rw-rw----   1 oracle   dba     10485248 May 14 17:44 0000000265_0001.arch
    # ...

    # get "Current log sequence"
    current=$(grep -i ^"Current " $tmpfile4 | awk '{print $NF}')
    echo "current achive log is:" $current

    # We have the "oldest" sequence number above, now having obtained the 
    # current sequence number and having done the log switch we can query 
    # the database for all the logs between the two sequence numbers.  These 
    # will be the ones that need to be backed up with the datafiles for the 
    # "recover" command to work properly.

    ${su} - ${oracle} << EOF >> $log1 2>&1
    ${prep_script}
    sqlplus -SILENT "/ as sysdba" << EON >>  $log2
    whenever sqlerror exit 1
    set linesize 100 trimspool on feedback off echo off term off heading off pagesize 0
    spool $tmpname.archive_logs
    select name from $ARCHIVED_LOG where sequence# >= ${oldest} and  sequence# <= ${current};
    spool off
    exit 0
EON
EOF

    filesys=$(df $(dirname $arch_dir)|cut -f1 -d'(')
    echo $filesys $tmpname.archive_logs "/dev/null" >> $filesystems

    # now build the backup cards for the controlfile we told oracle to "backup"
    pathlist=$CTLFILE_BKUP
    filesys=$(df $(dirname $CTLFILE_BKUP)|cut -f1 -d'(')
    for path in $pathlist; do
        echo $path >> $tmpname.control_file
    done
    echo $filesys $tmpname.control_file "/dev/null" >> $filesystems
    
fi

if [ $rc -ne 0 ]; then
    # one of the snapshots attempt above failed, in theory we will have
    # deleted the others, and resumed user operations with the database
    # and we can now exit.
    echo "Snapshot error must have occured, exiting"
    logger  -ip local1.notice $0": job terminating."
    exit
fi

#keep_going

# read the file back in and run a background job for
# each filesystem.
while read filesystem pathnames snapshot
do
    # if there are max background jobs running, sleep
    # until one ends - before starting any more
    while [ $(jobs -p | wc -l) -ge $children ]
    do
        sleep 5
    done
    # start a background task and tell it which
    # files to process. then add it to the pid_history list
    $child $filesystem $pathnames $child_prefix $snapshot $description $retention_period &
    pid_history=$pid_history" "$!

    # wait a few moments for it to start up, then do an iostat to get
    # some numbers regarding the i/o throughput (feel free to change or 
    # remove the echo and iostat commands below.
    sleep 5
    echo "---------------------------------------------------" >> $log1
    iostat -xtnTd 3 3 >> $log1
done < $filesystems
#${rm} $filesystems
# for now save the filesystems list... (We might need it for other stuff)
${mv} $filesystems $logdir/$(basename $filesystems)

#keep_going

# wait for all jobs to end, and then collect all logs
wait
for pid in $pid_history
do
    cat $child_prefix.$pid.log >> $log1
    ${rm} $child_prefix.$pid.log
done
echo "files archived via adsm:"
grep -i normal $log1
echo "\nDescription field used:" $description
echo "\n to list files use the mountpoint name (ie: /tmp/shark01/)"
echo "and the -desc=... option to list them, ie:"
echo "\n"
echo "dsmc query archive \"/tmp/shark01/*\" -desc=\""$description"\"\n"
# finally move the logfile to a permanant location, in this case the
# /usr/local/logs directory for example.
${mv} $log1 $logdir/.
logger  -ip local1.notice $0": Oracle database ("$DB") backup complete"
echo "Parent ending"
rm $tmpname.*
exit