#!/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 function get_device { # This is untested! 2025-12-21 file_device_with_partition="$1" case "$file_device_with_partition" in /dev/nvme*) # EG /dev/nvme0n1p1 -> /dev/nvme0n1 # shellcheck disable=SC2001 echo "$file_device_with_partition" | sed 's/p[0-9][0-9]*$//' ;; *) # shellcheck disable=SC2001 echo "$file_device_with_partition" | sed 's/[0-9]*$//' ;; esac } # shellcheck disable=SC2001 source_device=$(set -eu; get_device "$source_partition") # shellcheck disable=SC2001 destination_device=$(set -eu; get_device "$destination_partition") case "$source_device" in # This nvme pattern is a little sketchy; can it have double-digit numbers? /dev/sd[a-z]|/dev/hd[a-z]|/dev/nvme[0-9]n[0-9]) ;; *) echo "$0: unexpected derived source device spec: $source_device" 1>&2 all_good=False ;; esac case "$destination_device" in /dev/sd[a-z]|/dev/hd[a-z]|/dev/nvme[0-9]) ;; *) echo "$0: unexpected derived destination device spec: $destination_device" 1>&2 all_good=False ;; esac if ! [ -b "$source_device" ] && ! [ -c "$source_device" ] then echo "$0: $source_device does not exist or is not a special file" 1>&2 exit 1 fi if ! [ -b "$destination_device" ] && ! [ -c "$destination_device" ] then echo "$0: $destination_device does not exist or is not a special file" 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 # Do the data movement. # 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 & # Close stdout. exec >&- # And wait for the pipeline. wait