#!/bin/ksh -u
# SCCS: @(#)UpdateBackupRFS	1.6 12/23/09 12:50:26
# Make a backup copy of the root file system.
# This is dangerous (because is wipes the old partition completely)
# and should be used with caution, this is why there is all sorts of
# sanity checking at the start of this script.
# The backup is made to the partition
# ADDW, June 2001, Copyright (c) Parliament Hill Computers

# This script zapps the current contents of the disk by reformatting it,
# it is faster & is guaranteed to get rid of all the rubbish.

# Vars:
# RfsMount -- mount point of standard RFS
# RfsDisk  -- /dev name of standard RFS
# RfsLabel -- what is in /etc/fstab as RfsDisk (may be of the form LABEL=xxx)
# BackupMount, BackupDisk, BackupLabel -- as for Rfs

PROGNAME=${0##*/}

# Print the arguments prefixed by PROGNAME and exit code 2
function Die
{
	echo "$PROGNAME: $*" >&2
	exit 2
}

function Checks1
{
	[[ ! -w / ]] &&
		Die "you must be root to run me"

	[[ ! -t 0 ]] &&
		Die "must be run interactively"

	# Find out where the backup RFS mount point is, the mount point must be /RFS.BAK
	# Don't nail the partition name in to get a small degree of portability.
	if (( $( awk -v rfs=$BackupMount '$1 ~ /^#/ {next} $2 == rfs {print}' /etc/fstab |
				wc -l ) != 1 ))
	then	Die "I can't see exactly 1 line in /etc/fstab containing '$BackupMount'"
	fi
}

# Checks after we know $BackupDisk.
function Checks2
{
	# Check mounted exactly once:
	if (( $( mount | grep -c "^$BackupDisk " ) != 1 ))
	then	Die "partition $BackupDisk doesn't seem to be mounted properly"
	fi

	# The backup disk, as yet, contains rubbish; do no checks ?
	[[ $Initialise = true ]] && return

	# Check for a few files that we should find there, ie not something else mounted in it's place
	for file in etc/passwd etc/Hacks bin/mkdir lib/security/pam_access.so etc/fstab
	do	[[ -f $BackupMount/$file ]] && continue
		Die "I can't see the file $BackupMount/$file"
	done

	for dir in lib/modules home etc/sysconfig tmp
	do	[[ -d $BackupMount/$dir ]] && continue
		Die "I can't see the directory $BackupMount/$dir"
	done

	# Check that we can see the device names:
	for dev in $BackupDisk $RfsDisk
	do	if [[ ! -b /$dev ]]
		then	Die "I can't see the block device /$dev"
		fi
	done

	# Check that we can see entries in /etc/fstab:
	for label in $BackupLabel $RfsLabel
	do 
		grep -q "^$label " /etc/fstab && continue

		Die "I can't see a line for $label in /etc/fstab"
	done
}

# Find where we are to copy to
function FindBackupFs
{
	BackupLabel=$( awk -v rfs=$BackupMount '$1 ~ /^#/ {next} $2 == rfs {print $1}' /etc/fstab )
	NormaliseDiskName "$BackupLabel" BackupDisk
	[[ $ShowPartition = 1 ]] && echo "Backup RFS Label='$BackupLabel' device=$BackupDisk"
}

# Find the name of the partition that / is mounted on
# Check that we know what this in in /etc/fstab - convert if we see a label
# Also set RfsFsType to the file system type.
function FindRFS
{
	if (( $( mount | awk '$3 == "/" {print}' | wc -l ) != 1 ))
	then	Die "$PROGNAME: The root file system isn't mounted exactly once ???"
	fi

	RfsLabel=$( awk -v rfs=/ '$1 ~ /^#/ {next} $2 == rfs {print $1}' /etc/fstab )
	NormaliseDiskName "$RfsLabel" RfsDisk
	RfsFsType=$( mount | awk '$3 == "/" {print $5}' )
	[[ $ShowPartition = 1 ]] && echo "Standard RFS Label='$RfsLabel' device=$RfsDisk"
}

# Check if a disk name is of the form LABEL=xxx, if it is convert
# to a device name (/dev/dddd).
# Arguments:
# 1 the disk device name or the label name -- out of fstab
# 2 the shell variable that will be assigned the name of the device that this corresponds to
# The point is that we try to preserve the names in /etc/fstab, but need to mkfs using a /dev name.
function NormaliseDiskName
{
	local Label="$1"
	local var=$2

	case "$Label" in
	/dev/*)	# No conversion needed
		dev="$Label"
		;;
	LABEL=*)# Get a /dev name
		dev=$( findfs "$Label" )
		if [[ -z $dev ]]
		then	Die "Cannot get a device name from '$Label'"
		fi
		;;
	*)	Die "Cannot normalise a disk name like '$Label'"
		;;
	esac

	eval $var=\$dev
}

# Print a usage message & exit the program.
# If an argument is given print that as an error and exit 1
function Usage {
	(( $# > 0 )) && echo "$PROGNAME: $*" >&2
	cat <<-!
		Make a backup copy of the root file system to /RFS.BAK.
		Usage: $PROGNAME [-options]
		Options:
		-d	Dont do anything, Display the commands that would be done
		-i	Initialise the RFS.BAK file system, no sanity checks, use -d first.
		-n	No checking is to be made on the partition being the right one
		-r	Recover/Rebuild from Backup to Standard RFS
		-s	Show the name of the partition that will be used, and stop
		-x	eXplain
		--help	help message
		Version: 1.6 12/23/09, latest from: http://www.phcomp.co.uk/Packages/UpdateBackupRFS.html
	!
	exit $#
}

# When we boot the system using the backup RFS we have some things in different places
# This function makes those small changes.
function HackBackupPartition
{
	local	NewFstab=$BackupMount/etc/fstab

	# Try to keep the formatting nice:
	$Echo perl -e '@a = split;s:\s/\s{1,7}\s: /'$RfsBackupMount' : if($a[0] eq "'$RfsLabel'");' -i -p $NewFstab

	$Echo perl -e '@a = split;s:\s'$BackupMount'\s: /        : if($a[0] eq "'$BackupLabel'");' -i -p $NewFstab

	# Make the backup RFS writable when it is mounted as the RFS
	# But if we *are* currently running on the backup RFS, swap the
	# other way round:
	[[ $BackupMode = ro ]] && ModeIs=rw || ModeIs=ro
	[[ $BackupMode = ro ]] && bakdisk=$RfsLabel || bakdisk=$BackupLabel
	$Echo perl -e '@a = split;s:\s'$ModeIs'\s: '$BackupMode' : if($a[0] eq "'$bakdisk'");' -i -p $NewFstab
}

# Only called in debugging.
# Echo to stderr in case stdout is down a pipe.
function Echo
{
	echo "$@" >&2
}

# **** Start Here ****

BackupMount=/RFS.BAK
RfsBackupMount=RFS.STD
RfsMount=/
BackupMode=rw
Nocheck=0
ShowPartition=0
Echo=
Initialise=false

# Parse options, recognise --help
while	[[ $# -ge $OPTIND ]] && eval A1=\$$OPTIND || A1=
	if [[ $A1 = --help ]]
	then	opt=x	# --help is -x
	else	getopts :dinrsx opt
	fi
do	case "$opt" in
	d)	Echo=Echo		;;
	i)	Initialise=true		;;
	n)	Nocheck=1		;;
	s)	ShowPartition=1		;;
	r)	BackupMount=/RFS.STD
		RfsBackupMount=RFS.BAK
		BackupMode=ro		;;
	x)	Usage ;;
	:)	Usage "Missing argument to option '$OPTARG'" ;;
	\?)	Usage "Unknown option '$OPTARG'" ;;
	*)	Usage "Internal program error, unrecognised argument '$opt'" ;;
	esac
done
shift $(( OPTIND - 1 ))

# Do checks if allowed to:
[[ $Nocheck = 0 ]] && Checks1

FindBackupFs

# Where is the current RFS ?
FindRFS

[[ $ShowPartition = 1 ]] && exit 0

# More checks:
[[ $Nocheck = 0 ]] && Checks2

echo "Standard RFS: label=$RfsLabel disk=$RfsDisk RfsFsType='$RfsFsType'"
echo "Backup RFS: label=$BackupLabel disk=$BackupDisk mount point=$BackupMount"

# Real work starts now:
$Echo umount $BackupDisk || exit

# Reformat with the same file system type as before:
case "$RfsFsType" in
	ext2)
		$Echo mkfs -t ext2 $BackupDisk || exit
		[[ $BackupLabel = LABEL=* ]] &&
			$Echo e2label $BackupDisk $BackupLabel
		;;

	ext3)
		$Echo mkfs -t ext2 -j $BackupDisk || exit
		[[ $BackupLabel = LABEL=* ]] &&
			$Echo e2label $BackupDisk $BackupLabel
		;;

	reiserfs)
		$Echo mkreiserfs $BackupDisk || exit
		;;

	*)	Die "File system type '$RfsFsType' is unknown by me"
		;;
esac

echo "Remounting $BackupDisk on $BackupMount"
$Echo mount -t $RfsFsType $BackupDisk $BackupMount || exit

echo "Copying to $BackupMount"
$Echo cd /
$Echo find . -mount | $Echo cpio -pduma $BackupMount

# Make changes that are needed for the new partition to work -- when we boot it
echo "Patch the backup RFS so that it will work"
HackBackupPartition

echo "Remounting $BackupMount read-only"
$Echo umount $BackupMount
$Echo mount -t $RfsFsType -o ro $BackupDisk $BackupMount || exit

echo "Done"

# end
