#!/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