#!/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/ 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"