Add scripterase-zfs.sh which does fulla / metadata erase and verification
This commit is contained in:
+206
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env bash
|
||||
# Fully erase a device or erase its partition-table and ZFS metadata.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
readonly DEFAULT_PASSES=10
|
||||
readonly MIB=$((1024 * 1024))
|
||||
readonly METADATA_MIB=4
|
||||
readonly METADATA_BYTES=$((METADATA_MIB * MIB))
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: sudo ./erase-zfs.sh [--wipe|--metadata] [--source zeroes|random] [--passes <count>] [--verify] /dev/<device>
|
||||
|
||||
Fully overwrites the target device ten times by default. --metadata overwrites
|
||||
only the first and last 4 MiB, covering partition tables and ZFS vdev labels.
|
||||
|
||||
Options:
|
||||
--wipe Fully overwrite the device (default).
|
||||
--metadata Overwrite only partition-table and ZFS metadata regions.
|
||||
--source <value> Write zeroes (default) or random data.
|
||||
--passes <count> Use a positive number of overwrite passes (default: 10).
|
||||
--verify Run signature, ZFS-label, and pool-discovery checks after wiping.
|
||||
-h, --help Show this help message.
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
printf 'Error: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
wipe_mode=wipe
|
||||
write_source=/dev/zero
|
||||
source_name=zeroes
|
||||
passes=$DEFAULT_PASSES
|
||||
verify=false
|
||||
device=
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--wipe)
|
||||
wipe_mode=wipe
|
||||
shift
|
||||
;;
|
||||
--metadata)
|
||||
wipe_mode=metadata
|
||||
shift
|
||||
;;
|
||||
--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
|
||||
;;
|
||||
--passes)
|
||||
[[ $# -ge 2 ]] || die '--passes requires a positive integer.'
|
||||
[[ $2 =~ ^[1-9][0-9]*$ ]] || die "invalid pass count '$2'; use a positive integer."
|
||||
passes=$2
|
||||
shift 2
|
||||
;;
|
||||
--verify)
|
||||
verify=true
|
||||
shift
|
||||
;;
|
||||
-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 [[ $verify == true ]]; then
|
||||
for command in wipefs zdb; do
|
||||
command -v "$command" >/dev/null 2>&1 || die "--verify requires '$command'."
|
||||
done
|
||||
if [[ $source_name == zeroes ]]; then
|
||||
command -v cmp >/dev/null 2>&1 || die "--verify with zeroes requires 'cmp'."
|
||||
fi
|
||||
fi
|
||||
|
||||
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 > 0 )) || die "'$device' has no addressable capacity."
|
||||
if [[ $wipe_mode == metadata ]]; then
|
||||
(( size_bytes >= 2 * METADATA_BYTES )) || die "'$device' is smaller than $((2 * METADATA_MIB)) MiB."
|
||||
fi
|
||||
|
||||
printf '\nWARNING: this permanently performs a %s wipe on:\n %s\nusing %s data for %d passes.\n\n' \
|
||||
"$wipe_mode" "$device" "$source_name" "$passes"
|
||||
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.'
|
||||
|
||||
wipe_metadata() {
|
||||
local last_offset=$((size_bytes - METADATA_BYTES))
|
||||
dd if="$write_source" of="$device" bs="$MIB" count="$METADATA_MIB" iflag=fullblock conv=fsync,notrunc status=none
|
||||
dd if="$write_source" of="$device" bs=1 count="$METADATA_BYTES" seek="$last_offset" iflag=fullblock conv=fsync,notrunc status=none
|
||||
}
|
||||
|
||||
wipe_full_device() {
|
||||
local whole_mib=$((size_bytes / MIB))
|
||||
local remainder=$((size_bytes % MIB))
|
||||
|
||||
if (( whole_mib > 0 )); then
|
||||
dd if="$write_source" of="$device" bs="$MIB" count="$whole_mib" iflag=fullblock conv=fsync,notrunc status=progress
|
||||
fi
|
||||
if (( remainder > 0 )); then
|
||||
dd if="$write_source" of="$device" bs=1 count="$remainder" seek=$((whole_mib * MIB)) iflag=fullblock conv=fsync,notrunc status=none
|
||||
fi
|
||||
}
|
||||
|
||||
for ((pass = 1; pass <= passes; pass++)); do
|
||||
printf 'Pass %d/%d...\n' "$pass" "$passes"
|
||||
if [[ $wipe_mode == wipe ]]; then
|
||||
wipe_full_device
|
||||
else
|
||||
wipe_metadata
|
||||
fi
|
||||
done
|
||||
sync
|
||||
printf 'Completed %d %s %s passes on %s.\n' "$passes" "$source_name" "$wipe_mode" "$device"
|
||||
|
||||
[[ $verify == true ]] || exit 0
|
||||
|
||||
verification_failed=false
|
||||
printf '\nVerification: checking for remaining signatures.\n'
|
||||
if wipefs --all --no-act "$device"; then
|
||||
printf 'PASS: wipefs found no signatures.\n'
|
||||
else
|
||||
printf 'FAIL: wipefs reported one or more signatures.\n' >&2
|
||||
verification_failed=true
|
||||
fi
|
||||
|
||||
printf '\nVerification: checking for readable ZFS labels.\n'
|
||||
if zdb -l "$device"; then
|
||||
printf 'FAIL: zdb found a readable ZFS label.\n' >&2
|
||||
verification_failed=true
|
||||
else
|
||||
printf 'PASS: zdb found no readable ZFS label.\n'
|
||||
fi
|
||||
|
||||
printf '\nVerification: checking whether ZFS discovers an importable pool near this device.\n'
|
||||
discovery_output=
|
||||
if ! discovery_output=$(zpool import -d "$(dirname -- "$device")" 2>&1); then
|
||||
:
|
||||
fi
|
||||
if grep -Eq '^[[:space:]]*pool:[[:space:]]' <<<"$discovery_output"; then
|
||||
printf '%s\n' "$discovery_output"
|
||||
printf 'FAIL: zpool found an importable pool, possibly using this device.\n' >&2
|
||||
verification_failed=true
|
||||
elif grep -Fq 'no pools available to import' <<<"$discovery_output"; then
|
||||
printf 'PASS: zpool found no importable pools in the device directory.\n'
|
||||
else
|
||||
printf '%s\n' "$discovery_output" >&2
|
||||
printf 'FAIL: zpool discovery did not produce a conclusive result.\n' >&2
|
||||
verification_failed=true
|
||||
fi
|
||||
|
||||
if [[ $wipe_mode == wipe && $source_name == zeroes ]]; then
|
||||
printf '\nVerification: comparing the full device with zeroes.\n'
|
||||
if cmp --silent /dev/zero "$device"; then
|
||||
printf 'PASS: the complete readable device contains zeroes.\n'
|
||||
else
|
||||
printf 'FAIL: the complete readable device is not all zeroes.\n' >&2
|
||||
verification_failed=true
|
||||
fi
|
||||
fi
|
||||
|
||||
[[ $verification_failed == false ]] || exit 1
|
||||
printf '\nVerification completed successfully.\n'
|
||||
Reference in New Issue
Block a user