#!/bin/ksh

set +o sh
export PATH=/sbin:/bin:/usr/bin:/usr/sbin:/

POLL_INTERVAL=2
READY_INTERVAL=10
PROGRESS_INTERVAL=60
RESULT_INTERVAL=30
DD_IF=/dev/zero
WIPE_TIMEOUT=0

trap '' HUP INT QUIT TSTP

beep() {
	if [[ -c /dev/speaker ]] &&
	    print -nr -- 'T240 O4 L8 C' >/dev/speaker 2>/dev/null; then
		return
	fi
	print -n '\a'
}

beep_pattern() {
	typeset _count=$1 _interval=$2

	while ((_count > 0)); do
		beep
		_count=$((_count - 1))
		((_count > 0)) && sleep "$_interval"
	done
}

result_loop() {
	typeset _message=$1 _count=$2 _interval=$3

	while :; do
		echo
		echo "$_message"
		beep_pattern "$_count" "$_interval"
		sleep $RESULT_INTERVAL
	done
}

get_disks() {
	typeset _entry _disk _disks='' _oldifs=$IFS

	IFS=,
	for _entry in $(sysctl -n hw.disknames); do
		_disk=${_entry%%:*}
		case $_disk in
		sd+([0-9])|wd+([0-9]))
			_disks="${_disks}${_disks:+ }$_disk"
			;;
		esac
	done
	IFS=$_oldifs
	print -r -- "$_disks"
}

compare_disks() {
	typeset _disk

	_removed=''
	_removed_count=0
	_added_count=0
	for _disk in $_initial_disks; do
		case " $_current_disks " in
		*" $_disk "*) ;;
		*)
			_removed="${_removed}${_removed:+ }$_disk"
			_removed_count=$((_removed_count + 1))
			;;
		esac
	done
	for _disk in $_current_disks; do
		case " $_initial_disks " in
		*" $_disk "*) ;;
		*)
			_added_count=$((_added_count + 1))
			;;
		esac
	done
}

start_reader() {
	rm -f /tmp/bsddd.answer
	(
		typeset _answer
		IFS= read -r _answer </dev/console || _answer=
		if [[ $_answer == [yY] ]]; then
			print -n 'Wipe every attached sd/wd disk, including boot media? [y/N] ' \
			    >/dev/console
			IFS= read -r _answer </dev/console || _answer=
		fi
		print -r -- "$_answer" > /tmp/bsddd.answer
	) &
	_reader=$!
}

stop_reader() {
	kill "$_reader" 2>/dev/null || :
	wait "$_reader" 2>/dev/null || :
	rm -f /tmp/bsddd.answer
}

wipe_disk() {
	typeset _disk=$1 _geometry _sector_size _sectors
	typeset _sectors_per_mib _full _tail _raw

	(cd /dev && sh MAKEDEV "$_disk") || return 1
	_raw=/dev/r${_disk}c
	[[ -c $_raw ]] || return 1

	_geometry=$(disklabel -d "$_disk" 2>/dev/null) || return 1
	_sector_size=$(print -r -- "$_geometry" |
	    sed -n 's/^bytes\/sector:[[:space:]]*//p')
	_sectors=$(print -r -- "$_geometry" |
	    sed -n 's/^total sectors:[[:space:]]*\([0-9][0-9]*\).*/\1/p')

	case $_sector_size in
	''|*[!0-9]*) return 1 ;;
	esac
	case $_sectors in
	''|*[!0-9]*) return 1 ;;
	esac
	((_sector_size > 0 && _sectors > 0)) || return 1

	echo "$_disk: overwriting $_sectors sectors at $_sector_size bytes"

	if ((1048576 % _sector_size != 0)); then
		dd if="$DD_IF" of="$_raw" bs="$_sector_size" count="$_sectors" \
		    status=none || return 1
	else
		_sectors_per_mib=$((1048576 / _sector_size))
		_full=$((_sectors / _sectors_per_mib))
		_tail=$((_sectors % _sectors_per_mib))

		if ((_full > 0)); then
			dd if="$DD_IF" of="$_raw" bs=1m count="$_full" \
			    status=none || return 1
		fi
		if ((_tail > 0)); then
			dd if="$DD_IF" of="$_raw" bs="$_sector_size" \
			    seek="$((_full * _sectors_per_mib))" count="$_tail" \
			    status=none || return 1
		fi
	fi

	echo "$_disk: complete"
}

_initial_disks=$(get_disks)
if [[ -z $_initial_disks ]]; then
	result_loop 'ERROR: no eligible sd(4) or wd(4) disks were found.' 3 1
fi

echo
echo "bsddd.rd is resident in memory."
echo "Detected disks: $_initial_disks"
echo "Remove exactly one disk to begin automatically."
_timeout_deadline=0
if ((WIPE_TIMEOUT > 0)); then
	echo "Automatic all-disk wipe in $WIPE_TIMEOUT seconds."
	_timeout_deadline=$((SECONDS + WIPE_TIMEOUT))
fi
echo "Proceed without detected device removal? [y/N]"

start_reader
_ready_elapsed=$READY_INTERVAL
_last_disks=$_initial_disks
_override=no

while :; do
	_current_disks=$(get_disks)
	compare_disks
	if ((_removed_count == 1 && _added_count == 0)); then
		stop_reader
		_disks=$_current_disks
		echo
		echo "Removed disk: $_removed"
		break
	fi

	if [[ $_current_disks != "$_last_disks" ]]; then
		echo "Waiting; current disks: ${_current_disks:-none}"
		_last_disks=$_current_disks
	fi

	if [[ -s /tmp/bsddd.answer ]]; then
		_answer=$(</tmp/bsddd.answer)
		wait "$_reader" 2>/dev/null || :
		rm -f /tmp/bsddd.answer
		if [[ $_answer == [yY] ]]; then
			_override=console
			_disks=$(get_disks)
			break
		fi
		echo "Proceed without detected device removal? [y/N]"
		start_reader
	elif ! kill -0 "$_reader" 2>/dev/null; then
		wait "$_reader" 2>/dev/null || :
		start_reader
	fi

	if ((_timeout_deadline > 0 && SECONDS >= _timeout_deadline)); then
		stop_reader
		_override=timeout
		_disks=$(get_disks)
		break
	fi

	if ((_ready_elapsed >= READY_INTERVAL)); then
		if ((_timeout_deadline > 0)); then
			beep_pattern 4 0.15
		else
			beep_pattern 3 0.25
		fi
		_ready_elapsed=0
	fi
	_sleep=$POLL_INTERVAL
	if ((_timeout_deadline > 0)); then
		_remaining=$((_timeout_deadline - SECONDS))
		((_remaining < _sleep)) && _sleep=$_remaining
		((_sleep > 0)) || continue
	fi
	sleep "$_sleep"
	_ready_elapsed=$((_ready_elapsed + _sleep))
done

if ! _softraid_output=$(bioctl softraid0 2>/dev/null); then
	result_loop 'ERROR: cannot inspect softraid volumes.' 3 1
fi
_softraid_volumes=$(print -r -- "$_softraid_output" |
    sed -n 's/^softraid0.*\(sd[0-9][0-9]*\).*/\1/p')
for _volume in $_softraid_volumes; do
	echo "$_volume: detaching softraid volume"
	if ! bioctl -d "$_volume" >/dev/null 2>&1; then
		result_loop "ERROR: cannot detach softraid volume: $_volume" 3 1
	fi
done

_wipe_disks=
for _disk in $_disks; do
	case " $_softraid_volumes " in
	*" $_disk "*) ;;
	*) _wipe_disks="${_wipe_disks}${_wipe_disks:+ }$_disk" ;;
	esac
done
_disks=$_wipe_disks
if [[ -z $_disks ]]; then
	result_loop 'ERROR: no eligible sd(4) or wd(4) disks remain.' 3 1
fi

_status_dir=/tmp/bsddd.status
if ! rm -rf "$_status_dir" || ! mkdir "$_status_dir"; then
	result_loop 'ERROR: cannot initialize wipe status storage.' 3 1
fi
for _disk in $_disks; do
	_status_file=$_status_dir/$_disk
	_status_ok=${_status_file}.ok
	_status_failed=${_status_file}.failed
	if ! print -r -- running > "$_status_file" ||
	    ! print -r -- ok > "$_status_ok" ||
	    ! print -r -- failed > "$_status_failed" ||
	    [[ $(<"$_status_file") != running ]] ||
	    [[ $(<"$_status_ok") != ok ]] ||
	    [[ $(<"$_status_failed") != failed ]]; then
		result_loop 'ERROR: cannot initialize wipe status storage.' 3 1
	fi
done

echo
if [[ $_override == console ]]; then
	echo "Console override confirmed."
elif [[ $_override == timeout ]]; then
	echo "Wipe timeout expired; all attached sd/wd disks selected."
fi
echo "Wiping disks in parallel: $_disks"
echo "Wipe input: $DD_IF"
echo "DO NOT POWER OFF."
beep_pattern 2 1

_jobs=
_total=0
for _disk in $_disks; do
	(
		_status_file=$_status_dir/$_disk
		if wipe_disk "$_disk"; then
			mv "${_status_file}.ok" "$_status_file" || exit 2
			exit 0
		else
			mv "${_status_file}.failed" "$_status_file" || exit 2
			exit 1
		fi
	) &
	_jobs="${_jobs}${_jobs:+ }$!:$_disk"
	_total=$((_total + 1))
done

_elapsed=0
_status_error=no
while :; do
	_done=0
	for _job in $_jobs; do
		_pid=${_job%%:*}
		_disk=${_job#*:}
		_status_file=$_status_dir/$_disk
		if [[ ! -r $_status_file ]]; then
			_status_error=yes
			break
		fi
		_status=$(<"$_status_file")
		case $_status in
		ok|failed)
			_done=$((_done + 1))
			;;
		running)
			if ! kill -0 "$_pid" 2>/dev/null; then
				_status_error=yes
				break
			fi
			;;
		*)
			_status_error=yes
			break
			;;
		esac
	done
	[[ $_status_error == yes ]] && break
	((_done == _total)) && break

	sleep $POLL_INTERVAL
	_elapsed=$((_elapsed + POLL_INTERVAL))
	if ((_elapsed >= PROGRESS_INTERVAL)); then
		echo "Wipe in progress: $_done/$_total disks complete."
		beep_pattern 1 0
		_elapsed=0
	fi
done

if [[ $_status_error == yes ]]; then
	echo "ERROR: wipe status unavailable; waiting for disk jobs."
fi
for _job in $_jobs; do
	_pid=${_job%%:*}
	wait "$_pid" 2>/dev/null || :
done
sync

if [[ $_status_error == yes ]]; then
	result_loop 'ERROR: wipe status unavailable.' 3 1
fi

_failed=
for _disk in $_disks; do
	_status_file=$_status_dir/$_disk
	[[ $(<"$_status_file") == ok ]] ||
	    _failed="${_failed}${_failed:+ }$_disk"
done

if [[ -n $_failed ]]; then
	result_loop "ERROR: wipe failed: $_failed" 3 1
fi

result_loop 'WIPE COMPLETE: all selected disks were overwritten.' 5 0.25
