Cloning a disk using dump/restore

OpenBSD edition. A script that will produce a copy of an original disk, will run installboot(8) on it and replace the disk UID in /etc/fstab with the clone’s UID. The purpose is to produce a copy of the current system disk ready to replace it by simply changing the boot device or replacing the original in case of failure.

Intended for live disks, an alternative to cloning a disk in OpenBSD using dd.

EXECUTING IT WITHOUT UNDERSTANDING WHAT IT DOES WILL LIKELY RESULT IN DATA LOSS, HAIR LOSS, LOSS OF FLOSS AND POSSIBLE OTHER KINDS OF LOSS.

There are two failsafes built in, firstly it won’t do anything unless run with ‘yarly’ as the first parameter, second it will ask for confirmation warning that all data on the destination disk will be lost, unless run with ‘cron’ as the second parameter.

It’s not ready to run as-is, variables will need to be properly set before executing.

It will erase MBR and all partitioning data on DST_DISK and will copy the scheme from SRC_DISK over. My disks are the same size, DST_DISK can’t be of lower size than SRC_DISK, obviously, if it’s bigger some space will be left unused. After that it will run newfs on DST_DISK on all partitions of type 4.2BSD, everything else, like swap, will be left untouched. Each such partition will be mounted to DST_MOUNT and the data from the corresponding partition on SRC_DISK will be dumped over. If the current partition is the root partition and after dumping/restoring there’s /boot on it, the script will run installboot(8). If there’s an /etc/fstab file, it will replace all instances of SRC_DISK’s duid in it with DST_DISK’s. This should leave the disc ready to be booted with a working system on it.

Most commands the script runs are redirected to STDOUT, changing this variable to /dev/null will make the commands basically silent, but stderr is not redirected, which is probably a good thing.

It is entirely possible that something will go amiss with non-standard partitioning schemes, it does make a few assumptions, like /etc being part of / for example, or that the partitions to be backed up are already mounted.

#!/bin/sh

# makes a few assumptions, standard mount points
# will run installboot to write the boot sector
# will modify /etc/fstab on root partition,
# replacing source disk UID with DST_DISK

# CHANGE THESE VARIABLES
SRC_DISK="sd0" # disk to be cloned
DST_DISK="sd2" # disk to clone *to*
DST_MOUNT="/mnt/clonebak" # where the partition written to will be mounted
# make it /dev/stdout to see details, /dev/null to hide them
STDOUT="/dev/stdout"

# don't run unless 'yarly' is passed as parameter
if [[ $1 != 'yarly' ]] ; then
    echo 'orly?'
    exit
fi

# if not run with 'cron' as the second parameter, ask for confirmation
if [[ $2 != 'cron' ]] ; then
    print -n "This will destroy everything on *$DST_DISK*. Are you sure? (yes/no) "
    read sure
    if [[ $sure != 'yes' ]] ; then
        exit
    fi
fi

# create mountpoint for clone
echo "creating $DST_MOUNT..."
mkdir -p $DST_MOUNT > $STDOUT
if [[ $? != 0 ]] ; then
    echo "  $DST_MOUNT creation failed, aborting"
    exit
fi

# can dump only if partition is mounted, any partition that isn't will be
# skipped if there's no mounted source partition, no reason to do anything
mount | grep /dev/$SRC_DISK > $STDOUT
if [[ $? != 0 ]] ; then
    echo "no partitions on $SRC_DISK seem to be mounted, aborting"
    exit
fi

# make sure destination disk isn't mounted anywhere,
# or script will fail unpredictably
mount | grep /dev/$DST_DISK > $STDOUT
if [[ $? != 1 ]] ; then
    echo "looks like $DST_DISK is mounted, or there was an error, aborting"
    exit
fi

# make sure the mount point is free
mount | grep $DST_MOUNT
if [[ $? != 1 ]] ; then
    echo "$DST_MOUNT is mounted or there was an error, aborting"
    exit
fi

# real work starts here
# copy partitioning scheme to clone
echo "reinitializing $DST_DISK..."
fdisk -iy $DST_DISK > $STDOUT
disklabel -d $DST_DISK > $STDOUT
echo "partitioning..."
disklabel $SRC_DISK > - | (disklabel -R $DST_DISK -)

# get a list of all partitions on SRC_DISK and iterate through them
PARTITIONS=`disklabel $SRC_DISK | grep ^\ \ [a-p] | sed s/\:.*// | sed s/\ \ //`
for i in $PARTITIONS; do
    PARTINFO=`disklabel $SRC_DISK | grep \ \ $i\:`
    if [[ $PARTINFO != *4.2BSD* ]] ; then
        continue
    fi
    MOUNTDIR=`disklabel $SRC_DISK | grep \ \ $i\: | sed s/^.*#\ //`

    # before being able to dump a partition we need to check if it's
    # mounted and where. If it isn't, skip it, don't try to mount it,
    # there might be a reason why it's down
    echo "checking $SRC_DISK$i mount point..."
    mount | grep \/dev\/$SRC_DISK$i > $STDOUT
    if [[ $? != 0 ]] ; then
        # not mounted, don't dump
        echo "  $SRC_DISK$i isn't mounted, won't be backed up"
        continue
    fi
    SRC_MOUNT=`mount | grep \/dev\/$SRC_DISK$i | sed s/^.*$SRC_DISC$i\ on\ // | sed s/\ type\ .*//`
    echo " ..found $SRC_MOUNT"

    # install a new file system and mount it
    echo "formatting $DST_DISK$i..."
    newfs $DST_DISK$i > $STDOUT

    echo "mounting /dev/$DST_DISK$i to $DST_MOUNT..."
    mount /dev/$DST_DISK$i $DST_MOUNT > $STDOUT
    if [[ $? != 0 ]] ; then
        echo "  mount failed, skipping $DST_DISK$i"
        continue
    fi

    # finally, the backup
    echo "dumping $SRC_MOUNT onto $DST_MOUNT..."
    /sbin/dump -0au -f - $SRC_MOUNT | ( cd $DST_MOUNT ; /sbin/restore -rf - )

    # if we just dumped the root partition
    # let's install boot sector
    if [[ $SRC_MOUNT = "/" && -f $DST_MOUNT/boot ]] ; then
        echo "writing boot on $DST_DISK..."
        /usr/mdec/installboot $DST_MOUNT/boot /usr/mdec/biosboot $DST_DISK > $STDOUT
    fi
    # and change duid in fstab
    if [[ $SRC_MOUNT = "/" && -f $DST_MOUNT/etc/fstab ]] ; then
        echo "replacing duid in $DST_MOUNT/etc/fstab..."
        # make a copy of fstab, run sed on the copy with output to original
        cp $DST_MOUNT/etc/fstab $DST_MOUNT/etc/fstab.bak
        SRC_DUID=`disklabel $SRC_DISK | grep ^duid: | sed s/duid\:\ //`
        DST_DUID=`disklabel $DST_DISK | grep ^duid: | sed s/duid\:\ //`
        sed s/$SRC_DUID/$DST_DUID/ $DST_MOUNT/etc/fstab.bak > $DST_MOUNT/etc/fstab
     fi

    umount $DST_MOUNT > $STDOUT
    if [[ $? != 0 ]] ; then
        echo "there was an error unmounting $DST_MOUNT, aborting"
        exit
    fi

done