#!/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
# DEVICE-TIMEOUT in seconds, 0 waits without limit.
set -u

FAILED=/run/svctl/failed

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

# device node for UUID=, LABEL=, PARTUUID=, PARTLABEL= and /dev/...; empty otherwise
devnode() {
	case $1 in
	UUID=*) echo "/dev/disk/by-uuid/${1#UUID=}" ;;
	LABEL=*) echo "/dev/disk/by-label/${1#LABEL=}" ;;
	PARTUUID=*) echo "/dev/disk/by-partuuid/${1#PARTUUID=}" ;;
	PARTLABEL=*) echo "/dev/disk/by-partlabel/${1#PARTLABEL=}" ;;
	/dev/*) echo "$1" ;;
	esac
}

# Waits for the device node DEV, at most SECONDS (0: no limit): through udev
# when udevd runs, else by polling.
wait_device() {
	if [ -S /run/udev/control ] && command -v udevadm >/dev/null; then
		if [ "$2" = 0 ]; then
			udevadm wait "$1"
		else
			udevadm wait --timeout="$2" "$1"
		fi && return 0
	else
		n=$(($2 * 10))
		while [ ! -e "$1" ]; do
			[ "$2" = 0 ] || [ "$n" -gt 0 ] || break
			n=$((n - 1))
			sleep 0.1
		done
		[ -e "$1" ] && return 0
	fi
	die "$1 did not appear within $2 s"
}

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=$(devnode "$spec")
	[ -z "$dev" ] || wait_device "$dev" "$tmo"
	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=$(devnode "$1")
	[ -z "$dev" ] || wait_device "$dev" "$3"
	target=$(readlink -f "${dev:-$1}")
	grep -q "^$target " /proc/swaps && exit 0
	set -- "${dev:-$1}" "$2"
	pri=$(optval pri "$2")
	if [ -n "$pri" ]; then
		swapon -p "$pri" "$1"
	else
		swapon "$1"
	fi
}

do_swapoff() {
	dev=$(devnode "$1")
	target=$(readlink -f "${dev:-$1}")
	grep -q "^$target " /proc/swaps || exit 0
	swapoff "${dev:-$1}"
}

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=$(devnode "$src")
	[ -z "$dev" ] || wait_device "$dev" "$tmo"
	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:-$src}" "$name" || die "cryptsetup open of $name failed"
	wait_device "/dev/mapper/$name" 30
}

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
