Oracle Database Backup

by Curtis Preston
As published in the July, 1996 Sys Admin.
Copyright 1996 by Miller Freeman, Inc. All rights reserved.
Curtis says "I recommend SysAdmin for every System Administrator"
             Sys Admin Logo

(Get this article & the current version of oraback.sh in one tar file.)

Trying to back up today's databases is like trying to catch a charging rhino with a butterfly net. Real world databases have become screaming behemoths that are very difficult to capture (back up), and standard UNIX utilities are like butterfly nets. If you try to use these utilities for the job, not only will you not capture the data -- you'll break your arm! This article is my attempt to build a better rhino trap. It uses many advanced shell scripting features, so you may find it useful even if you do not have Oracle.

Hot or Cold Backups?

One of the first decisions a database administrator must make is whether to perform "cold" or "hot" backups. A cold backup involves:

  1. Shutting down the instance
  2. Backing up its filesystems or raw devices
  3. Starting up the instance

Here are the steps to perform a hot backup. (Note that a hot backup requires much more knowledge of Oracle's internal structure, so a basic definition of helpful terms is given in the sidebar "Definitions.") First, you must determine the name of each tablespace in the instance and the files that are within it. Then, for each tablespace, you:

  1. Put that tablespace in backup mode;
  2. Backup that tablespace's files or raw partitions (using cp, tar, dd, etc.);
  3. Take that tablespace out of backup mode.

Once all tablespaces are done:

  1. Switch redolog files; and
  2. Backup the control file.

It is much easier to perform cold backups, so why do hot backups at all? Simply stated, they increase your backup window, or the time in which you are allowed to perform backups. Because cold backups require a shutdown of the database, your users will probably allow only a very small backup window. Hot backups allow you to back up without shutting down, remaining virtually invisible to the user, and thereby significantly enlarging your window. I have even performed hot backups of production data during primetime hours. Try that with a cold backup!

Be Careful

If you are going to use hot backups, though, you must follow certain precautions. Failure to do so could mean you would only be able to restore up to your last cold backup -- if you made one.

During a cold backup, all data files have been synchronized. A restore simply requires copying all files into place and restarting the database. However, during a hot backup, each file is backed up at a different time; thus each has a different time stamp. You must have some method of syncing all of those files to the same point during a restore. That is what Oracle's Redo Logs, (also known as archiving, or Archive Logs) are for. Archiving continually records all database changes in what is called the On-line Redo log. Once that log is full, it is copied to an Archived Redo log. (For more information, see the sidebar "Should I Use Archiving?")

When restoring from a hot backup, you first replace the damaged database files with their respective backup copies. (You only need to replace the damaged files.) You then "play" the redo logs, effectively "redoing" all the transactions that affected those files. All files are then synchronized, and the database can be brought on line.

With all the logs in place, this restore can be done with one command. However, if just one redolog is lost, your restore could range from extremely difficult to impossible. If you do not have the set of redo logs that span the time of your backup, you will not recover at all.

This is why you must treat your redo logs as if they were gold. Some of the methods of protecting them are:

  • Mirroring the filesystem on which they are stored
  • Storing them on an NFS filesystem mounted from another server (can result in a performance loss)
  • Performing hourly incremental backups (to tape or disk) of that filesystem
  • It is not uncommon, or unwise, to use all of the methods listed above to protect your redo logs.

    A Bird's Eye View

    To automate Oracle backups, you must know the location of tablespaces, raw partitions, data files, control files, redolog files, and whether archiving is running. To make things more difficult, each installation will have different answers to these questions. Oraback.sh (Listing 1) addresses all of the problems above by obtaining most of its information from Oracle. The script will do the following for each instance:

    Oraback.sh supports these features:

    The script assumes that all instances want hot backups, which require redologs. Oraback.sh will warn you of any instances that do not have logs. It will continue to warn you until you correct the problem or customize your setup for that instance. Customization is done with oraback.conf (Listing 2).

    Looking in the Backup Window

    Now that I've explained the overall purpose of oraback.sh, here is a description of each step within the script. Oraback.sh begins by checking oraback.conf to see whether the user running the backup is Oracle, or a user listed in field 7. It then looks to see whether the word "skip" is in field 2. If so, it skips the backup once, and removes the word from oraback.conf. The script then does one of the following, depending on which arguments, if any, it received:

    $ oraback.sh at
    If called with at as an argument (you would usually do this with cron), the script checks oraback.conf to see whether the day in field 3 (the cold backup day) is today. If so, it will set the variable $TIME to the time in field 4. If not, it will set it to the time in field 5. The script then schedules an at job that will run oraback.sh at the time specified by $TIME.
    $ oraback.sh [$SID1 $SID2...]
    If given one or more instance names (ORACLE_SID's, or SID's for short) as arguments, the script will perform a backup of each of them. If given no arguments, it performs a backup of all SID's listed in Oracle's oratab file. (The latter is what the at job will do.)

    Oraback.sh first checks the $HOST.master line in oraback.conf to find out if: (a) there is a tape device in field 6. If so, it labels the tape, making sure the no-rewind device is being used; and (b) there is a number in field 8. If so, it will perform that many simultaneous copies of database files.

    Next, oraback.sh asks Oracle questions that will determine how (or if) the SID will be backed up. It asks:

    Hot Backup

    If the instance is online, logs are on, and the instance is not excluded from hot backups, the script will put each tablespace into backup mode by using the sqldba command begin backup. The script then copies that tablespace's files to the backup device. When the copying is done, it takes the tablespace out of backup mode with the sqldba command end backup.

    These three steps are the core of the hot backup. While the tablespace is in backup mode, the files will not change. Any changes made to the tablespace while it is in backup mode are sent to the redo logs, and are applied once it is taken out of backup mode. This is all done automatically by Oracle.

    The script supports simultaneous copying of individual data files to dramatically increase the speed of the backup. Depending on how many files there are per tablespace, there may be one or more tablespaces in backup mode at once. This will be done in order to perform the number of concurrent file copies (using dd) that are specified in field 8 of oraback.conf, if you are using this feature.

    Next, the script forces a checkpoint and archive log switch, which causes the online redo log to be sent to an archived redo log. It then backs up the control files to disk using both sqldba and a manual copy. Finally, the script makes copies of the essential redo logs and compresses them. (Essential logs are those that span the time of your backup.) If the backup device is a tape, it then backs up all files that were sent to disk to the tape device.

    Cold Backup

    If the instance is off-line, oraback.sh only needs to copy the datafiles to the backup device. The script then backs up the control files and redo logs the same way the hot backup does.

    Special Cases

    If the instance is online, but archiving is off, it checks oraback.conf for a line that reads:

    $HOST:$SID:NOARCHIVELOG

    If that line is not found, oraback.sh complains and sends you mail. If the line is found, it looks for the word "offline" on that same line. If "offline" is there, oraback.sh will shut down the instance, perform a cold backup, and then restart the instance. (This is one of the ways you can customize oraback.sh for different instances.)

    If the instance is online, oraback.sh also looks for a line in oraback.conf that reads

    $HOST:$SID:::nohotbackup

    If that word is found, the script will skip this instance when performing a hot backup.

    In summary, oraback.sh can back up from one to all of the instances in oratab. To back up all instances, run it with no arguments. To back up one or more, run it with those SID's as arguments. To schedule a backup for the time in oraback.conf, run it with the at argument.

    Installing Oraback.sh

    Check the following values in the site-specific section at the top of the script. They must be changed to fit your site.
    a. BINDIR must be set to where you install oraback.sh.
    b. ORATAB must be set to the name and location of Oracle's oratab file. (It must be located in a directory that Oracle can write to.)
    c. ORACONF must be set to the name and location of your oraback.conf file.

    You should also review and confirm all other preference variables in the site-specific section, including ORADIR, TMP, LOG, and PATH.

    You must also verify that:
    a. You know which instances are using archiving
    b. There is a valid archive_dest in each instances's config.ora (not the default /?/dbs/arch)
    c. Oracle can create directories in the filesystem that you are using for backups

    Customizing Your Backups

    Oraback.conf is the main configuration file for oraback.sh. If you want to use the at argument, you must have the following line in that file:

    $HOST.master::Sun:1200:1730:::5 :/dmp::Mail -s Error_oracle:

    These and other fields are described below:

    There are four predefined functions, unused by default, which are listed in the site-specific section of oraback.sh. They are listed below, with their respective usage times:

    Preback: Runs before entire backup starts
    Preshut: Runs before shutdown of each instance
    Poststart: Runs after startup of each instance
    Postback: Runs after entire backup finishes

    If you wanted to restart orasrv, for instance, after an instance is restarted (with a script called rc.orasrv.sh,) you would change the Poststart function to read:

    Poststart(){
    rc.orasrv.sh
    }

    Customizing Backups of an Instance

    If you have an instance that will not run archiving or hot backups, you need a line in oraback.conf that starts with:

    $HOST:$SID::::

    Testing the Backup

    For testing, select a small instance and run the following command as Oracle:

    $ /usr/local/bin/oraback.sh <instance>

    Be aware that if the word "offline," is in oraback.conf, or if the day that you specified for cold backups is today, the instance will be shut down!

    If this test functions properly, you can put an entry in Oracle's cron. Call this command:

    /usr/local/bin/oraback.sh at

    at around 1200. It will check the oraback.conf and schedule an at job that will perform the backup.

    Testing the Restore

    Never accept that a new backup strategy works until you have fully tested it under all circumstances! Make sure you test all of the following until you are comfortable:

    Once your restore test is successful, you will have caught and tamed the rhino! This script represents more than three years of work, and its development is a continual process. I hope you find it useful.

    divider

    Definitions

    Actually, I've learned quite a bit since writing this article. If you want to read the up to date definitions of these terms, as well as a comparison between similar terms in Informix and Oracle, please refer to this table.
    Data Files
    The physical files that hold the data or indexes. These may be files in a filesystem (e.g., ~/data.dbf) or raw partitions (e.g., /dev/rdsk/c1t0d0s0).
    Tablespace
    A collection of data files (e.g., tablespace TBS1 contains files ~/data1.dbf and ~/data2.dbf).
    Instance
    Each occurrence, or "instance," of Oracle. This is sometimes referred to as the database, but it can actually contain several databases. There may be one or many instances on each UNIX server, and all will be listed in Oracle's oratab file.
    Oratab
    Oracle's global configuration file, listing each instance name, its working directory, and whether it is to be restarted after a reboot.
    Redo Log File
    This is a transaction log that keeps track of all changes to each tablespace. It is used to redo those changes during a restore, thus the name. (Each instance has its own set of logs.)
    Control File
    Each instance has a control file that keeps track of all the tablespaces, data files, redo log files, and their current state.

     RETURN


    Should I Use Archiving?

    In one word, yes! Having logs available during a recovery is a tremendous help. There are several things that you can do with logs that simply are not possible without them:

    There are two common arguments for not using archiving. The first is that it would decrease performance. The performance loss is relatively minor, unless you have some parameters set incorrectly. It is important to know that redo logs are always being created in every Oracle instance. Enabling archiving simply copies the closed redo log to another directory for recovery purposes. Before you make any decision about archiving, you should benchmark your instance with and without archiving. (A recent benchmark I performed showed a 2.5 percent performance loss during continuous loads.)

    The second argument is that archiving is superfluous. Some databases are read-only and are updated only occasionally. Users might argue that archiving is not needed. They often tell you to backup the database manually when it changes, after a load or import. This defeats one of the main purposes of automating: cron never forgets! If the database does not need archiving because there is little activity, then enabling it won't hurt. The CPU usage is negligible, and archiving will allow you to do hot backups and use all the features listed above.

    There is one type of database that I think would be significantly hampered by archiving: the test database. I still believe that logs should be on whenever possible, but when a DBA is testing a new database, he/she may be loading and unloading tables all day long. This generates a huge amount of archived redo logs that have no value. It was for this type of database that I added the ability to do a nightly cold backup of an instance that does not have archiving.

    In summary, every production and development database should use archiving, unless continuous imports make it unrealistic.  RETURN


    Listing 1 -- oraback.sh

    PLEASE NOTE! This script is always changing, trying to keep up with the latest versions of Oracle and requests from users. To make sure you are running the most current version, contact me. I am in the process of adding some new features to it, so the version that you see here may not be fully functional! Once again, contact me!

    #!/bin/sh
    #
    # Rcs_ID" = '$Id: oraback.sh,v 1.3 1997/06/20 15:26:59 Pcurtis Exp Pcurtis $'
    #
    # @(#)oraback.sh  -  (Hot backups for Oracle databases)
    # Copyright (C) 1996 Curtis Preston - curtis@pencom.com
    #
    # This program is free software; you can redistribute it and/or modify it
    # under the terms of the GNU General Public License as published by the Free
    # Software Foundation; either version 2 of the License, or (at your option)
    # any later version.
    #
    # This program is distributed in the hope that it will be useful, but WITHOUT
    # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    # details.
    #
    # For a copy of the license, write to the Free Software
    # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139
    #
    # Many thanks to Sam Binder, for bringing me gracefully into SQL land...
    #
    #######################################################
    ##Site-specific section (change as appopriate)
    
    COMPRESS=/usr/local/bin/gzip
    BINDIR=/usr/local/bin             #Where $0 is located
    ORACLE=oracle
    ORATAB=/var/opt/oracle/oratab      #Oracle's configfile
    ORADIR=`grep -v '^#' $ORATAB|cut -d: -f2|tail -1`
    ORACONF=$ORACLE_HOME/oraback.conf
    TMP=/var/tmp
    PATH=$PATH:/usr/bin:/usr/sbin:/sbin:/usr/5bin:/bin:$ORACLE_HOME/bin
    ORIG_PATH=$PATH
    
    Preshut() {        #Run prior to shutting down instance
    echo
    }
    
    Poststart() {           #Run after starting up instance
    echo
    }
    
    Preback() {             #Run prior to backup 
    echo
    }
    
    Postback() {          #Run after entire backup finishes
    echo
    }
    
    export BINDIR ORATAB ORADIR ORACONF TMP PATH ORIG_PATH
    ##End site-specific configuration section
    #######################################################
     
    Usage() {
     echo "Usage:   $0 [at|\$ORACLE_SID(s)]"
     echo "(Must be run as one of the following users:"
     echo "${USERS}."
     exit 1
    }
    
    Log_error() {            #Send errors to various places
     if [ "$1" -gt 0 ] ; then
      TIME="`date '+%b %e %T'` `uname -n`"
      MSG=" `basename $0` $SID: WARNING:"
      ERR=`echo $2|sed "s/^/$TIME $MSG /"`
      echo "$ERR" |$TLOG
      echo "$ERR" >>$TMP/$X.Mail
      [ "$1" = 99 ] && touch $TMP/$X.Fatal
     fi
    }
    
    Error_chk() {     #Check sqldba output files for errors
     ERROR=`egrep -i 'dba\-|ora\-|error' $1`
     [ -n "$ERROR" ] && Log_error 1 "$ERROR"
     [ ! -s $1 ] && Log_error 99 "Bus error on sqdldba"
    }
    
    Backup_offline() {             #backup database offline
     echo "  Backing up $ORACLE_SID OFFLINE." |$TLOG
     Backup_instance cold
    }
    
    Backup_instance() {     #All tablespaces in an instance
     cut -d: -f1 $CWD/$TBLSPACE_LIST | sort -u |\
     while read TBS ; do
      if [ -n "$SIMULT" ] ; then     #If doing simultaneous
        Ps_check                          #backups, then do
        export TBS                             #them in the
        Backup_one_tablespace $1 &              #background
      else
        export TBS
        Backup_one_tablespace $1
      fi
     done                        #done with all tablespaces
     while [ -n "`ls $TMP/$X.$SID.*.* 2>/dev/null`" ] ; do
       sleep 10               #Wait for all TBS's to finish
     done
    }
    
    Backup_one_tablespace() {         #All files in one tbs
     touch $TMP/$X.$SID.${TBS}all.allfiles
     if [ $1 = "hot" ] ; then     #If hot b/up, then put in
      $SQLDBA<<EOF_BOT >$TMP/$X.$TBS.tablebu 2>&1     #b/up mode
    connect internal;
    alter tablespace $TBS begin backup;
    exit;
    EOF_BOT
      Error_chk $TMP/$X.$TBS.tablebu
     fi
     
     grep "^$TBS:" $CWD/$TBLSPACE_LIST | cut -d: -f2 | \
     while read FILE ; do
      if [ -n "$SIMULT" ] ; then      #More simult. copying
        export FILE
        Ps_check
        Copy_one_database_file &
      else
        CT=`cat $TMP/$X.CT`
        echo `expr $CT + 1` > $TMP/$X.CT
        export FILE CT
        Copy_one_database_file
      fi
     done                       #Done with all files in TBS
     
     while [ -n "`ls $TMP/$X.*.$TBS.* 2>/dev/null`" ] ; do
      sleep 10                  #Wait for all files to copy
     done
     
     if [ $1 = "hot" ] ; then #If hot, take out of b/u mode
      $SQLDBA << EOF_TBS > $TMP/$X.$TBS.tablebu 2>&1
    connect internal;
    alter tablespace $TBS end backup;
    exit;
    EOF_TBS
      Error_chk $TMP/$X.$TBS.tablebu
     fi
     rm $TMP/$X.$SID.${TBS}all.allfiles
    }
    
    Copy_one_database_file() {
     DBF_BASENAME=`basename $FILE`
     touch $TMP/$X.$SID.$TBS.$DBF_BASENAME
     [ -n "$TAPE" ] && DEVICE=$TAPE || DEVICE=$CWD/$DBF_BASENAME
     
     NEW=`find $FILE -newer $CWD/$DBF_BASENAME.Z -print 2>/dev/null`
    
     #If datafile newer than b/u,or if no b/u file,copy it
     if [ $? -gt 0 -o "$NEW" = "$FILE" ]  ; then
     
      echo "   ${CT}- Backing up $FILE." |$TLOG
     
      if [ "$COMP" = Y ]  ; then
       dd if=$FILE bs=64k 2>/dev/null| $COMPRESS -c >$DEVICE.Z
      else
       dd if=$FILE of=$DEVICE bs=64k 2>/dev/null
      fi
     
      Log_error $? "Error Copying $FILE to $DEVICE"
     
     else
      echo "   NOT Backing up $FILE. (NO CHANGE)" |$TLOG
     fi
     rm $TMP/$X.$SID.$TBS.$DBF_BASENAME
    }
    
    Switch_logfile_n_backup_controlfile() {
     echo "  Switch logfile and backup controlfile"|$TLOG
    
    #This section needs to be changed to use the 'alter system logfile current'
    #syntax, while providing backwards compatibility to this old way for v.6
     
     $SQLDBA << EOF_CONTROL >$TMP/$X.control 2>&1
    connect internal;
    alter system checkpoint;
    alter system switch logfile;
    alter database backup controlfile to '$CWD/cntrlfile';
    exit;
    EOF_CONTROL
     Error_chk $TMP/$X.control
    
     $COMPRESS -f $CWD/cntrlfile >/dev/null 2>&1
     Log_error $? "Error compressing $CWD/cntrlfile"
    
     #If it is a valid archive log dir, and there are logs in it
     if [ -d $ARCH_LOG_DIR -a -f ${LOGDEST}* ] ; then
      NEWER=`find $ARCH_LOG_DIR -type f -name "$ARCH_LOG_FILE*" -newer $OLD -print`
      NEWER=`echo $NEWER`
      NEWEST=`ls -t $NEWER|head -1`
      OLD_SIZE=`du -s $NEWEST|awk '{print $1}'` ; SLEEP=Y
    
      #This section sleeps if last archive log file is still being copied
      #(sometimes this takes a few minutes)
      #This is a 'hacky' way to do it, but was the best way to do in v.6
    
      while test $SLEEP = Y ; do
       sleep 60
       NEW_SIZE=`du -s $NEWEST|awk '{print $1}'`
       [ $NEW_SIZE -eq $OLD_SIZE ] && SLEEP=N || OLD_SIZE=$NEW_SIZE
      done
    
     else
      [ ! -d $ARCH_LOG_DIR ] && Log_error 1 "Log dest. (${ARCH_LOG_DIR}) not found." 
      [ ! -f ${LOGDEST}* ] && Log_error 1 "NO archive logs found in $ARCH_LOG_DIR." 
     fi
    
    }
    
    Copy_control_and_essential_redolog_files() {
    
     if [ -f $CWD/$CONTROLFILE_LIST ] ; then
     
      echo "  Manually copying one controlfile." |$TLOG
      CONTROLFILE=`grep -v '^$' $CWD/$CONTROLFILE_LIST`
      CONTROLFILE_BASENAME=`basename $CONTROLFILE`
      $COMPRESS -c $CONTROLFILE > $CWD/$CONTROLFILE_BASENAME.Z
      Log_error $? "Error copying $FILE to $CWD"
     
     else
       Log_error 1 "$CWD/$CONTROLFILE_LIST: File not found"
     fi
     
     if [ -f $CWD/$CONTROLFILE_LIST $CWD/$LOGFILE_LIST ] ; then
      if [ -z "$NOLOG" ] ; then
    
        #If Instance is up, remove old logs, otherwise leave them
        if [ "$DB_UP" = 2 ] ; then
    
         REMOVE=`ls $CWD/BacKuP.CoPy.${ARCH_LOG_FILE}* 2>/dev/null|grep -v $BOLD`
         REMOVE=`echo $REMOVE`
    
         if [ -n "$REMOVE" ] ; then
          echo "  Removing old archive logs: $REMOVE" |$TLOG
          rm -f $REMOVE
         else
          echo "  NO old archive logs found." |$TLOG
         fi
    
        fi
     
        echo "  Copying redo logs created since $0 started:" |$TLOG
        NEWER=`find $ARCH_LOG_DIR -type f -name "$ARCH_LOG_FILE*" -newer $OLD -print`
        NEWER=`echo $NEWER`
    
        if [ -n "$NEWER" ] ; then
         echo "  New archive logs found: $NEWER" |$TLOG
         
         #Files are stable, copy them
         for ARCHIVED_LOG_FILE in $NEWER ; do
          ARCHIVED_LOG_FILE_BASENAME=`basename $ARCHIVED_LOG_FILE`
          BACKUP_COPY_NAME="BacKuP.CoPy.$ARCHIVED_LOG_FILE_BASENAME"
          cp -p $ARCHIVED_LOG_FILE $CWD/$BACKUP_COPY_NAME
          [ $? -ne 0 ] && Log_error 1 "Error copying $ARCHIVED_LOG_FILE to $CWD"
         done
    
         $COMPRESS -f $CWD/BacKuP.CoPy.${ARCH_LOG_FILE}*
         [ $? -ne 0 ] && Log_error 1 "Error compressing $CWD/${ARCH_LOG_FILE}\*"
    
        else
         echo "  NO archive logs made since this backup started." |$TLOG
        fi
     
      else
        echo "  No logs:  $SID set to NOARCHIVELOG." |$TLOG
      fi
     else
        Log_error 1 "$CWD/$LOGFILE_LIST: File not found"
     fi
     
     if [ -n "$TAPE" ] ; then      #Send disk b/u's to tape
       CT=`cat $TMP/$X.CT`
       echo `expr $CT + 1` > $TMP/$X.CT
       echo "  ${CT}- Tar file of $CWD." |$TLOG
       cd $CWD
       tar cvf $TAPE ./*
       mt -t $TAPE offl
     fi
    }
    
    Copy_online_redologs() {
      echo "  Copying online redo logs." |$TLOG
    
      cat $CWD/$REDOLOGS_LIST | \
      while read REDOLOG
      do
        LOGBASE=`basename $REDOLOG`
        $COMPRESS -c $REDOLOG >$CWD/$LOGBASE.Z
        [ $? -ne 0 ] && Log_error 1 "Error compressing $REDOLOG to $CWD/LOGBASE.Z"
      done
    
    }
    
    Shutdown_instance() {
     echo "  (Shutting down $ORACLE_SID `date` )" |$TLOG
    
     Preshut                         #Pre-Shutdown function
     $SQLDBA<<EOF_SID >$TMP/$SID.shut.out 2>&1
    connect internal;
    shutdown immediate;
    host sleep 2
    startup restrict;
    host sleep 2
    shutdown;
    exit;
    EOF_SID
     Error_chk $TMP/$SID.shut.out
    }
    
    Startup_instance() {
     echo "  (Starting up $ORACLE_SID `date` )" |$TLOG
     $SQLDBA<<EOF_STARTUP >$TMP/$SID.start.out 2>&1
    connect internal;
    startup;
    exit;
    EOF_STARTUP
     Error_chk $TMP/$SID.start.out
     Poststart                       #Post-startup function
    }
    
    Ps_check() {
     while [ `$PS|grep "$WHO.*dd" | grep -v grep | wc -l \
      |sed 's/  *//'` -ge $SIMULT ] ; do
      sleep 10     #Sleep while more than $SIMULT processes
     done
    }
     
                            #Setup log and global variables
    LOG=$TMP/oraback.log
    touch $LOG
    chown $ORACLE $LOG
    TLOG="tee -a $LOG"
    LOGFILE_LIST=logfile.list
    CONTROLFILE_LIST=controlfile.list
    REDOLOGS_LIST=redologs.list
    X=$$
    TBLSPACE_LIST=tablespace.list
    export LOGFILE_LIST CONTROLFILE_LIST TBLSPACE_LIST REDOLOGS_LIST
    ps -ef >/dev/null && PS='ps -ef' || PS='ps -aux'
    HOST=`uname -n|cut -d. -f1`
    SKIP=`grep "^$HOST.master:" $ORACONF | cut -d: -f2`
    COLD=`grep "^$HOST.master:" $ORACONF | cut -d: -f3`
     [ "$COLD" -gt 0 ] 2>/dev/null  &&  DAY=`date +%d`  ||  DAY=`date +%a`
     [ "$COLD" = '*' ]  &&  COLD=$DAY
     if [ "$DAY" = "$COLD" ] ; then
      TYPE=COLD  ;  NUM=4
     else
      TYPE=HOT   ;  NUM=5
     fi
    TIME=`grep "^$HOST.master:" $ORACONF  | cut -d: -f$NUM`
    TAPE=`grep "^$HOST.master:" $ORACONF  | cut -d: -f6`
    USERS=`grep "^$HOST.master:" $ORACONF | cut -d: -f7`
     [ -z "$USERS" ]  &&  USERS=$ORACLE
    SIMULT=`grep "^$HOST.master:" $ORACONF | cut -d: -f8`
    BACKUPDIR=`grep "^$HOST.master:" $ORACONF |cut -d: -f9`
    if [ -n "$TAPE" -a -n "$SIMULT" ] ; then
     echo "Simultaneous backups not supported for" |$TLOG
     echo "tape devices.  (Will do serially.) " |$TLOG
     SIMULT=''  ;  export SIMULT
    fi
    COMP=`grep "^$HOST.master:" $ORACONF | cut -d: -f10`
    MAIL_DBA=`grep "^$HOST.master:" $ORACONF |cut -d: -f11`
    WHO=`id | cut -d'(' -f2 | cut -d')' -f1`
     
    if [ -z "$BACKUPDIR" -o -z "$MAIL_DBA" ] ; then
      echo "Field 9 or 12 in $ORACONF is empty!"   |$TLOG
      echo "(9=BACKUPDIR, 12=complete Mail command)" |$TLOG
      exit 1
    fi
                                       #Build list of SID's
    if [ $# -ne 0 -a "$1" != "at" ] ; then
     GREP=`echo $* | sed 's/ /:|^/g' | sed 's/$/:/'`
     SID_LIST=`egrep "^$GREP" $ORATAB`
     ARG=backup
    else
     SID_LIST=`grep -v '^\#' $ORATAB`
     [ $# -eq 0 ] && ARG=backup || ARG=at
    fi
     
    if [ -z "`echo $WHO | egrep \"$USERS\"`" ] ; then
     echo "WRONG LOGIN!\n"
     Usage
    fi
     
    if [ ! -s $ORATAB ]; then
     Log_error 1 "NO $ORATAB - ABORTING BACKUP"
     exit 1
    fi
    
    touch $ORACONF ${ORACONF}.2
    Log_error $? "Unable to create $ORACONF or $ORACONF.2 (Needed 4 SKIP feature.)"
    
                        #Skip b/u if find "skip" in ORACONF
    if [ "$SKIP" = "skip" ]; then
     sed "s/$HOST.*:skip:/$HOST.master::/" $ORACONF >${ORACONF}.2
     mv -f ${ORACONF}.2 $ORACONF
     echo "SKIPPING $0 ON $HOST TONIGHT ONLY DUE"   |$TLOG
     echo "TO THE WORD \"skip\" IN $ORACONF!!"      |$TLOG
     exit 0
    fi
    
    case ${ARG} in
    
    at)          #Check $ORACONF for backup and schedule it
     if [ -z "$TIME" ]; then
      Log_error 1 "No backup time found in $ORACONF"
      exit 1
     fi
    
     #Apparently, HP-UX doesn't like the -s flag for 'at'
     [ `uname -s` = "HP-UX" ] && _S='' || _S='-s'
     
     at $_S $TIME<<EOF_AT          
     #Start an at job w/Bourne sh
    $BINDIR/oraback.sh > $TMP/oraback.out 2>&1
    EOF_AT
    ;;
     
    backup)
    #Actually run backup
     
    echo "in backup"
     echo "\n====================================" |$TLOG
     echo "Began Oracle backup: `date '+%X %D'`"   |$TLOG
     echo "1" > $TMP/$X.CT
     Preback                           #Pre-backup function
     
     if [ -n "$TAPE" ]; then         #If using tape, label
      LABEL="$* $X.`date +%D`"
      mt -t $TAPE rew
      if [ $? -eq 0 ]; then
       sleep 2
       echo $LABEL > $TAPE
      else
       Log_error 1 "$TAPE failed to rewind!"
       exit 1
      fi
      if [ "`dd if=$TAPE count=5 2>/dev/null`" = "$LABEL" ]
      then
       Log_error 1 "$TAPE NOT the non-rewind device"
       exit 1
      else
       mt -t $TAPE rew
       mt -t $TAPE fsf 1
       echo "Label the tape in $TAPE: \"$LABEL\"." |$TLOG
      fi
     fi
     
                            #For each SID we are backing up
     for LINE in $SID_LIST
     do
      ORACLE_SID=`echo $LINE  | cut -d: -f1`
      SID=$ORACLE_SID 
      CWD=$BACKUPDIR/$SID
      ORACLE_HOME=`echo $LINE | cut -d: -f2`
      [ -f $ORACLE_HOME/bin/sqldba ] && SQLDBA=sqldba || SQLDBA=svrmgrl
      export CWD ORACLE_SID SID ORACLE_HOME SQLDBA
      mkdir $CWD 2>/dev/null
      chmod 775 $CWD  ;  chgrp dba $CWD
      PATH=$ORIG_PATH:$ORACLE_HOME/bin  ;  export PATH
     
      DBNUM=`$PS|egrep -c "ora_...._$SID"`
      if [ $DBNUM -ge 4 ] ; then            #Database is UP
       DB_UP=2
     
       $SQLDBA <<EOF_XTBS >$TMP/$X.tbs    #Make list of tbls
    connect internal;
    select tablespace_name, file_name \
    from sys.dba_data_files;
    exit;
    EOF_XTBS
    
       nawk 'NF == 2 && $2 ~ "/" {print $1":"$2}' $TMP/$X.tbs > $CWD/$TBLSPACE_LIST 
       Error_chk $TMP/$X.tbs
    
       $SQLDBA <<EOF_LOGFILE > $TMP/$X.rdl
    connect internal;
    select * from v\$logfile ;
    exit;
    EOF_LOGFILE
    
        nawk 'NF == 2 && $2 ~ "/" {print $2}' $TMP/$X.rdl > $CWD/$REDOLOGS_LIST
    
       Error_chk $TMP/$X.rdl
     
       $SQLDBA <<EOF_ARCHIVE >$TMP/$X.lf   #List archiving info
    connect internal;
    archive log list;
    exit;
    EOF_ARCHIVE
       awk '/^Ar/ {print $3}' $TMP/$X.lf > $CWD/logfile.list
       Error_chk $TMP/$X.lf
     
       LOGMODE=`grep 'og mode.* ARCHIVELO' $TMP/$X.lf`
       [ -n "$LOGMODE" ]  &&  LOGS_UP=20  ||  LOGS_UP=10
     
    #
    #Yet another section that needs to be coded for v.6
    # Original version of the "get control files" (which works for v.6)
    #
    #   $SLQDBA<<EOF_SHOW >$TMP/$X.cf  #Get name of controlfile
    #connect internal;
    #show parameters;
    #exit;
    #EOF_SHOW
    #   grep '^control_' $TMP/$X.cf | awk '{print $3}' \
    #    |sed 's/,//' >$CWD/$CONTROLFILE_LIST
    #   Error_chk $TMP/$X.cf
    
        $SQLDBA <<EOF >$TMP/$X.cf  #Get name of controlfile
    connect internal;
    select name from v_\$controlfile;
    exit;
    EOF
       grep "^/" $TMP/$X.cf > $CWD/$CONTROLFILE_LIST
    
       Error_chk $TMP/$X.cf
    
      else                           #else Database is DOWN
       DB_UP=1  ;  LOGS_UP=10
      fi
     
      LOGDEST=`grep -v '^$' $CWD/$LOGFILE_LIST`
      ARCH_LOG_FILE=`basename $LOGDEST`
    
      ARCH_LOG_DIR=`echo $LOGDEST | sed 's-/[^/]*$--'`
      OLD=`ls -t $ARCH_LOG_DIR/${ARCH_LOG_FILE}* | head -3 | tail -1`
      BOLD=`basename $OLD`
     
      NOLOG=`grep "^$HOST:$SID:NOARCHIVELOG" $ORACONF`
      NOHOTBACKUP=`grep -i "$HOST:$SID:.*nohotbackup" $ORACONF`
      [ -n "$NOHOTBACKUP" ]  &&  HOTBACKUP=100  ||  HOTBACKUP=200
     
      DB_STAT=`expr $HOTBACKUP + $LOGS_UP + $DB_UP`
      [ -f $TMP/$X.Fatal ]  &&  DB_STAT=221
    
      if [ "$TYPE" = COLD ] ; then
    
       #IF instance is not already down, and its cold backup
       #time, then shut her down, else set a variable to not
       #start it when we are done
    
       if [ $DB_STAT -ne 111 o $DB_STAT -ne 211 ] ; then
         Switch_logfile_n_backup_controlfile #Function
         Shutdown_instance
         DB_UP=1  ;  LOGS_UP=10
         DB_STAT=`expr $HOTBACKUP + $LOGS_UP + $DB_UP`
         [ -f $TMP/$X.Fatal ]  &&  DB_STAT=221
       else
         DONT_START=DONT
       fi
      fi
     
      echo " -----------------------------"     |$TLOG
      echo " Backup of $SID Started `date +%T`" |$TLOG
     
      case $DB_STAT in
      222)         #DB=up, LOGS=on, Backup gets green light
     
       Backup_instance hot                 #Function
    
       Switch_logfile_n_backup_controlfile #Function
     
       Copy_online_redologs
    
       Copy_control_and_essential_redolog_files ;;
    
     
      211|111)                 #Database is completely down
     
       if [ $DB_STAT = 211 -a "$TYPE" = "HOT" ] ; then
        Log_error 1 "!!$SID IS DOWN During a Hot Backup!!"
       fi
     
       Backup_offline
       Copy_online_redologs
       Copy_control_and_essential_redolog_files ;;
     
      212)      #Database is up, but is set to NOARCHIVELOG
     
       if [ -n "$NOLOG" ]  ; then       #Logs should be off
        if [ -n "`echo "$NOLOG"|grep ':offline'`" ] ; then
         Shutdown_instance          #Configured for offline
         Backup_offline                   #backup, so do it
         Copy_online_redologs
         Copy_control_and_essential_redolog_files
         Startup_instance
         Switch_logfile_n_backup_controlfile
    
       Error_chk $TMP/$X.control
        else                       #else just skip instance
         echo " $SID skipped! (NOARCHIVELOG)"   |$TLOG
        fi
       else     #This SID has no logs and is not customized
        Log_error 1 "BACKUP FAILED (NOARCHIVELOG)!"
       fi ;;
     
       112|122)           #Word 'nohotbackup' is in ORACONF
       echo " Skipping $SID.  Found NOHOTBACKUP"  |$TLOG
       echo " variable in $ORATAB."               |$TLOG ;;
       221)            #Fatal error in early sqldba command
       Log_error 1 "No backup performed"
       rm $TMP/$X.Fatal ;;
    
      esac
     
       #If instance was not already down before the /bu started,
       #then start it up
       if [ "$DONT_START" != DONT ] ; then
        [ "$TYPE" = COLD ]  &&  Startup_instance
       fi
     
     done                              #done with all $SIDS
     
     Postback       #Run Post-backup script if there is one
     
     echo "Ended Oracle backup: `date '+%X %D'`"  |$TLOG
     echo "===================================="  |$TLOG
     
                #If there were errors, mail them to the dba
     [ -s $TMP/$X.Mail ] && $MAIL_DBA < $TMP/$X.Mail
     
     rm -f $TMP/$X.* ;;              #Remove temporary logs
    esac
    #End of oraback.sh

     RETURN


    Listing 2 -- oraback.conf

    #(Back-slashes used for display only.  Real config
    #lines must be continuous)
    
    #Host.master:skip:Sun:2330:1730:/dev/rmt/1cn \
    # :oracle|curtis:3:/backup:Y:/usr/ucb/Mail -s Backup_Error
    # oracle@Host,sa@HisPC
    
    #Host:instance:NOARCHIVELOG:offline:nohotbackup:
    
    saturn.master::Sat:2330:1830:::5:/back:Y:/usr/ucb/Mail -s Backup_Error
    oracle@saturn,sa@HisPC
    saturn:OraSatDbA:NOARCHIVELOG:offline::
    saturn:OraSatDbB:::nohotbackup:
    
    #'.Master' line says that all db's on saturn will get a
    #cold b/u on Sat at 2330, a hot b/u all other nights at
    #1830, doing 5 simultaneous copies, backing up to /back
    #with compression, and will send mail to id's oracle&sa
    
    #OraSatDbA has no redologs-will do cold b/u every night
    #OraSatDbB will be skipped on hot b/u nights, and will
    #only be backed up by the cold b/u on Saturday
    
    apollo.master::*:2330::/dev/rmt/1cn:root::/dump:Y:/usr/ucb/Mail -s \
     Backup_Error root@apollo
    
    #This host will do a cold b/u every night, and will 
    #use the tape /dev/rmt/1cn.  Only root is allowed to
    #run oraback.sh. (Simult. backups do not work to tape)
    
    # End of File

     RETURN

    Go back to Backup Central