devcopy source



#!/usr/local/bin/bash
# AUTHOR:
#   Greg Keraunen <gk@proliberty.com>
#   1/29/2002

# PURPOSE:
#   Backup multiple partitions on one hard drive to another, using rsync
#   Easily configure each backup strategy with separate config/exclude files

# USAGE:
#   cd <directory of devcopy.sh>
#   devcopy.sh [-b] <device name of drive to backup>

# OPTIONS:
#   EXPERIMENTAL: -b  copy master boot record

# CHANGE LOG:
#    10/31/04     cygwin support, no remount
#                added -i for interactive confirmation of partition copying
#   3/5/05        allow different partition types on source, destination
#               allow different mount options on source, destination
#               added $2 command line option to specify index of partition to start with
#               added options: -h, -v
#                for cygwin, changed first line of this script to !/usr/bin/bash since /bin/bash does not exist
#                 TODO: auto-determine and change DEVICE_DIR value appropriately?
#    5/8/05        reconciled cygwin and unix versions of this file
#                changed SRC_PARTITIONS from string to an array
#    9/2/05        fixed bug: DEVICE_DIR=/dev/ changed to /dev
#    9/2/05        changed had.config, etc.: /dev/hda changed to hda ...
#    9/2/05        fixed bug: src_mount_options=${SRC_MOUNT_OPTIONS[$i]}
#    9/2/05        fixed bug: $type_src changed to $src_type
#    12/19/2005    VERIFIED - NO mount options can use "" as a placeholder (E.g., vfat -> ntfs)
#
# TO DO:
#    12/16/2005
#        change format of config file and parsing to use mtab format

# on CYGWIN, manually try:
#rsync -avx --delete --exclude=pagefile.sys /cygdrive/f/ /cygdrive/d/ 2>> rsync.error.log

#### DEFAULT SETTINGS:
OPTSTRING=":bhivd"
BASENAME=$(basename "$0" .sh)    
USAGE="Usage: $BASENAME [OPTIONS] DEVICE_NAME [PARTITION_NUM]"
VERSION="1.1.2"

# the directory where device files are located on the system
DEVICE_DIR=/dev
MOUNT_OPTIONS=""

INTERACTIVE=false
#RSYNC_FLAGS="-avx --delete --exclude=Icon\? --exclude=resource.frk/ --exclude=pagefile.sys"
RSYNC_FLAGS="-avx --delete"

CUR_DIR=$(pwd -P)
# error log file:
ERROR_LOG=$CUR_DIR/devcopy.log

#### END OF DEFAULT SETTINGS

export PATH=/usr/local/bin:/usr/bin:/bin

ROOT_UID=0     # Only users with $UID 0 have root privileges
E_NOTROOT=67   # Non-root exit error.
E_BADARGS=65

######################################################
# help()
######################################################
help(){
        cat << eof
$USAGE
Required argument DEVICE_NAME:
    device name, without path, such as: hda, hdc, etc.
Optional arguments PARTITION_NUM:
    index number of partitions to copy, starting with 0
    if none specified, all are copied
Options:
    -b              copy Master Boot Record; EXPERIMENTAL!
    -d              debug use rsync -v option
    -h              display help; exit
    -i              interactive confirmation of each partition copy
    -v              display version; exit
eof
} ### help()

######################################################
# function definitions
######################################################
action () {
    $1
}

######################################################
# non-standard while loop
# abs-guide/internal.html#GETOPTSX
while getopts $OPTSTRING Option
do
  case $Option in
    b ) COPY_MBR=true;;
    # -vvv causes a hang; reported by others too
    d )     RSYNC_FLAGS=${RSYNC_FLAGS}" -vv";;
    h )
        help;
        exit 0;;
    i ) INTERACTIVE=true;;
    v )
        echo "$BASENAME version $VERSION, by Gregory Keranen";
        exit 0;;
    * ) echo "Unimplemented option chosen.";;   # DEFAULT
  esac
done


# I guess this decrements the argument pointer so it points to next argument after the options.
shift $(($OPTIND - 1))

if [ -z "$1" ]  # Standard check for command line arg.
   then
        help
        exit $E_BADARGS
    else
        SRC_DRIVE=$1
fi  
REMOUNT=true
case `uname` in
  Linux*) OS=Linux;;
  FreeBSD*) OS=FreeBSD;;
  SunOS*) OS=Solaris;;
  HP-UX*) OS=HPUX;;
  CYGWIN*) OS=cygwin
    #SRC_DRIVE=/cygdrive/${SRC_DRIVE};
    RSYNC_FLAGS=${RSYNC_FLAGS}" --exclude=pagefile.sys"
    REMOUNT=false    
    ;;
  Darwin*) OS=Darwin;;
  *)      OS=generic;;
esac
echo "Detected Operating System: "$OS
echo REMOUNT=$REMOUNT
echo RSYNC_FLAGS=$RSYNC_FLAGS

if [ $OS != 'cygwin' -a "$UID" -ne "$ROOT_UID" ]
then
   echo "Must be root to run this script."
   exit $E_NOTROOT
fi

###################################



# change to directory of this script; careful of symbolic links
if [ -L "$0" ]
then
    cd -P "$(dirname $0)"  
    cd -P "$(dirname $(readlink $0 ))"
else
    cd -P "$(dirname $0)" ;
fi

SCRIPT_DIR=$(pwd -P)

# list of filename patterns to exclude from backup:
CONFIG_FILE="$SCRIPT_DIR/$SRC_DRIVE.config"

# list of filename patterns to exclude from backup:
EXCLUDE_FILE="$SCRIPT_DIR/$SRC_DRIVE.exclude"

if [ $OS = 'cygwin' ] ; then
    CONFIG_FILE="$SCRIPT_DIR/$1.config"
    EXCLUDE_FILE="$SCRIPT_DIR/$1.exclude"
fi

if [ ! -e $CONFIG_FILE ]
    then
        echo "Configuration file not found: $CONFIG_FILE" >&2
        exit 1
    else
        . "$CONFIG_FILE" # source the file
fi


[ -f /proc/ide/$BAK_DRIVE/model ] && BAK_MODEL=$(cat /proc/ide/$BAK_DRIVE/model)
[ -f /proc/ide/$BAK_DRIVE/model ] && SRC_MODEL=$(cat /proc/ide/$SRC_DRIVE/model)
#echo BAK_MODEL="$BAK_MODEL" REQUIRED_BAK_MODEL= "$REQUIRED_BAK_MODEL";exit;
# to prevent backing up to wrong destination when drives are swapped around,
# define the correct drive model for each backup drive with REQUIRED_BAK_MODEL=
[ ! -z "$REQUIRED_BAK_MODEL" ] && if [[ ("$BAK_MODEL" !=  "$REQUIRED_BAK_MODEL") ]]
then
    echo "The drive model in /proc/ide/$BAK_DRIVE/model, is $BAK_MODEL."
    echo "This doesn't match required model ($REQUIRED_BAK_MODEL) for backup of source, $SRC_DRIVE."
    echo "You may want to edit the required model listed in config file, $CONFIG_FILE"
    echo "Procede anyway? (y/n)"
    read continue  
    continue=$(echo $continue | tr 'A-Z' 'a-z')
    if [ "$continue" != "y"  ]
     then
       echo "Backup aborted."
       exit 0
    fi
fi

echo "Ready to backup $SRC_DRIVE: $SRC_MODEL"
echo "to destination: $BAK_DRIVE: $BAK_MODEL"
#echo "COPY_MBR= $COPY_MBR"; exit;
if [[  $COPY_MBR = true ]]
then
    echo "Copy Master Boot Record?";
    echo "Caution: this could make the destination drive, $BAK_DRIVE, unbootable";
fi
echo "Procede? (y/n)";
read continue  
continue=$(echo $continue | tr 'A-Z' 'a-z')
if [ "$continue" != "y"  ]
then
   echo "Backup aborted."
  exit 0
fi

if [ $REMOUNT ]; then
    SRC_MOUNT=$SCRIPT_DIR/"src_mount"
    DEST_MOUNT=$SCRIPT_DIR/"dest_mount"
    if [ ! -d $SRC_MOUNT ]
     then
        mkdir $SRC_MOUNT && echo "Created directory $SRC_MOUNT"
     else
        echo "Directory $SRC_MOUNT exists"
    fi
    if [ ! -d $DEST_MOUNT ]
     then
        mkdir $DEST_MOUNT && echo "Created directory $DEST_MOUNT"
     else
        echo "Directory $DEST_MOUNT exists"
    fi
fi

date > $ERROR_LOG
failed=false

rsync_flags="$RSYNC_FLAGS"
if [ -e "$EXCLUDE_FILE" ] ;then
    rsync_flags="$RSYNC_FLAGS --exclude-from=$EXCLUDE_FILE"
fi  

#i=0
if [ -z $2 ]  # check for optional command line arg.
   then
        first_partition=0
   else
        first_partition=$2
fi  

start=`date` # time backup started

#for src_partition in $SRC_PARTITIONS; do
for (( i=0 ; $i < ${#SRC_PARTITIONS[@]}; i=$i+1 )); do
    src_partition=${SRC_PARTITIONS[$i]}
    mountok=true
    dest_partition=""
    src_type=""
    dest_type=""
    src_mount_options=""
    dest_mount_options=""

    if [ $i -lt $first_partition ]  
       then
            echo "Skipping partition index number: $i"
            #i=$i+1
            continue
        else
            dest_partition=${DEST_PARTITIONS[$i]}
            src_type=${SRC_PARTITION_TYPES[$i]}
            dest_type=${DEST_PARTITION_TYPES[$i]}
            src_mount_options=${SRC_MOUNT_OPTIONS[$i]}
            dest_mount_options=${DEST_MOUNT_OPTIONS[$i]}
            echo "Current partition index number: $i"
            echo "src_partition=$src_partition"
            echo "    src_type=$src_type"
            echo "    src_mount_options=$src_mount_options"
            echo "dest_partition=$dest_partition"
            echo "    dest_type=$dest_type"
            echo "    dest_mount_options=$dest_mount_options"
    fi  
    
# NOTE:
# "Invalid argument" or other errors may result when filenames contain ncharacters that appear as '?' - illegal on vfat filesystem
# The following mount option: this will escape unicode characters and allow copying
#     mount -o uni_xlate
# ntfs, unlike vfat,  does not return any value for unsupported unicode chars in a filename.
# The following mount option may also be useful:
#     mount -o utf8

# TODO: auto-determine and change DEVICE_DIR value appropriately?
    if [ ! -e "$DEVICE_DIR" ] ;then
        echo "Device directory does not exist: '$DEVICE_DIR'"
        echo "DEVICE_DIR can be specified in your config file: $CONFIG_FILE"
        exit 1
    fi  
    src_device_file=${DEVICE_DIR}/$src_partition
    dest_device_file=${DEVICE_DIR}/$dest_partition
    if [ ! -e "$src_device_file" ] ;then
        echo "Device does not exist: $src_device_file"
        exit 1
    fi  
    if [ ! -e "$dest_device_file" ] ;then
        echo "Device does not exist: $dest_device_file"
        exit 1
    fi  

# make sure nothing is already mounted
    if [ -f "/etc/mtab" ]; then
        remount=true
#echo SRC_MOUNT=$SRC_MOUNT        
        line=$(grep "$SRC_MOUNT" /etc/mtab)
        if ! test -z "$line"; then
           echo "Source partition, $SRC_MOUNT, is already a mount point: $line"
           exit 1
        fi
        line=$(grep "$DEST_MOUNT" /etc/mtab)
        if ! test -z "$line"; then
           echo "Destination partition, $DEST_MOUNT, is already a mount point: $line"
           exit 1
        fi
        
    # check to see if devices are already mounted, if so, use --bind option:
        src_mount_options="$src_mount_options -r"
        dest_mount_options="$dest_mount_options -w"
        src_mount_cmd="mount $src_mount_options -t $src_type $src_device_file $SRC_MOUNT"
        dest_mount_cmd="mount $dest_mount_options -t $dest_type $dest_device_file $DEST_MOUNT"
    
        line=( $(grep $src_partition /etc/mtab) )
        mount_dir=${line[1]}
        if ! test -z "$mount_dir"; then
           echo "Source partition, $src_partition, was already mounted on $mount_dir"
           src_mount_options="--bind $mount_dir $SRC_MOUNT -t $type $src_mount_options -r"
           src_mount_cmd="mount $src_mount_options"
        fi
    
        line=( $(grep $dest_partition /etc/mtab) )
        mount_dir=${line[1]}
        if ! test -z "$mount_dir"; then
           echo "Destination partition, $dest_partition, was already mounted on $mount_dir"
           dest_mount_options="--bind $mount_dir $DEST_MOUNT -t $type $dest_mount_options -w"
           dest_mount_cmd="mount $dest_mount_options"
        fi
    
        echo "Attempting mount: $src_mount_cmd ..." >> $ERROR_LOG
        $src_mount_cmd 2>> $ERROR_LOG
        if  test $? -eq 0; then  
           echo "Mounted source filesystem read-only: $src_partition on $SRC_MOUNT"
        else
            mountok=false
            echo "### Unable to mount source filesystem read-only: $src_partition on $SRC_MOUNT"  >> $ERROR_LOG
            echo "### Command: $src_mount_cmd"  >> $ERROR_LOG
        fi
    
        
        if [[ $mountok = true ]] ;then
        echo "Attempting mount: $dest_mount_cmd ..." >> $ERROR_LOG
        $dest_mount_cmd 2>> $ERROR_LOG
            if test $? -eq 0 ; then
                echo "Mounted destination filesystem: $dest_partition on $DEST_MOUNT"
            else
                mountok=false
                echo "### Unable to mount destination filesystem: $dest_partition on $DEST_MOUNT" >> $ERROR_LOG
                echo "### Command: $dest_mount_cmd"  >> $ERROR_LOG
            fi
        fi
    else
    # /etc/mtab doesn't exist
    # E.g., under CYGWIN
         REMOUNT=false
         SRC_MOUNT=${src_device_file}
         DEST_MOUNT=${dest_device_file}
    fi
    
    if ! [[ $mountok = true ]]; then
        failed=true
    else
        if [[ $INTERACTIVE = true  ]]; then
            echo "Ready to backup $SRC_MOUNT/ to $DEST_MOUNT/"
            echo "Procede? (y/n)";
            read continue  
            continue=$(echo $continue | tr 'A-Z' 'a-z')
            if [ "$continue" != "y"  ]
             then
               echo "Backup aborted."
               exit 0
            fi
        fi
        if [ ! -z "$PRE_COMMANDS" ] ; then
            eval $PRE_COMMANDS
        fi  
        rsync_cmd="rsync $rsync_flags $SRC_MOUNT/ $DEST_MOUNT/"
        echo "# Copying partition $src_partition to $dest_partition" >>$ERROR_LOG  
        echo "# Executing command: $rsync_cmd" >>$ERROR_LOG  
        echo "# Copying partition $src_partition to $dest_partition"
        echo "# Executing command: $rsync_cmd"
        $rsync_cmd 2>>$ERROR_LOG
    status=$?
        if ! test $status -eq 0; then
            failed=true
            echo "### rsync FAILED with status=$status" >>$ERROR_LOG
        fi
        if [ ! -z "$POST_COMMANDS" ] ; then
           eval $POST_COMMANDS
        fi
        $REMOUNT && umount $SRC_MOUNT 2> /dev/null
        $REMOUNT && umount $DEST_MOUNT 2> /dev/null
    fi
    #i=$i+1
done

# I never use this option
if [[ $COPY_MBR = true ]] ;then
    # copy MBR
    dd bs=512 count=1 if=/dev/$SRC_DRIVE of=/dev/$BAK_DRIVE
fi

echo $start - backup started
echo `date` - backup ended

if ! [[ $failed = false ]]; then
    echo Backup did NOT complete successfully.
    echo Errors were logged to: $ERROR_LOG
    exit 1
fi
exit 0