#!@DEFAULT_INIT_SHELL@
# SPDX-License-Identifier: BSD-2-Clause
# shellcheck disable=SC2154
#
# zfs-import    This script will import ZFS pools
#
# chkconfig:    2345 01 99
# description:  This script will perform a verbatim import of ZFS pools
#               during system boot.
# probe: true
#
### BEGIN INIT INFO
# Provides:          zfs-import
# Required-Start:    mtab
# Required-Stop:     $local_fs mtab
# Default-Start:     S
# Default-Stop:      0 1 6
# X-Start-Before:    checkfs
# X-Stop-After:      zfs-mount
# Short-Description: Import ZFS pools
# Description: Run the `zpool import` command.
### END INIT INFO
#
# NOTE: Not having '$local_fs' on Required-Start but only on Required-Stop
#       is on purpose. If we have '$local_fs' in both (and X-Start-Before=checkfs)
#       we get conflicts - import needs to be started extremely early,
#       but not stopped too late.
#
# 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.

# Source the common init script
. @sysconfdir@/zfs/zfs-functions

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

do_depend()
{
	before swap
	after sysfs udev
	keyword -lxc -openvz -prefix -vserver
}

# Use the zpool cache file to import pools
do_verbatim_import()
{
	if [ -f "$ZPOOL_CACHE" ]
	then
		zfs_action "Importing ZFS pool(s)" \
			"$ZPOOL" import -c "$ZPOOL_CACHE" -N -a
	fi
}

# Support function to get a list of all pools, separated with ';'
find_pools()
{
	local pools

	pools=$("$@" 2> /dev/null | \
		sed -Ee '/pool:|^[a-zA-Z0-9]/!d' -e 's@.*: @@' | \
		sort | \
		tr '\n' ';')

	echo "${pools%%;}" # Return without the last ';'.
}

# Find and import all visible pools, even exported ones
do_import_all_visible()
{
	local already_imported available_pools pool npools
	local exception dir ZPOOL_IMPORT_PATH RET=0 r=1

	# In case not shutdown cleanly.
	# shellcheck disable=SC2154
	[ -n "$init" ] && rm -f /etc/dfs/sharetab

	# Just simplify code later on.
	if [ -n "$USE_DISK_BY_ID" ] && [ "$USE_DISK_BY_ID" != 'yes' ]
	then
		# It's something, but not 'yes' so it's no good to us.
		unset USE_DISK_BY_ID
	fi

	# Find list of already imported pools.
	already_imported=$(find_pools "$ZPOOL" list -H -oname)
	available_pools=$(find_pools "$ZPOOL" import)

	# Just in case - seen it happen (that a pool isn't visible/found
	# with a simple "zpool import" but only when using the "-d"
	# option or setting ZPOOL_IMPORT_PATH).
	if [ -d "/dev/disk/by-id" ]
	then
		npools=$(find_pools "$ZPOOL" import -d /dev/disk/by-id)
		if [ -n "$npools" ]
		then
			# Because we have found extra pool(s) here, which wasn't
			# found 'normally', we need to force USE_DISK_BY_ID to
			# make sure we're able to actually import it/them later.
			USE_DISK_BY_ID='yes'

			if [ -n "$available_pools" ]
			then
				# Filter out duplicates (pools found with the simpl
				# "zpool import" but which is also found with the
				# "zpool import -d ...").
				npools=$(echo "$npools" | sed "s,$available_pools,,")

				# Add the list to the existing list of
				# available pools
				available_pools="$available_pools;$npools"
			else
				available_pools="$npools"
			fi
		fi
	fi

	# Filter out any exceptions...
	if [ -n "$ZFS_POOL_EXCEPTIONS" ]
	then
		local found=""
		local apools=""
		OLD_IFS="$IFS" ; IFS=";"

		for pool in $available_pools
		do
			for exception in $ZFS_POOL_EXCEPTIONS
			do
				[ "$pool" = "$exception" ] && continue 2
				found="$pool"
			done

			if [ -n "$found" ]
			then
				if [ -n "$apools" ]
				then
					apools="$apools;$pool"
				else
					apools="$pool"
				fi
			fi
		done

		IFS="$OLD_IFS"
		available_pools="$apools"
	fi

	# For backwards compatibility, make sure that ZPOOL_IMPORT_PATH is set
	# to something we can use later with the real import(s). We want to
	# make sure we find all by* dirs, BUT by-vdev should be first (if it
	# exists).
	if [ -n "$USE_DISK_BY_ID" ] && [ -z "$ZPOOL_IMPORT_PATH" ]
	then
		local dirs
		dirs="$(for dir in $(echo /dev/disk/by-*)
		do
			# Ignore by-vdev here - we want it first!
			echo "$dir" | grep -q /by-vdev && continue
			[ ! -d "$dir" ] && continue

			printf "%s" "$dir:"
		done | sed 's,:$,,g')"

		if [ -d "/dev/disk/by-vdev" ]
		then
			# Add by-vdev at the beginning.
			ZPOOL_IMPORT_PATH="/dev/disk/by-vdev:"
		fi

		# Help with getting LUKS partitions etc imported.
		if [ -d "/dev/mapper" ]; then
			if [ -n "$ZPOOL_IMPORT_PATH" ]; then
				ZPOOL_IMPORT_PATH="$ZPOOL_IMPORT_PATH:/dev/mapper:"
			else
				ZPOOL_IMPORT_PATH="/dev/mapper:"
			fi
		fi

		# ... and /dev at the very end, just for good measure.
		ZPOOL_IMPORT_PATH="$ZPOOL_IMPORT_PATH$dirs:/dev"
	fi

	# Needs to be exported for "zpool" to catch it.
	[ -n "$ZPOOL_IMPORT_PATH" ] && export ZPOOL_IMPORT_PATH

	# Mount all available pools (except those set in ZFS_POOL_EXCEPTIONS.
	#
	# If not interactive (run from init - variable init='/sbin/init')
	# we get ONE line for all pools being imported, with just a dot
	# as status for each pool.
	# Example: Importing ZFS pool(s)...                             [OK]
	#
	# If it IS interactive (started from the shell manually), then we
	# get one line per pool importing.
	# Example: Importing ZFS pool pool1                             [OK]
	#          Importing ZFS pool pool2                             [OK]
	#          [etc]
	[ -n "$init" ] && zfs_log_begin_msg "Importing ZFS pool(s)"
	OLD_IFS="$IFS" ; IFS=";"
	for pool in $available_pools
	do
		[ -z "$pool" ] && continue

		# We have pools that haven't been imported - import them
		if [ -n "$init" ]
		then
			# Not interactive - a dot for each pool.
			# Except on Gentoo where this doesn't work.
			zfs_log_progress_msg "."
		else
			# Interactive - one 'Importing ...' line per pool
			zfs_log_begin_msg "Importing ZFS pool $pool"
		fi

		# Import by using ZPOOL_IMPORT_PATH (either set above or in
		# the config file) _or_ with the 'built in' default search
		# paths. This is the preferred way.
		# shellcheck disable=SC2086
		"$ZPOOL" import -N ${ZPOOL_IMPORT_OPTS} "$pool" 2> /dev/null
		r="$?" ; RET=$((RET + r))
		if [ "$r" -eq 0 ]
		then
			# Output success and process the next pool
			[ -z "$init" ] && zfs_log_end_msg 0
			continue
		fi
		# We don't want a fail msg here, we're going to try import
		# using the cache file soon and that might succeed.
		[ ! -f "$ZPOOL_CACHE" ] && zfs_log_end_msg "$RET"

		if [ "$r" -gt 0 ] && [ -f "$ZPOOL_CACHE" ]
		then
			# Failed to import without a cache file. Try WITH...
			if [ -z "$init" ] && check_boolean "$VERBOSE_MOUNT"
			then
				# Interactive + Verbose = more information
				zfs_log_progress_msg " using cache file"
			fi

			# shellcheck disable=SC2086
			"$ZPOOL" import -c "$ZPOOL_CACHE" -N ${ZPOOL_IMPORT_OPTS} \
				"$pool" 2> /dev/null
			r="$?" ; RET=$((RET + r))
			if [ "$r" -eq 0 ]
			then
				[ -z "$init" ] && zfs_log_end_msg 0
				continue 3 # Next pool
			fi
			zfs_log_end_msg "$RET"
		fi
	done
	[ -n "$init" ] && zfs_log_end_msg "$RET"

	IFS="$OLD_IFS"
	[ -n "$already_imported" ] && [ -z "$available_pools" ] && return 0

	return "$RET"
}

do_import()
{
	if check_boolean "$ZPOOL_IMPORT_ALL_VISIBLE"
	then
		do_import_all_visible
	else
		# This is the default option
		do_verbatim_import
	fi
}

# Output the status and list of pools
do_status()
{
	check_module_loaded "zfs" || exit 0

	"$ZPOOL" status && echo "" && "$ZPOOL" list
}

do_start()
{
	if check_boolean "$VERBOSE_MOUNT"
	then
	    zfs_log_begin_msg "Checking if ZFS userspace tools present"
	fi

	if checksystem
	then
		check_boolean "$VERBOSE_MOUNT" && zfs_log_end_msg 0

		check_boolean "$VERBOSE_MOUNT" && \
			zfs_log_begin_msg "Loading kernel ZFS infrastructure"

		if ! load_module "zfs"
		then
			check_boolean "$VERBOSE_MOUNT" && zfs_log_end_msg 1
			return 5
		fi
		check_boolean "$VERBOSE_MOUNT" && zfs_log_end_msg 0

		do_import && udev_trigger # just to make sure we get zvols.

		return 0
	else
		return 1
	fi
}

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

if @IS_SYSV_RC@
then
	case "$1" in
		start)
			do_start
			;;
		stop)
			# no-op
			;;
		status)
			do_status
			;;
		force-reload|condrestart|reload|restart)
			# no-op
			;;
		*)
			[ -n "$1" ] && echo "Error: Unknown command $1."
			echo "Usage: $0 {start|status}"
			exit 3
			;;
	esac

	exit $?
else
	# Create wrapper functions since Gentoo don't use the case part.
	depend() { do_depend; }
	start() { do_start; }
	status() { do_status; }
fi
