#!/bin/sh

# NFS root mount helper for initramfs-tools.
#
# The initramfs nfs script calls this with the klibc nfsmount interface: one or
# more "-o options" pairs followed by the server path and the mount point.
#
# Two clients are shipped in the initramfs and this picks between them:
#
#   nfsmount.klibc  klibc's helper.  NFSv2/v3 only, with its own RPC code: it
#                   asks the portmapper for the NFS and MOUNT ports the old way
#                   and hands mount(2) a ready filehandle.
#   mount.nfs       nfs-utils.  Needed for NFSv4, which klibc cannot do at all,
#                   but it negotiates through libtirpc and some hosts answer
#                   that differently - see below.
#
# v2/v3 mounts go to klibc first because that is the combination every KernelCI
# lab has been booting for years, and mount.nfs is tried after it as a fallback.
# Anything that asks for v4 goes straight to mount.nfs.  Keeping both means a
# rootfs that supports v4 without giving up the hosts klibc could already talk
# to.
#
# Replacing klibc's nfsmount with mount.nfs outright follows the workaround in
# Debian bug #409271, but doing so regressed NFS root boots on part of the
# Collabora lab: mount.nfs got "Protocol not supported" out of the portmapper
# query on hosts where klibc's query had always worked.  Those same hosts keep
# serving production, which still runs the klibc image.
# https://bugs.debian.org/409271

KLIBC_NFSMOUNT=/bin/nfsmount.klibc
MOUNT_NFS=/usr/sbin/mount.nfs

# LAVA spells the NFS version the way the kernel's nfsroot= does ("v3", "v4.2").
# mount.nfs wants vers=, klibc wants neither, so normalise only for mount.nfs.
normalize_options()
{
	input=$1
	output=

	while [ -n "$input" ]; do
		case "$input" in
		*,*)
			option=${input%%,*}
			input=${input#*,}
			;;
		*)
			option=$input
			input=
			;;
		esac

		case "$option" in
		v4)
			option=vers=4
			;;
		v4.0)
			option=vers=4.0
			;;
		v4.1)
			option=vers=4.1
			;;
		v4.2)
			option=vers=4.2
			;;
		esac

		if [ -n "$output" ]; then
			output="$output,$option"
		else
			output=$option
		fi
	done

	printf '%s' "$output"
}

# True when the caller asked for NFSv4 in any spelling klibc cannot serve.
wants_v4()
{
	case ",$1," in
	*,v4,*|*,v4.[0-9],*|*,vers=4*|*,nfsvers=4*)
		return 0
		;;
	esac
	return 1
}

mount_options=
mount_source=
mount_target=
expect_option=0

# Parse without consuming "$@": klibc takes the arguments exactly as received.
for arg in "$@"; do
	if [ "$expect_option" -eq 1 ]; then
		expect_option=0
		options=$(normalize_options "$arg")
		if [ -n "$mount_options" ]; then
			mount_options="$mount_options,$options"
		else
			mount_options=$options
		fi
		continue
	fi

	case "$arg" in
	-o)
		expect_option=1
		;;
	-*)
		echo "nfsmount: unsupported option '$arg'" >&2
		exit 1
		;;
	*)
		if [ -z "$mount_source" ]; then
			mount_source=$arg
		elif [ -z "$mount_target" ]; then
			mount_target=$arg
		else
			echo "nfsmount: too many paths" >&2
			exit 1
		fi
		;;
	esac
done

if [ "$expect_option" -eq 1 ]; then
	echo "nfsmount: option requires an argument -- 'o'" >&2
	exit 1
fi

if [ -z "$mount_source" ] || [ -z "$mount_target" ]; then
	echo "nfsmount: need a server path and mount point" >&2
	exit 1
fi

run_mount_nfs()
{
	if [ ! -x "$MOUNT_NFS" ]; then
		echo "nfsmount: $MOUNT_NFS is missing" >&2
		return 1
	fi

	if [ -n "$mount_options" ]; then
		"$MOUNT_NFS" -o "$mount_options" "$mount_source" "$mount_target"
	else
		"$MOUNT_NFS" "$mount_source" "$mount_target"
	fi
}

if wants_v4 "$mount_options"; then
	exec "$MOUNT_NFS" -o "$mount_options" "$mount_source" "$mount_target"
fi

if [ -x "$KLIBC_NFSMOUNT" ]; then
	"$KLIBC_NFSMOUNT" "$@" && exit 0
	echo "nfsmount: klibc helper failed, retrying with $MOUNT_NFS" >&2
fi

run_mount_nfs
