# SPDX-License-Identifier: BSD-2-Clause
# This is a script with common functions etc used by zfs-import, zfs-load-key,
# zfs-mount, zfs-share and zfs-zed.
#
# It is _NOT_ to be called independently
#
# Released under the 2-clause BSD license.
#
# This script is based on debian/zfsutils.zfs.init from the
# Debian GNU/kFreeBSD zfsutils 8.1-3 package, written by Aurelien Jarno.

PATH=/sbin:/bin:/usr/bin:/usr/sbin

# Source function library
if [ -f /etc/rc.d/init.d/functions ]; then
	# RedHat and derivatives
	. /etc/rc.d/init.d/functions
elif [ -L /etc/init.d/functions.sh ]; then
	# Gentoo
	. /etc/init.d/functions.sh
elif [ -f /lib/lsb/init-functions ]; then
	# LSB, Debian, and derivatives
	. /lib/lsb/init-functions
fi

# Of course the functions we need are called differently
# on different distributions - it would be way too easy
# otherwise!!
if type log_failure_msg > /dev/null 2>&1 ; then
	# LSB functions - fall through
	zfs_log_begin_msg() { log_begin_msg "$1"; }
	zfs_log_end_msg() { log_end_msg "$1"; }
	zfs_log_failure_msg() { log_failure_msg "$1"; }
	zfs_log_progress_msg() { log_progress_msg "$1"; }
elif type success > /dev/null 2>&1 ; then
	# Fedora/RedHat functions
	zfs_set_ifs() {
		# For some reason, the init function library have a problem
		# with a changed IFS, so this function goes around that.
		local tIFS="$1"
		if [ -n "$tIFS" ]
		then
			TMP_IFS="$IFS"
			IFS="$tIFS"
		fi
	}

	zfs_log_begin_msg() { printf "%s" "$1 "; }
	zfs_log_end_msg() {
		# shellcheck disable=SC2154
		zfs_set_ifs "$OLD_IFS"
		if [ "$1" -eq 0 ]; then
			success
		else
			failure
		fi
		echo
		zfs_set_ifs "$TMP_IFS"
	}
	zfs_log_failure_msg() {
		zfs_set_ifs "$OLD_IFS"
		failure
		echo
		zfs_set_ifs "$TMP_IFS"
	}
	zfs_log_progress_msg() { printf "%s" "$""$1"; }
elif type einfo > /dev/null 2>&1 ; then
	# Gentoo functions
	zfs_log_begin_msg() { ebegin "$1"; }
	zfs_log_end_msg() { eend "$1"; }
	zfs_log_failure_msg() { eend "$1"; }
#	zfs_log_progress_msg() { printf "%s" "$1"; }
	zfs_log_progress_msg() { :; }
else
	# Unknown - simple substitutes.
	zfs_log_begin_msg() { printf "%s" "$1"; }
	zfs_log_end_msg() {
		ret=$1
		if [ "$ret" -ge 1 ]; then
			echo " failed!"
		else
			echo " success"
		fi
		return "$ret"
	}
	zfs_log_failure_msg() { echo "$1"; }
	zfs_log_progress_msg() { printf "%s" "$1"; }
fi

# Paths to what we need
ZFS="@sbindir@/zfs"
ZED="@sbindir@/zed"
ZPOOL="@sbindir@/zpool"
ZPOOL_CACHE="@sysconfdir@/zfs/zpool.cache"

# Sensible defaults
ZFS_LOAD_KEY='yes'
ZFS_UNLOAD_KEY='no'
ZFS_MOUNT='yes'
ZFS_UNMOUNT='yes'
ZFS_SHARE='yes'
ZFS_UNSHARE='yes'

# Source zfs configuration, overriding the defaults
if [ -f @initconfdir@/zfs ]; then
	. @initconfdir@/zfs
fi

# ----------------------------------------------------

export ZFS ZED ZPOOL ZPOOL_CACHE ZFS_LOAD_KEY ZFS_UNLOAD_KEY ZFS_MOUNT ZFS_UNMOUNT \
    ZFS_SHARE ZFS_UNSHARE

zfs_action()
{
	local MSG="$1";	shift
	local CMD="$*"
	local ret

	zfs_log_begin_msg "$MSG "
	$CMD
	ret=$?
	if [ "$ret" -eq 0 ]; then
		zfs_log_end_msg "$ret"
	else
		zfs_log_failure_msg "$ret"
	fi

	return "$ret"
}

# Returns
#   0 if daemon has been started
#   1 if daemon was already running
#   2 if daemon could not be started
#   3 if unsupported
#
zfs_daemon_start()
{
	local PIDFILE="$1";	shift
	local DAEMON_BIN="$1";	shift

	if type start-stop-daemon > /dev/null 2>&1 ; then
		# LSB functions
		start-stop-daemon --start --quiet --pidfile "$PIDFILE" \
		    --exec "$DAEMON_BIN" --test > /dev/null || return 1

		# shellcheck disable=SC2086
		start-stop-daemon --start --quiet --exec "$DAEMON_BIN" -- \
		    "$@" || return 2

		# On Debian, there's a 'sendsigs' script that will
		# kill basically everything quite early and zed is stopped
		# much later than that. We don't want zed to be among them,
		# so add the zed pid to list of pids to ignore.
		if [ -f "$PIDFILE" ] && [ -d /run/sendsigs.omit.d ]
		then
			ln -sf "$PIDFILE" /run/sendsigs.omit.d/zed
		fi
	elif type daemon > /dev/null 2>&1 ; then
		# Fedora/RedHat functions
		# shellcheck disable=SC2086
		daemon --pidfile "$PIDFILE" "$DAEMON_BIN" "$@"
		return $?
	else
		# Unsupported
		return 3
	fi

	return 0
}

# Returns
#   0 if daemon has been stopped
#   1 if daemon was already stopped
#   2 if daemon could not be stopped
#   3 if unsupported
#
zfs_daemon_stop()
{
	local PIDFILE="$1"
	local DAEMON_BIN="$2"
	local DAEMON_NAME="$3"

	if type start-stop-daemon > /dev/null 2>&1 ; then
		# LSB functions
		start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 \
		    --pidfile "$PIDFILE" --name "$DAEMON_NAME"
		ret="$?"
		[ "$ret" = 0 ] && rm -f "$PIDFILE"

		return "$ret"
	elif type killproc > /dev/null 2>&1 ; then
		# Fedora/RedHat functions
		killproc -p "$PIDFILE" "$DAEMON_NAME"
		ret="$?"
		[ "$ret" = 0 ] && rm -f "$PIDFILE"

		return "$ret"
	else
		# Unsupported
		return 3
	fi

	return 0
}

# Returns status
zfs_daemon_status()
{
	local PIDFILE="$1"
	local DAEMON_BIN="$2"
	local DAEMON_NAME="$3"

	if type status_of_proc > /dev/null 2>&1 ; then
		# LSB functions
		status_of_proc "$DAEMON_NAME" "$DAEMON_BIN"
		return $?
	elif type status > /dev/null 2>&1 ; then
		# Fedora/RedHat functions
		status -p "$PIDFILE" "$DAEMON_NAME"
		return $?
	else
		# Unsupported
		return 3
	fi

	return 0
}

zfs_daemon_reload()
{
	local PIDFILE="$1"
	local DAEMON_NAME="$2"

	if type start-stop-daemon > /dev/null 2>&1 ; then
		# LSB functions
		start-stop-daemon --stop --signal 1 --quiet \
		    --pidfile "$PIDFILE" --name "$DAEMON_NAME"
		return $?
	elif type killproc > /dev/null 2>&1 ; then
		# Fedora/RedHat functions
		killproc -p "$PIDFILE" "$DAEMON_NAME" -HUP
		return $?
	else
		# Unsupported
		return 3
	fi

	return 0
}

zfs_installed()
{
	if [ ! -x "$ZPOOL" ]; then
		return 1
	else
		# Test if it works (will catch missing/broken libs etc)
		"$ZPOOL" -? > /dev/null 2>&1
		return $?
	fi

	if [ ! -x "$ZFS" ]; then
		return 2
	else
		# Test if it works (will catch missing/broken libs etc)
		"$ZFS" -? > /dev/null 2>&1
		return $?
	fi

	return 0
}

# Trigger udev and wait for it to settle.
udev_trigger()
{
	if [ -x /sbin/udevadm ]; then
		/sbin/udevadm trigger --action=change --subsystem-match=block
		/sbin/udevadm settle
	elif [ -x /sbin/udevsettle ]; then
		/sbin/udevtrigger
		/sbin/udevsettle
	fi
}

# Do a lot of checks to make sure it's 'safe' to continue with the import.
checksystem()
{
	if grep -qiE '(^|[^\\](\\\\)* )zfs=(off|no|0)( |$)' /proc/cmdline;
	then
		# Called with zfs=(off|no|0) - bail because we don't
		# want anything import, mounted or shared.
		# HOWEVER, only do this if we're called at the boot up
		# (from init), not if we're running interactively (as in
		# from the shell - we know what we're doing).
		# shellcheck disable=SC2154
		[ -n "$init" ] && exit 3
	fi

	# Check if ZFS is installed.
	zfs_installed || return 5

	# Just make sure that /dev/zfs is created.
	udev_trigger

	return 0
}

get_root_pool()
{
	# shellcheck disable=SC2046
	set -- $(mount | grep ' on / ')
	[ "$5" = "zfs" ] && echo "${1%%/*}"
}

# Check if a variable is 'yes' (any case) or '1'
# Returns TRUE if set.
check_boolean()
{
	local var="$1"

	echo "$var" | grep -Eiq "^yes$|^on$|^true$|^1$" && return 0 || return 1
}

check_module_loaded()
{
	module="$1"

	[ -r "/sys/module/${module}/version" ] && return 0 || return 1
}

load_module()
{
	module="$1"

	# Load the zfs module stack
	if ! check_module_loaded "$module"; then
		if ! /sbin/modprobe "$module"; then
			return 5
		fi
	fi
	return 0
}

# first parameter is a regular expression that filters mtab
read_mtab()
{
	local match="$1"
	local fs mntpnt fstype opts rest

	# Unset all MTAB_* variables
	# shellcheck disable=SC2046
	unset $(env | sed -e '/^MTAB_/!d' -e 's,=.*,,')

	while read -r fs mntpnt fstype opts rest; do
		if echo "$fs $mntpnt $fstype $opts" | grep -qE "$match"; then
			# * Fix problems (!?) in the mounts file. It will record
			#   'rpool 1' as 'rpool\0401' instead of 'rpool\00401'
			#   which seems to be the correct (at least as far as
			#   'printf' is concerned).
			# * We need to use the external echo, because the
			#   internal one would interpret the backslash code
			#   (incorrectly), giving us a  instead.
			mntpnt=$(/bin/echo "$mntpnt" | sed 's,\\0,\\00,g')
			fs=$(/bin/echo "$fs" | sed 's,\\0,\\00,')

			# Remove 'unwanted' characters.
			mntpnt=$(printf '%b' "$mntpnt" | tr -d '/. -')
			fs=$(printf '%b' "$fs")

			# Set the variable.
			eval export "MTAB_$mntpnt=\"$fs\""
		fi
	done < /proc/self/mounts
}

in_mtab()
{
	local mntpnt="$1"
	# Remove 'unwanted' characters.
	mntpnt=$(printf '%b' "$mntpnt" | tr -d '/. -')
	local var

	var="$(eval echo "MTAB_$mntpnt")"
	[ "$(eval echo "$""$var")" != "" ]
	return "$?"
}

# first parameter is a regular expression that filters fstab
read_fstab()
{
	local match="$1"
	local i var

	# Unset all FSTAB_* variables
	# shellcheck disable=SC2046
	unset $(env | sed -e '/^FSTAB_/!d' -e 's,=.*,,')

	i=0
	while read -r fs mntpnt fstype opts; do
		echo "$fs" | grep -qE '^#|^$' && continue
		echo "$mntpnt" | grep -qE '^none|^swap' && continue
		echo "$fstype" | grep -qE '^swap' && continue

		if echo "$fs $mntpnt $fstype $opts" | grep -qE "$match"; then
			eval export "FSTAB_dev_$i=$fs"
			fs=$(printf '%b' "$fs" | tr '/' '_')
			eval export "FSTAB_$i=$mntpnt"

			i=$((i + 1))
		fi
	done < /etc/fstab
}

in_fstab()
{
	local var

	var="$(eval echo "FSTAB_$1")"
	[ "${var}" != "" ]
	return $?
}

is_mounted()
{
	local mntpt="$1"
	local mp

	while read -r _ mp _; do
		[ "$mp" = "$mntpt" ] && return 0
	done < /proc/self/mounts

	return 1
}
