#!/bin/bash set -eu set -o pipefail source_partition="" destination_partition="" blocksize=$((1024 * 1024)) function usage { retval="$1" case "$retval" in 0) ;; *) exec 1>&2 ;; esac echo "Usage: $0 --source-partition /dev/sda1 --destination-partition /dev/sdb1 --blocksize $blocksize" echo echo "Please note that this script is intended for partitions, not entire disks. So /dev/sda1 is" echo "supported, but /dev/sda is not." exit "$retval" } while [ "$#" -ge 1 ] do case "$1" in --source-partition) source_partition="$2" shift ;; --destination-partition) destination_partition="$2" shift ;; --blocksize) blocksize="$2" shift ;; --help|-h) usage 0 ;; *) echo "$0: unrecognized option: $1" 1>&2 usage 1 ;; esac shift done all_good=True case "$source_partition" in "") echo "$0: --source-partition is a required option" 1>&2 all_good=False ;; esac case "$destination_partition" in "") echo "$0: --destination-partition is a required option" 1>&2 all_good=False ;; esac if [ "$source_partition" = "$destination_partition" ] then echo "$0: I cannot safely copy a partition back to itself" 1>&2 all_good=False fi # shellcheck disable=2050 if [ 1 = 2 ] then # We don't do these checks, bcause there are /dev/hd*, /dev/sd* and /dev/nvme* - and there could easily be more in the future case "$source_partition" in /dev/sd[a-z][1-9]*) ;; *) echo "$0: unexpected source partition spec: $source_partition" 1>&2 all_good=False ;; esac case "$destination_partition" in /dev/sd[a-z][1-9]*) ;; *) echo "$0: unexpected destination partition spec: $destination_partition" 1>&2 all_good=False ;; esac fi # shellcheck disable=SC2001 source_device=$(set -eu; echo "$source_partition" | sed 's/[0-9]*$//') # shellcheck disable=SC2001 destination_device=$(set -eu; echo "$destination_partition" | sed 's/[0-9]*$//') case "$source_device" in /dev/sd[a-z]) ;; *) echo "$0: unexpected derived source device spec: $source_device" 1>&2 all_good=False ;; esac case "$destination_device" in /dev/sd[a-z]) ;; *) echo "$0: unexpected derived destination device spec: $destination_device" 1>&2 all_good=False ;; esac if ! [ -e "$source_device" ] then echo "$0: $source_device does not exist" 1>&2 exit 1 fi if ! [ -e "$destination_device" ] then echo "$0: $destination_device does not exist" 1>&2 exit 1 fi # shellcheck disable=SC2001 source_partition_no=$(set -eu; echo "$source_partition" | sed 's/^.*\([0-9][0-9]*\)$/\1/') # shellcheck disable=SC2001 destination_partition_no=$(set -eu; echo "$source_partition" | sed 's/^.*\([0-9][0-9]*\)$/\1/') function size_in_bytes { device="$1" partition_no="$2" parted "$device" unit B print | awk "\$1 == \"$partition_no\" { print \$4 }" | sed 's/B$//' } source_partition_size=$(size_in_bytes "$source_device" "$source_partition_no") destination_partition_size=$(size_in_bytes "$destination_device" "$destination_partition_no") if [ "$source_partition_size" -eq "$destination_partition_size" ] then echo "$0: Good, the sizes match" 1>&2 elif [ "$source_partition_size" -lt "$destination_partition_size" ] then echo "$0: Good enough: the source partition is smaller than the destination partition" 1>&2 else echo "$0: Not good: The source partition is larger than the destination partition" 1>&2 exit 1 fi num_source_blocks=$((source_partition_size / blocksize)) if [ $((num_source_blocks * blocksize)) != source_partition_size ] then echo "$0: Not good: blocksize does not evenly divide size of source partition" exit 1 fi function is_mounted { # Untested function. partition="$1" if mount | grep -q "^$partition " then # Return shell-true return 0 else # Return shell-false return 1 fi } if is_mounted "$source_partition" then echo "0: source partition $source_partition appears to be mounted" 1>&2 exit 1 fi if is_mounted "$destination_partition" then echo "0: destination partition $destination_partition appears to be mounted" 1>&2 exit 1 fi case "$all_good" in True) ;; False) echo "$0: one or more items in preflight check failed" 1>&2 usage 1 ;; *) echo "$0: internal error: \$all_good is neither True nor False: $all_good" 1>&2 usage 1 ;; esac # Note that we use num_source_blocks for the destination, because that's how many blocks we're transferring. dd if="$source_partition" bs="$blocksize" count="$num_source_blocks" iflag=fullblock | \ gprog --size-estimate "$source_partition_size" | \ dd of="$destination_partition" bs="$blocksize" count="$num_source_blocks" iflag=fullblock