#!/bin/sh


#
# do_mirror
# (c) SPDsoft 2001-2011. v0.7.6
# Tue Oct 31 13:22:20 2006
# Thu Apr 12 14:06:14 2007 - Support for Solaris i86pc
# Fri May  4 14:09:25 2007 - Linux-x86_64, fix_fstab
# Wed May 21 12:52:48 2008 - Linux grub / no chroot
# Thu Apr 22 15:33:00 2010 - IRIX, /dev/root /dev/usr aliases
# Mon Oct 10 12:13:17 2011 - Linux, ext4
# Tue Mar 11 11:58:04 2014 - Linux, GPT
# ex: set tabstop=4
#
#
#	This script will clone your system disk to a second local or remote
#	identical hard disk.
#	If disks are not identical you should create the partition table
#	on the destination disk first, and then, use this script with
#	-p option.
#
# Requires:
#	Linux
#	Two identical disks $ORIG $DEST (default /dev/sda and /dev/sdb)
#	Original system on $ORIG
#	No more than a single swap partition
#	ext2/ext3/ext4 filesystems.
#	/new/[1-9] exists
#	For lilo/grub, /boot, /sbin and /etc reside on ${ORIG}1
#	For GPT, gdisk: An fdisk-like partitioning tool for GPT disks
#	I'm not really sure about the "grub" setup. Since we are copying to
#	second disk, it should be hd1, I guess...
#
#	Note: /dev/md original only supported on live mounted FS
#	Note: /dev/md0 is translated to /dev/?d1
#	Note: GPT not supported when destination is remote
#	Warning!: GPT not tested
#
# Or:
#	Solaris 2.x
#	Two identical disks $ORIG $DEST
#	(default /dev/dsk/c0t0d0s2 /dev/dsk/c0t1d0s2)
#	Single drive mounted on $ORIG (this could be easily fixed -df-)
#	Original running system on ${ORIG}2
#	ufs filesystems
#	/new/[0-9] exists
#
# Or:
#	IRIX 6.x
#	Two identical disks $ORIG $DEST
#	(default dev/dsk/dks0d1s DEST=/dev/dsk/dks0d3s)
#	Single drive mounted on $ORIG (this could be easily fixed -df-)
#	xfs/efs filesystems
#	/new/[0-9] exists
#	/root/tmp exists (with about 3 Mb free space)
#	Not implemented: copy of partition table, remote boot loader install
#	Note: IRIX uses /dev/root and /dev/usr aliases. Only these two
#	aliases are supported by this script.
#
# Disclaimer:
#	This script works for me in some typical configurations.
#	It is not a robust-luser-proof script.
#	Plesase, read it carefully, use '-n' switch and make sure it
#	will work for you.
#	THIS SCRIPT COULD ERASE ALL OF YOUR FILESYSTEMS!
#	Use it at your own risk
#
#  NOTE: This script uses the output of "df", which may vary from
#  a system to another. Someday I will remove this dependence; by
#  now you can't use dev aliases nor Linux LABEL's; and your "df"
#  should print mount point on the last column. See code for details.
#  This means also that you can't boot from another disk and copy
#  a disk which is not mounted. I will fix this soon...
#
#  Now, it should work with non mounted partitions on Linux, but is
#  not tested.
#

case `uname` in
	SunOS)
		ARCH=SunOS-`arch`
		FSTAB=etc/vfstab
		;;
	Linux)
		ARCH=Linux-`arch`
		;;

	IRIX*)
		ARCH=IRIX-mips
		;;

	*)
		echo Error: unsupported system
		exit 1
		;;
esac


case $ARCH in
	SunOS-sun4)
		ORIG=/dev/dsk/c0t0d0s
		DEST=/dev/dsk/c0t1d0s
		GNU_TAR=gtar
		CPIO_FLAGS=""
		;;

	SunOS-i86pc)
		ORIG=/dev/dsk/c1d0s
		DEST=/dev/dsk/c2d0s
		GNU_TAR=/usr/sfw/bin/gtar
		CPIO_FLAGS=""
		;;

	IRIX*)
		ORIG=/dev/dsk/dks0d1s
		DEST=/dev/dsk/dks0d3s
		GNU_TAR=gtar
		CPIO_FLAGS=""
		;;

	Linux-i*86|Linux-x86_64)
		ORIG=/dev/sda
		DEST=/dev/sdb
		GNU_TAR=tar
		CPIO_FLAGS=--sparse
		;;
	*)
		echo Error: unsupported system
		exit 1
		;;
esac

CMD=`basename $0`

Usage()
{
	cat <<EOF
$CMD [-y|n][-pPbcvCDg -f from -t dest] [-e exclude]
	-y: do it
	-n (default): print commands, but do not execute anything.
	-c: check
	-v: verbose
	-P: force copy of partition table
	-p: force preserve of partition table on destination disk
	-b: force preserve of boot loader on destination disk
	-F: fix fstab on destination disk
	-C: use cpio instead of GNU tar
	-D: use dump instead of GNU tar
	-f: original (default $ORIG)
	-t: destination (default $DEST)
	-e: colon separated exclude list
	-g: grub's destination disk id (Linux/GRUB only)
	Use "host:dev" syntax for remote destination device via ssh

$CMD v0.7.5
$CMD will clone system disk on Linux, IRIX or Solaris host, including:

	* Copy partition table if is different (or if "-P" is used)
	* Enable swap partition (Only Linux. Solaris, IRIX don't need this step)
	* Copy all filesystems
	* Install boot loader

(c) SPDsoft <spd@daphne.cps.unizar.es>
EOF
	
	exit 1
}


ECHO=echo
#ECHO=
CHECK=
#CHECK=-c
VERB=
TOC=false
NOTOC=false
NOBOOT=false
FIXFSTAB=false
RSH="sh -c"

TAR=:
CPIO=false
DUMP=false

GRUB=1

EXCLUDE=""

FSTAB=etc/fstab

copy_fs()
{
	if expr "$2" : "/new/.*" >/dev/null 2>&1
	then
		:
	else
		echo "error: \"$2\" not under /new"
		exit 1
	fi

	if $TAR
	then

	$ECHO sh -c "( cd $1 && $GNU_TAR \
	--create \
	--sparse \
	--atime-preserve \
	--preserve \
	--read-full-records \
	--one-file-system \
	--totals \
	--file=- \
	. ) | $RSH \"( cd $2 && $GNU_TAR $VERB \
	--extract \
	--sparse \
	--read-full-records \
	--atime-preserve \
	--file=- \
	--preserve )\""

	else
	if $CPIO
	then
	
	$ECHO sh -c "(cd $1; find . -xdev -depth -print | $RSH \"cpio -pdm $CPIO_FLAGS $VERB $2)\""

	else
	
	case $ARCH in
		SunOS-*)
			$ECHO sh -c "ufsdump 0uf - $1 | $RSH \"(cd $2 && ufsrestore rf -; rm -f restoresymtable)\""
		;;
		Linux-i*86|Linux-x86_64)
			$ECHO sh -c "dump -0au -f - $1 | $RSH \"(cd $2 && /sbin/restore -r $VERB -f -; rm -f restoresymtable )\""
		;;
		IRIX*)
			case $FSTYPE in
			efs)
			echo "#### EFS dump not tested"
			$ECHO sh -c "dump -0u -f - $1 | $RSH \"(cd $2 && /sbin/restore xf -; rm -f restoresymtable )\""
			;;
			xfs)
			$ECHO sh -c "xfsdump -l 0 -p 60 -F - $1 | $RSH \"(cd $2 && /sbin/xfsrestore -F - .; rm -rf xfsrestorehousekeepingdir )\""
			;;
			esac
		;;
	esac
	fi
	fi
}

fix_fstab()
{
        $ECHO echo "## Fixing fstab"
               $ECHO $RSH "sed -e \"s,$ORIG,$DEST,g\" \
		-e \"s,$RORIG,$RDEST,g\" \
		/new/fstab > /new/$1/$FSTAB" < /dev/null
}


#
# Note: I recall having read cpio is faster for fs copies; but
# on a Linux 2.2.19/RH 6.2 system with SCSI disks:
#
# cpio: 10.48user 71.27system 7:24.40elapsed 18%CPU
# tar: 12.86user 79.25system 6:37.65elapsed 23%CPU
# (tar ignores sockets like /dev/log, which is a Bad Thing)
# dump: user    0m22.95s sys     1m20.69s real    7m52.55s
#
# and on a Sun E450 Solaris 8 system with SCSI disks:
#
# cpio: real  1:12:50.5 user       16.0 sys      5:28.3
# gtar: real  1:06:25.5 user       23.9 sys      6:28.2
# (tar ignores sockets and doors like etc/sysevent/sysevent_door)
# dump: real  1:05:18.1 user     1:51.5 sys      6:51.2
#

set -- `getopt FPpbDCvcynhg:f:t:e: $*`
for i in $*
do
	case $i in
	-h) Usage; shift;;
	-y) ECHO= ; shift;;
	-n) ECHO=echo ; shift;;
	-c) CHECK=-c ; shift;;
	-v) VERB=-v ; shift;;
	-b) NOBOOT=: ; shift;;
	-P) TOC=: ; shift;;
	-F) FIXFSTAB=: ; shift;;
	-p) NOTOC=: ; shift;;
	-C) TAR=false ; CPIO=: ; DUMP=false ; shift;;
	-D) TAR=false ; CPIO=false ; DUMP=: ; shift;;
	-f) ORIG=$2; shift; shift;;
	-t) DEST=$2; shift; shift;;
	-e) EXCLUDE=$2; shift; shift;;
	-g) GRUB=$2; shift; shift;;
	esac
done

RHOST=""
if expr "$DEST" : ".*:.*" >/dev/null 2>&1
then
	RHOST=`echo "$DEST" | sed -e 's/:.*$//'`
	DEST=`echo "$DEST" | sed -e 's/^.*://'`
	$ECHO echo "#### Using $DEST on remote host $RHOST"
	#RSH="ssh -xT $RHOST"
	RSH="ssh -xT -c blowfish -o 'compression no' $RHOST"
fi

[ -d /root/tmp ] || mkdir -p /root/tmp
set -e
cd /root/tmp
[ -d /new ] || mkdir -p /new
for f in 0 1 2 3 4 5 6 7 8 9
do
	[ -d /new/$f ] || mkdir -p /new/$f
done
[ -d /new/orig ] || mkdir -p /new/orig
for f in 0 1 2 3 4 5 6 7 8 9
do
	[ -d /new/orig/$f ] || mkdir -p /new/orig/$f
done
set +e

$ECHO echo "#### `date`"
RORIG=`echo ${ORIG} | sed -e "s:dsk:rdsk:"`
RDEST=`echo ${DEST} | sed -e "s:dsk:rdsk:"`

case $ARCH in
	SunOS-*)
		prtvtoc -h ${ORIG}2 | sed -e 's,[ ]*/.*,,' > orig.txt
	;;
	Linux-i*86|Linux-x86_64)
		if sfdisk -l $ORIG 2>&1 | fgrep "Use GNU Parted" > /dev/null
		then
			gdisk -l $ORIG | grep -v GUID > orig.txt
		else
			/sbin/fdisk -u -l $ORIG > orig.txt
		fi
	;;
	IRIX*)
		prtvtoc -h ${ORIG}0 > orig.txt
	;;
esac

if $NOTOC
then
	:
else
	$ECHO echo "#### Comparing $DEST $ORIG partition tables"

	case $ARCH in
	SunOS-*)
		$RSH "prtvtoc -h ${DEST}2" | sed -e 's,[ ]*/.*,,' > dest.txt
	;;

	Linux-i*86|Linux-x86_64)
		if sfdisk -l $ORIG 2>&1 | fgrep "Use GNU Parted" > /dev/null
		then
			gdisk -l $DEST | grep -v GUID | sed -e "s:$DEST:$ORIG:g" > dest.txt
		else
			$RSH "/sbin/fdisk -u -l $DEST" | sed -e "s:$DEST:$ORIG:g" > dest.txt
		fi
	;;

	IRIX*)
		$RSH "prtvtoc -h ${DEST}0" > dest.txt
	;;
	esac

	if $TOC
	then
		:>dest.txt
		$ECHO echo "## Forcing new TOC on $DEST"
	fi

	if cmp -s orig.txt dest.txt
	then
		:
	else
		$ECHO echo "#### Creating partition table on $DEST"
		$ECHO echo "## `diff orig.txt dest.txt`"

		case $ARCH in
			SunOS-*)
				$ECHO sh -c \
				"prtvtoc ${ORIG}2 | $RSH \"fmthard -s - ${RDEST}2\""
			;;
	
			Linux-i*86|Linux-x86_64)

				if sfdisk -l $ORIG 2>&1 | fgrep "Use GNU Parted" > /dev/null
				then
					$ECHO echo "#### GPT (GUID Partition Table) on $ORIG"
					if [ "$RHOST" != "" ]
					then
						$ECHO echo "#### GPT not supported on remote copy"
						exit 1
					fi
					$ECHO sgdisk -R=$DEST $ORIG
					$ECHO sgdisk -G $DEST
				else
					$ECHO sh -c \
 "/sbin/sfdisk -d $ORIG | sed -e s:$ORIG:$DEST:g | $RSH \"/sbin/sfdisk $DEST\""
					$ECHO $RSH "/sbin/sfdisk -V $DEST"

					SWAP=`$RSH "/sbin/fdisk -l $DEST" | awk '/swap$/ {print $1}'`
					if [ "$SWAP" != "" ]
					then
						$ECHO echo "## setting up swap area on $SWAP"
						$ECHO $RSH "/sbin/mkswap $CHECK $SWAP"
					fi
				fi
			;;
			IRIX*)
				echo "#### Sorry, not implemented by now"

# format: <partition>:<type>:<start>:<number of blocks>
#
#
#prtvtoc  -h /dev/dsk/${RORIG}vol |\
#sed -e -e '/volume/d' \
#-e 's/\([ ]*\)\([0-9][0-9]*\)\([    ]*\)\([a-z][a-z]*\)\([^0-9]*\)\([0-9][0-9]*\)\([^0-9]*\)\([0-9][0-9]*\)\([ ]*\)/\2:\4:\6:\8/' |\
#tr \\012 ";" | sed -e 's/;$//'
# /usr/sysadm/privbin/setDiskParts -d ${RDEST}vol -p "$format" -m

				exit 1
			;;
		esac
	fi
fi

EORIG=`echo "$ORIG" | sed -e 's,/,\\\/,g'`
(
#
# Output of this block must be partition id's (one number per line)
#
case $ARCH in
	SunOS-i86pc)
		df | sed -e "/($EORIG/!d" \
		-e 's:\(.*/dev/dsk/c.d.s\)\([0-9]\)\(.*\):\2:'
	;;

	SunOS-sun4)
		df | sed -e "/($EORIG/!d" \
		-e 's:\(.*/dev/dsk/c.t.d.s\)\([0-9]\)\(.*\):\2:'
	;;

	Linux-i*86|Linux-x86_64)
		case "$ORIG" in

			/dev/md*)
	df | sed -e "/^$EORIG/"'!d' -e 's:\(/dev/[a-z]*\)\([0-9]\)\(.*\):\2:'
			;;
		## braindamage: bash will try to find event "d"

			*)
		#
		# We will dump only Linux partitions (tag '83')
		#
	sed -ne 's/\/dev\/[^ ]*\([0-9]\).*83[ ][ ]*Linux/\1/p' orig.txt
			;;
		esac
	;;

	IRIX*)
		df | sed -e "/^$EORIG/!d" \
		-e 's:\(^/dev/dsk/dks.d.s\)\([0-9]\)\(.*\):\2:'
	;; 
esac
) |\
while read PART
do
	if [ "$EXCLUDE" != "" ]
	then
		if expr ":${EXCLUDE}" : "\([^:]*:\)\{0,\}\($PART\)\(:[^:]*\)\{0,\}" > /dev/null 2>&1 
		then
			$ECHO echo "#### Skipping filesystem $ORIG$PART"
			continue
		fi
	fi
	$ECHO echo "#### Clonating filesystem contents at $ORIG$PART"
	$ECHO echo "## creating filesystem $DEST$PART"


	case $ARCH in
		SunOS-*)
			OFS=`df | egrep -e "$ORIG$PART" | awk '{print $1}'`
			FSTYPE=`df -k $OFS | tail -1 | awk '{print $2}'`
			MOUNTED=:
			;;
		Linux-i*86|Linux-x86_64)

			OFS=`df | egrep -e "^$ORIG$PART" | awk '{print $6}'`
			if [ "_$OFS" = "_" ]
			then
				FSTYPE=`mount  -v -o ro $ORIG$PART /new/orig/$PART 2>&1 | sed -ne 's/\/dev.*type \([^ ]*\).*/\1/p'`
				MOUNTED=false
				OFS=/new/orig/$PART
			else
				FSTYPE=`mount | sed -n  "s/$EORIG$PART.*type \([^ ]*\) .*/\1/p"`
				$ECHO echo "## Warning: $ORIG$PART (${FSTYPE}) is mounted"
				MOUNTED=:
			fi

			;;
		IRIX*)
			SED=cat
			SEDR=""
			SEDA=""

			if df / | egrep ^/dev/root
			then
				MAJORMINOR=`ls -lL /dev/root | awk '{print $5}'`
				DEVICE=`ls -l /dev/dsk/d* | awk "/$MAJORMINOR/"' {print $NF}'`
				SEDR="s:^/dev/root:$DEVICE:"
				SEDA="-e"
				SED=sed
			fi

			if df /usr | egrep ^/dev/usr
			then
				MAJORMINOR=`ls -lL /dev/usr | awk '{print $5}'`
				DEVICE=`ls -l /dev/dsk/d* | awk "/$MAJORMINOR/"' {print $NF}'`
				SEDU="s:^/dev/usr:$DEVICE:"
				SEDA="-e"
				SED=sed
			fi

			OFS=`df | $SED $SEDA $SEDR $SEDA $SEDU |\
				egrep -e "^$ORIG$PART" | awk '{print $7}'`
			FSTYPE=`df -k $OFS | tail -1 | awk '{print $2}'`
			MOUNTED=:
			;;
	esac

	OPART=$PART

	case $ARCH in
		SunOS-*)
			$ECHO $RSH "echo y | newfs $VERB $DEST$PART" < /dev/null
			;;

		Linux-i*86|Linux-x86_64)
			
			if [ "$PART" = "0" ]
			then
				# special case for /dev/md0
				OPART=1
			fi

			if tune2fs -l $ORIG$PART 2>/dev/null |\
			egrep -e "^Filesystem features:.*extent" >/dev/null 2>&1
			then
				# ext4
				$ECHO $RSH "/sbin/mkfs $CHECK -t ext4 -v $DEST$OPART" < /dev/null

			else
				# ext3/ext2
		
				$ECHO $RSH "/sbin/mkfs $CHECK -v $DEST$OPART" < /dev/null

				if tune2fs -l $ORIG$PART 2>/dev/null |\
				egrep -e "^Filesystem features:.*has_journal" >/dev/null 2>&1
				then
					$ECHO echo "## creating journal $DEST$OPART"
					$ECHO $RSH "/sbin/tune2fs -j $DEST$OPART"
				fi

				LABEL=`/sbin/e2label $ORIG$PART`
				if [ "$LABEL" != "" ]
				then
					$ECHO echo "## creating label $DEST$OPART $LABEL"
					$ECHO $RSH "/sbin/e2label $DEST$OPART $LABEL"
				fi
			fi
			;;

		IRIX*)
			$ECHO $RSH "mkfs -t $FSTYPE $DEST$PART" < /dev/null
			;;
	esac

	$ECHO $RSH "mount  $DEST$OPART /new/$OPART" < /dev/null
	$ECHO echo "## copying $OFS ($ORIG$PART) to /new/$OPART"
	copy_fs $OFS /new/$OPART < /dev/null
	if [ -f $OFS/$FSTAB ]
	then
		if $FIXFSTAB
		then
			FSTAB_PART=$OPART
			$ECHO echo "#### Fixing fstab on $DEST part $FSTAB_PART"
			$ECHO $RSH "cp $OFS/$FSTAB /new/fstab" < /dev/null
			fix_fstab $FSTAB_PART
		fi
	fi
	$MOUNTED || $ECHO umount  /new/orig/$PART
	$ECHO $RSH "umount  /new/$OPART" < /dev/null
done

$ECHO echo "#### `date`"


if $NOBOOT
then
	:
else
	$ECHO echo "#### Installing boot loader on $DEST"

	case $ARCH in
		SunOS-i86pc)
			rel=`uname -r|sed -e 's,.*\.,,'`
			if [ "$rel" -ge 10 ]
			then
				$ECHO $RSH "/sbin/installgrub \
					/boot/grub/stage1 \
					/boot/grub/stage2 \
					${RDEST}0"

			else
				$ECHO $RSH "/usr/sbin/installboot \
				/usr/platform/`uname -i`/lib/fs/ufs/pboot \
				/usr/platform/`uname -i`/lib/fs/ufs/bootblk \
				${RDEST}2"
			fi
		;;
		SunOS-sun4)
			$ECHO $RSH "/usr/sbin/installboot \
			 /usr/platform/`uname -i`/lib/fs/ufs/bootblk ${RDEST}0"
		;;
		Linux-i*86|Linux-x86_64)
			$ECHO $RSH "mount ${DEST}1 /new/1"
			if [ -f /boot/grub/grub.conf ]
			then
				$ECHO echo "####     using grub"
				$ECHO $RSH "/sbin/grub --batch <<-EOF
				root (hd$GRUB,0)
				find /boot/grub/stage1
				setup (hd$GRUB)
				EOF"
			else
				$ECHO echo "####     using lilo"
				$ECHO $RSH "/sbin/lilo -v -r /new/1 -b $DEST"
			fi
			$ECHO $RSH "umount /new/1"
		;;
		IRIX*)
			#dvhtool -v list /dev/rdsk/dks0d1vh
			OVH=`echo $ORIG  | sed -e 's,/dsk/,/rdsk/,' -e 's/s$/vh/'`
			DVH=`echo $DEST  | sed -e 's,/dsk/,/rdsk/,' -e 's/s$/vh/'`
			cd /root/tmp || exit
			if [ "$RHOST" != "" ]
			then
				echo Remote header block edit not implemented on IRIX
			else
			for f in sgilabel sash ide symmon
			do
				$ECHO dvhtool -v get $f $f $OVH
				$ECHO dvhtool -v delete $f $DVH
				$ECHO dvhtool -v add $f $f $DVH
			done
			fi
			;;
	esac

fi
$ECHO echo "#### `date`"