#!/hint/busybox sh
# shellcheck shell=busybox disable=SC3003,SC3001

set -eo pipefail

#
# constants
#

: ${LIB_DIR="/usr/lib/zfs/initcpio"}
STATE_DIR="/run/zfs/initcpio"
STATE_FILE="$STATE_DIR/state"

#
# functions - logging
#

log() {
	echo "${SCRIPT_NAME:?}: $1" >&2
}
dbg() {
	log "$1" 7
}
setup_stderr() {
	:
}

# usage: log "message" [level]
if [[ -t 2 ]]; then
	log "${SCRIPT_NAME:?} started, logging to stderr" 7
elif [[ -n "$JOURNAL_STREAM" ]] \
  && [[ -e /proc/self/fd/2 ]] \
  && [[ "$(stat -L -c "%d:%i" /proc/self/fd/2)" == "$JOURNAL_STREAM" ]]; then
	log() {
		echo "<${2:-"6"}>$1" >&2
	}
	setup_stderr() {
		exec 2> >(sed '/^<[0-9]>/b; s/^/<7>DEBUG: /' >&2)
	}
	log "${SCRIPT_NAME:?} started, logging to journal" 7
else
	log() {
		echo "<${2:-"6"}>${SCRIPT_NAME:?}: $1" >/dev/kmsg
	}
	setup_stderr() {
		# exec 2> >(sed "/^<[0-9]>/b; s/^/<7>${SCRIPT_NAME}: DEBUG: /" | while IFS='' read -r line; do printf "%s\n" "$line"; done >/dev/kmsg)
		exec 2> >(stdbuf -oL -eL sed "/^<[0-9]>/b; s/^/<7>${SCRIPT_NAME}: DEBUG: /" &>/dev/kmsg)
	}
	log "${SCRIPT_NAME:?} started, logging to kmsg" 7
fi

die() {
	log "fatal: $1" "${2-"2"}"
	exit 1
}

setup_debug() {
	if [[ ${ZFS_ROOT_DEBUG+set} ]] || grep -q -Ew 'zfsroot.debug' /proc/cmdline; then
		setup_stderr
		set -x
	fi
}

dry_run() {
	if ! [[ ${DRY_RUN+set} ]]; then
		"$@"
	fi
}


#
# functions - cmdline
#

if [[ ${DEBUG_CMDLINE+set} ]]; then
	cat_cmdline() {
		log "cmdline: $DEBUG_CMDLINE"
		cat <<-EOF
		$DEBUG_CMDLINE
		EOF
	}
else
	cat_cmdline() {
		log "cmdline: $(cat /proc/cmdline)"
		cat /proc/cmdline
	}
fi

parse_cmdline() {
	local vars v

	# xargs(1) is used to perform word splitting with quoting
	vars="$(cat_cmdline | xargs printf "%s\n" | "$LIB_DIR/zfs-parse-cmdline")"

	local IFS=$'\n'
	for v in $vars; do
		log "cmdline(parsed): \$$v"
		export "${v?}"
	done
}

vars_present() {
	[[ -e "$STATE_FILE" ]]
}

dump_vars() {
	local vars k v

	# XXX: multi-line variables wtll break this
	vars="$(set | grep -Eo '^ZFS_ROOT_[^=]*')"

	for k in $vars; do
		export "${k?}"
	done

	install -dm700 "${STATE_FILE%/*}"
	local IFS=$'\n'
	for k in $vars; do
		if eval "[[ \${$k+set} ]]"; then
			v="$(eval "printf '%s' \"\$$k\"")"
			log "writing: \$$k=$v"
			printf "%s=%s\n" "$k" "$v"
		fi
	done | install -Dm600 /dev/stdin "$STATE_FILE"
}

load_vars() {
	while IFS='' read -r line; do
		log "env(read): \$$line"
		export "${line?}"
	done <"$STATE_FILE"
}

# Normalization dance, unified
#
# Case 1 (replace prefix):
# mountpoint=/            mountbase=/        relpoint=      sysrootpoint=/sysroot
# mountpoint=/usr         mountbase=/        relpoint=/usr  sysrootpoint=/sysroot/usr
# mountpoint=/target      mountbase=/target  relpoint=      sysrootpoint=/sysroot
# mountpoint=/target/usr  mountbase=/target  relpoint=/usr  sysrootpoint=/sysroot/usr
#
# Case 2 (strip prefix):
# mountpoint=/            mountbase=/        relpoint=/
# mountpoint=/usr         mountbase=/        relpoint=/usr
# mountpoint=/target      mountbase=/target  relpoint=/
# mountpoint=/target/usr  mountbase=/target  relpoint=/usr
#
# This function does not really have well-defined semantics in terms of paths
# (i.e., it is not a "relativize path" operation, and it does not always
# return a well-formed relative path). Instead, this function simply computes
# `relpoint` per the table above.
#
# $1: `mountpoint`
# $2: `mountbase`
# $3: desired result when `mountpoint` == `mountbase`
#     (either "" or "/" makes sense, for case 1 and case 2 respectively)
# Assumes that input paths are cleaned up (no extraneous trailing slashes).
# Outputs `relpoint`.
# Returns non-zero if `mountpoint` is not under `mountbase`.
_mountpoint_strip_prefix() {
	local mountpoint="$1" mountbase="$2" empty="$3" relpoint
	# unfortunately, busybox ash [[ expansion rules are braindead
	# and it cannot be used for anything where rhs consists of a
	# literal part and a pattern part, e.g.:
	#   if ! [[ "$mountpoint" == "$mountbase" || "$mountpoint" == "${mountbase%/}"/* ]]; then
	# anyway, normalization dance
	case "$mountpoint" in
	"$mountbase")
		relpoint="$empty" ;;
	"${mountbase%/}"/*)
		relpoint="${mountpoint#"${mountbase%/}"}" ;;
	*)
		return 1 ;;
	esac

	printf "%s\n" "$relpoint"
}

# Rebase a path (strip an existing prefix and append to a new one).
# $1: source path
# $2: old path prefix to strip
# $3: new path prefix
# - $3 == "/": if $1 == $2, produce "/"
# - $3 == "": if $1 == $2, produce an empty path
# - $3 is any other path: use $3 as the new path prefix
# Outputs $1 with $2 stripped and $3 prepended.
# Returns non-zero if $1 is not under $2.
mountpoint_rebase() {
	local mountpoint="$1" mountbase="$2" newbase="$3" stripped

	case "$newbase" in
	/) _mountpoint_strip_prefix "$mountpoint" "$mountbase" "/" ;;
	# the "") is equivalent to *), but we spell it out for clarity
	"") _mountpoint_strip_prefix "$mountpoint" "$mountbase" "" ;;
	*) stripped="$(_mountpoint_strip_prefix "$mountpoint" "$mountbase" "")" \
		&& printf "%s%s\n" "$newbase" "$stripped" ;;
	esac
}

# Rebase a path (strip an existing prefix, if applicable, and append to a new one).
# $1: source path
# $2: old path prefix to strip
# $3: new path prefix
# - $3 == "/": if $1 == $2, produce "/"
# - $3 == "": if $1 == $2, produce an empty path
# - $3 is any other path: use $3 as the new path prefix
# Outputs $1 with $2 stripped and $3 prepended.
# Stripping is skipped ($2 is assumed to be "/") if $1 is not under $2.
mountpoint_rebase_if() {
	local mountpoint="$1" newbase="$3"

	mountpoint_rebase "$@" \
	|| mountpoint_rebase "$mountpoint" "/" "$newbase"
}

# vim: ft=bash ts=8 noet:
