#!/bin/sh
#
# Setup script for dm-secdel
#
# (C) 2018,2019 <vt@altlinux.org>
# License: GPL-2.0-only
#

set -efu
PATH=/sbin:/usr/sbin:/bin:/usr/bin
secdeltab=${SECDELTAB:-/etc/secdeltab}

error() {
	echo "Error: $*"
	exit 1
}

usage() {
	echo "Usage:"
	echo "  secdelsetup <source-device> [mapper-name] [erase-modes]"
	echo "Options:"
	echo "  -d|--detach <device>    detach device"
	echo "  -D|--detach-all|--stop  detach all devices"
	echo "  -l|--list               list active device maps"
	echo "  -a|--all                list in different format"
	echo "  --uuid                  --all with UUID instead of dev name"
	echo "  --lsblk                 output in lsblk format"
	echo "  --start                 start devices from secdeltab"
	echo "  --save                  save active devices to secdeltab"
	echo "  -h|--help               this text"
	echo "erase-modes should be set to string of 0, 1, or R characters"
	echo "without any separators. Meaning of values are:"
	echo "  0 one pass of bit zeros"
	echo "  1 one pass of bit ones (i.e. 0xff bytes)"
	echo "  R one pass of random data"
	echo "For example, when erase-modes is set to \"1R0\" it will erase"
	echo "with three passes if ones, random, and zero patterns."
	echo "Default value for erase-modes is R (one pass of random)."
	exit
}

prog=${0##*/}
temp=$(getopt -n $prog -o d:,D,l,a,h -l uuid,lsblk,detach,detach-all,list,all,help,start,stop,save -- "$@") || usage
eval set -- "$temp"

check() {
	! dmsetup table --target secdel | grep -q '^No devices found'
}

list() {
	check && dmsetup table --target secdel | awk -F: '{print "/dev/mapper/"$1}'
}

list_lsblk() {
	check && lsblk -s $(list)
}

list_all() {
	local m=${1:-}

	check && dmsetup table --target secdel | while read devx x x x devn x opts; do
		dev=/dev/mapper/${devx%:}
		[ "$m" ] && [ "$m" != $dev ] && continue
		devidx=$(stat -L -c '%t:%T' $dev)
		while read ma mi z dv; do
			dn=$ma:$mi
			if [ "$devn" = "$dn" ]; then
				uuid=
				[ "${UUID:-}" ] && uuid=$(find_uuid_by_hex_minmaj $devidx)
				echo "$dev ${uuid:-/dev/$dv} $opts"
				dev=
				break
			fi
		done < /proc/partitions
		[ -n "$dev" ] && echo "$dev $devn"
	done
}

detach() {
	echo "detach $1"
	dmsetup remove "$1"
}

detach_all() {
	list | while read dev x; do
		detach $dev
	done
}

get_name() {
	local n fn

	for i in $(seq 0 6); do
		n=secdel$i
		fn=/dev/mapper/$n
		if [ ! -e $fn ]; then
			echo $n 
			return
		fi
	done
	return 1
}

find_uuid_by_hex_minmaj() {
	local devidx=$1

	find -L /dev/disk/by-uuid -xtype l -print0 |\
		xargs -0r stat -L -c '%t:%T %n' |\
		grep "^$devidx " |\
	while read mm dname; do
		basedname=${dname##*/}
		echo UUID=$basedname
		return
	done
}

find_dev() {
	local dev=$1
	local devbase=${dev##*/}
	local devid=${dev##*=}

	if [ $dev != $devid ]; then
		find /dev/disk -name $devid | head -1
		return
	fi
	if [ $dev != $devbase ]; then
		echo /dev/$devbase
		return
	fi
	echo $dev
}

attach() {
	local srcdev=${1:-}
	local mapname=${2:-}
	local opts=${3:-}

	[ -z "$srcdev" ]  && error "Specify source device"
	if [ -z "$mapname" ]; then
		mapname=$(get_name) || error "Can not generate secdel device name"
	fi
	modprobe -q dm-mod	|| :
	modprobe -q dm-secdel	|| :

	mapbase=${mapname##*/}
	mapname=/dev/mapper/$mapbase
	if [ -e $mapname ]; then
		echo "$mapname is already attached"
		return
	fi
	srcdev=$(find_dev $srcdev)
	sz=$(blockdev --getsz $srcdev)
	dmsetup create $mapbase --table "0 $sz secdel $srcdev 0 $opts" && \
	echo "$mapname is attached to $srcdev"
}

secdeltab_save() {
	UUID=true
	{
		echo "# <target name> <source device> <options>"
		list_all
	} > $secdeltab
}

secdeltab_start() {
	while read tgt src opts; do
		[ $(expr $tgt : "^#") = 1 ] && continue
		attach $src $tgt $opts || :
	done < $secdeltab
}

secdeltab_stop() {
	while read tgt src opts; do
		[ $(expr $tgt : ^# ) = 1 ] && continue
		detach $src || :
	done < $secdeltab
}

cmd=
while :; do
	case "${1:-}" in
		-d|--detach) detach $2; exit ;;
		-D|--detach-all) detach_all; exit ;;
		-h|--help) usage ;;
		-l|--list) list; exit ;;
		--uuid) UUID=true ;;
		-a|--all) cmd=list_all ;;
		-L|--lsblk) list_lsblk; exit ;;
		--start) secdeltab_start; exit ;;
		--save)  secdeltab_save; exit ;;
		--stop)  secdeltab_stop; exit ;;
		--) shift; break ;;
	esac
	shift
done

if [ $cmd ]; then
	eval $cmd "$@"
	exit
fi
attach "$@"
