Files
zfs-erase/erase-zfs-metadata.sh

108 lines
3.4 KiB
Bash

#!/usr/bin/env bash
# Overwrite partition-table and ZFS vdev-label metadata at both ends of a device.
set -euo pipefail
readonly PASSES=10
readonly MIB=$((1024 * 1024))
readonly WIPE_MIB=4
readonly WIPE_BYTES=$((WIPE_MIB * MIB))
usage() {
cat <<'EOF'
Usage: sudo ./erase-zfs-metadata.sh [--source zeroes|random] /dev/<device>
Overwrites the first and last 4 MiB of the specified block device ten times.
This destroys MBR/GPT partition tables and all conventional ZFS vdev labels.
The device must not be mounted or part of an imported ZFS pool.
Options:
--source zeroes Write zeroes from /dev/zero (default).
--source random Write random data from /dev/urandom.
-h, --help Show this help message.
EOF
}
die() {
printf 'Error: %s\n' "$*" >&2
exit 1
}
write_source=/dev/zero
source_name=zeroes
device=
while [[ $# -gt 0 ]]; do
case $1 in
--source)
[[ $# -ge 2 ]] || die '--source requires zeroes or random.'
case $2 in
zeroes)
write_source=/dev/zero
source_name=zeroes
;;
random)
write_source=/dev/urandom
source_name=random
;;
*)
die "invalid source '$2'; use zeroes or random."
;;
esac
shift 2
;;
-h|--help)
usage
exit 0
;;
-*)
die "unknown option '$1'."
;;
*)
[[ -z $device ]] || die 'specify exactly one block device.'
device=$1
shift
;;
esac
done
[[ -n $device ]] || { usage >&2; exit 2; }
[[ $EUID -eq 0 ]] || die 'run as root (for example, with sudo).'
[[ -b $device ]] || die "'$device' is not a block device."
[[ -r $write_source ]] || die "'$write_source' is not readable."
for command in blockdev dd findmnt lsblk zpool; do
command -v "$command" >/dev/null 2>&1 || die "required command '$command' was not found."
done
if findmnt --noheadings --source "$device" >/dev/null 2>&1; then
die "'$device' is mounted; unmount it before continuing."
fi
if lsblk --noheadings --raw --output MOUNTPOINT "$device" | grep -q '[^[:space:]]'; then
die "'$device' or one of its children is mounted; unmount it before continuing."
fi
if zpool status -P 2>/dev/null | grep -Fq -- "$device"; then
die "'$device' belongs to an imported ZFS pool; export or detach it before continuing."
fi
size_bytes=$(blockdev --getsize64 "$device")
(( size_bytes >= 2 * WIPE_BYTES )) || die "'$device' is smaller than $((2 * WIPE_MIB)) MiB."
last_offset=$((size_bytes - WIPE_BYTES))
printf '\nWARNING: this permanently destroys partition tables and ZFS metadata on:\n %s\nusing %s data.\n\n' "$device" "$source_name"
lsblk --output NAME,SIZE,TYPE,MOUNTPOINTS "$device"
read -r -p "Type exactly 'ERASE $device' to continue: " confirmation
[[ $confirmation == "ERASE $device" ]] || die 'confirmation did not match; no data was changed.'
for ((pass = 1; pass <= PASSES; pass++)); do
printf 'Pass %d/%d...\n' "$pass" "$PASSES"
dd if="$write_source" of="$device" bs="$MIB" count="$WIPE_MIB" conv=fsync,notrunc status=none
dd if="$write_source" of="$device" bs=1 count="$WIPE_BYTES" seek="$last_offset" conv=fsync,notrunc status=none
done
sync
printf 'Completed %d %s overwrite passes on %s.\n' "$PASSES" "$source_name" "$device"