#!/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 /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. EOF } die() { printf 'Error: %s\n' "$*" >&2 exit 1 } [[ $# -eq 1 ]] || { usage >&2; exit 2; } device=$1 [[ $EUID -eq 0 ]] || die 'run as root (for example, with sudo).' [[ -b $device ]] || die "'$device' is not a block device." 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\n\n' "$device" 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=/dev/zero of="$device" bs="$MIB" count="$WIPE_MIB" conv=fsync,notrunc status=none dd if=/dev/zero of="$device" bs=1 count="$WIPE_BYTES" seek="$last_offset" conv=fsync,notrunc status=none done sync printf 'Completed %d overwrite passes on %s.\n' "$PASSES" "$device"