#!/bin/sh
# Actions of the units generated from fstab and crypttab, see svctl(8):
#   fstab-unit mount SPEC MOUNTPOINT FSTYPE OPTIONS PASS DEVICE-TIMEOUT [CRYPT-UNIT]
#   fstab-unit umount MOUNTPOINT
#   fstab-unit check MOUNTPOINT
#   fstab-unit swapon SPEC OPTIONS DEVICE-TIMEOUT
#   fstab-unit swapoff SPEC
#   fstab-unit crypt-open NAME SOURCE KEY OPTIONS DEVICE-TIMEOUT
#   fstab-unit crypt-close NAME
# A source UUID=, LABEL=, PARTUUID=, PARTLABEL= is resolved by blkid, a source
# /dev/... is used as is; DEVICE-TIMEOUT in seconds, 0 waits without limit.
set -u

FAILED=/run/svctl/failed

die() {
	echo "fstab-unit: $*" >&2
	exit 1
}

# true if SPEC names a device: UUID=, LABEL=, PARTUUID=, PARTLABEL=, /dev/...
isdev() {
	case $1 in
	UUID=* | LABEL=* | PARTUUID=* | PARTLABEL=* | /dev/*) return 0 ;;
	esac
	return 1
}

# device node of the device SPEC, a tag resolved by blkid without its cache;
# empty while it is missing
resolve() {
	case $1 in
	/dev/*) [ ! -e "$1" ] || echo "$1" ;;
	*) blkid -c /dev/null -l -o device -t "$1" 2>/dev/null ;;
	esac
}

# Prints the device node of the device SPEC, polling for it at most SECONDS
# (0: no limit).
wait_device() {
	n=$(($2 * 10))
	while :; do
		_wd=$(resolve "$1")
		[ -z "$_wd" ] || break
		[ "$2" = 0 ] || [ "$n" -gt 0 ] || die "$1 did not appear within $2 s"
		n=$((n - 1))
		sleep 0.1
	done
	echo "$_wd"
}

has() { case ,$2, in *,"$1",*) return 0 ;; esac; return 1; }
optval() { echo ",$2," | sed -n "s/.*,$1=\([^,]*\),.*/\1/p"; }

do_mount() {
	[ $# -ge 6 ] || die "mount: missing arguments"
	spec=$1 mp=$2 fstype=$3 opts=$4 pass=$5 tmo=$6 cunit=${7:-}
	if [ -n "$cunit" ] && [ -e "$FAILED/$cunit" ]; then
		die "$cunit failed ($(cat "$FAILED/$cunit")), not mounting $mp"
	fi
	dev=''
	if isdev "$spec"; then
		dev=$(wait_device "$spec" "$tmo") || exit 1
	fi
	if mountpoint -q "$mp"; then
		exit 0
	fi
	if [ "$pass" -gt 0 ] && [ -n "$dev" ]; then
		fsck -a -T "$dev"
		r=$?
		[ "$r" -le 1 ] || die "fsck of $dev ended with $r"
	fi
	mkdir -p "$mp" || die "cannot create $mp"
	if [ "$fstype" = auto ]; then
		mount -o "$opts" "${dev:-$spec}" "$mp"
	else
		mount -t "$fstype" -o "$opts" "${dev:-$spec}" "$mp"
	fi || die "mount of ${dev:-$spec} on $mp failed"
}

do_umount() {
	mountpoint -q "$1" || exit 0
	umount "$1"
}

do_swapon() {
	[ $# -ge 3 ] || die "swapon: missing arguments"
	dev=$1
	if isdev "$1"; then
		dev=$(wait_device "$1" "$3") || exit 1
	fi
	target=$(readlink -f "$dev")
	grep -q "^$target " /proc/swaps && exit 0
	set -- "$dev" "$2"
	pri=$(optval pri "$2")
	if [ -n "$pri" ]; then
		swapon -p "$pri" "$1"
	else
		swapon "$1"
	fi
}

do_swapoff() {
	dev=$1
	if isdev "$1"; then
		dev=$(resolve "$1")
		[ -n "$dev" ] || exit 0
	fi
	target=$(readlink -f "$dev")
	grep -q "^$target " /proc/swaps || exit 0
	swapoff "$dev"
}

do_crypt_open() {
	[ $# -ge 5 ] || die "crypt-open: missing arguments"
	name=$1 src=$2 key=$3 opts=$4 tmo=$5
	[ -e "/dev/mapper/$name" ] && exit 0
	dev=$src
	if isdev "$src"; then
		dev=$(wait_device "$src" "$tmo") || exit 1
	fi
	set -- open --type luks
	has discard "$opts" && set -- "$@" --allow-discards
	{ has readonly "$opts" || has read-only "$opts"; } && set -- "$@" --readonly
	v=$(optval tries "$opts") && [ -n "$v" ] && set -- "$@" --tries "$v"
	v=$(optval timeout "$opts") && [ -n "$v" ] && set -- "$@" --timeout "${v%s}"
	v=$(optval keyfile-offset "$opts") && [ -n "$v" ] && set -- "$@" --keyfile-offset "$v"
	v=$(optval keyfile-size "$opts") && [ -n "$v" ] && set -- "$@" --keyfile-size "$v"
	v=$(optval header "$opts") && [ -n "$v" ] && set -- "$@" --header "$v"
	case $key in
	'' | none | -) ;;
	*) set -- "$@" --key-file "$key" ;;
	esac
	cryptsetup "$@" "$dev" "$name" || die "cryptsetup open of $name failed"
	wait_device "/dev/mapper/$name" 30 >/dev/null
}

do_crypt_close() {
	[ -e "/dev/mapper/$1" ] || exit 0
	cryptsetup close "$1"
}

[ $# -ge 2 ] || die "usage: fstab-unit ACTION ARGS..."
action=$1
shift
case $action in
mount) do_mount "$@" ;;
umount) do_umount "$1" ;;
check) mountpoint -q "$1" || die "$1 is not mounted" ;;
swapon) do_swapon "$@" ;;
swapoff) do_swapoff "$1" ;;
crypt-open) do_crypt_open "$@" ;;
crypt-close) do_crypt_close "$1" ;;
*) die "unknown action $action" ;;
esac
