#!/usr/local/bin/cbsd
#v13.0.0
MYARG=
MYOPTARG="fstab jname jroot"
MYDESC="Mount jail by fstab file"
ADDHELP="
${H3_COLOR}Description${N0_COLOR}:

Script for mounting filesystem or files into jail by 'fstab' file.
Use the normal fstab entry, except that the mountpoint (destination) is 
relative to the rootfs container.

For mount single file via nullfs you need https://reviews.freebsd.org/D37478.

Unlike the original fstab syntax, you can use a custom script as a 'source' for 
custom (SMB, iSCSI, FUSEFS-based) mounts, e.g. example for NTFS mount:

/usr/local/cbsd/share/examples/jails-fstab/ntfs-3g.sh

Also when you use encrypted ZFS and whant to unload the KEY each time when
crypto dataset unmounted, please set 'zfs_always_unload_key' params in ~cbsd/zfs.conf

${H3_COLOR}Options${N0_COLOR}:

 ${N2_COLOR}fstab${N0_COLOR} - Path to fstab file ( used with rootfs= );
 ${N2_COLOR}jname${N0_COLOR} - (re-)mount all fstab's for specified jail;
 ${N2_COLOR}jroot${N0_COLOR} - rootfs directory of jail ( used with fstab= );

${H3_COLOR}Examples${N0_COLOR}:

1)
# mkdir /tmp/jail-mnt
# echo \"/tmp/jail-mnt /mnt rw 0 0\" > /tmp/fstab

2)
# cbsd mountfstab fstab=/tmp/fstab jroot=/usr/jails/jails/myjail1

# cbsd mountfstab jname='*'
# cbsd mountfstab jail1 jail2

${H3_COLOR}See also${N0_COLOR}:

 cbsd unmountfstab --help
 cbsd mountmd --help
 cbsd jcleanup --help
 cat /usr/local/cbsd/share/examples/jails-fstab/ntfs-3g.sh
 cat /usr/local/cbsd/etc/defaults/zfs.conf

"

. ${subrdir}/nc.subr
jname=
ojname=
fstab=
jroot=
. ${cbsdinit}
. ${system}

mountfstab_by_jname()
{
	. ${subrdir}/rcconf.subr

	if [ $? -eq 1 ]; then
		${ECHO} "${W1_COLOR}${CBSD_APP} error: ${N1_COLOR}no such jail: ${N2_COLOR}${jname}${N0_COLOR}"
		return 1
	fi

	if [ ${baserw} -eq 0 ]; then
		_jroot="${path}"
	else
		_jroot="${data}"
	fi

	ret=0

	if [ -r ${mount_fstab} ]; then
		mountfstab jroot="${_jroot}" fstab="${mount_fstab}"
		ret=$?
		[ ${ret} -ne 0 ] && err 1 "${W1_COLOR}${CBSD_APP} error: ${N1_COLOR}mountfstab jroot=\"${_jroot}\" fstab=\"${mount_fstab}\"${N0_COLOR}"
	fi
	if [ -r ${mount_fstab}.local ]; then
		mountfstab jroot="${_jroot}" fstab="${mount_fstab}.local"
		ret=$?
		[ ${ret} -ne 0 ] && err 1 "${W1_COLOR}${CBSD_APP} error: ${N1_COLOR}mountfstab jroot=\"${_jroot}\" fstab=\"${mount_fstab}.local\"${N0_COLOR}"
	fi
}

if [ -z "${fstab}" -a -z "${jroot}" ]; then
	# mount by jname
	[ -z "${jname}" -a -z "${1}" ] && err 1 "${W1_COLOR}${CBSD_APP} error: ${N1_COLOR}mandatory params: ${N2_COLOR}fstab= jroot= ${N1_COLOR}or ${N2_COLOR}jname=${N0_COLOR}"
	[ -z "${jname}" ] && jname="${*}"

	emulator="jail"         # for jname_is_multiple
	jname_is_multiple

	[ -z "${jail_list}" ] && jail_list="${jname}"

	if [ -n "${jail_list}" ]; then
		for jname in ${jail_list}; do
			mountfstab_by_jname
		done
	fi
	exit ${ret}
fi

[ ! -f "${fstab}" ] && err 1 "${fstab} does not exist"
[ ! -d "${jroot}" ] && err 1 "${jroot} does not exist"

# local fstab exist ?
have_local=0

# special sign for zfs attach
with_zfs_attach="${jailsysdir}/${jname}/with_zfs_attach"

init_localmpt_fstab()
{
	local _ret=
	[ ! -f "${fstab}.local" ] && return 0

	have_local=1

	local i=0

	# check for CRLF at the end of file
	_ret=$( ${TAIL_CMD} -n1 "${fstab}.local" )

	if [ -n "${_ret}" ]; then
		# append CRLF for proper while read loop
		printf '\n' >> "${fstab}.local"
	fi

	# always rewrite CBSDROOT first
	replacewdir file0="${fstab}.local" old="CBSDROOT"

	eval $( ${CAT_CMD} ${fstab}.local | while read _device _mountpt _fs _mode _a _b; do

		[ -z "${_mountpt}" ] && continue

		case ":${_device}" in
			:#* | :)
				continue
			;;
			*)
				case "${_fs}" in
					external)
						if [ -x "${_device}" ]; then
							echo "local_mpt${i}=\"${_mountpt}\""
							i=$(( i + 1 ))
						fi
						;;
					zfs)
						if [ ${allow_zfs} -ne 1 ]; then
							# allow_zfs not enabled, skipp
							continue
						fi
						# we need special route for jails with zfs attach, make sign for jstart script
						${TOUCH_CMD} ${with_zfs_attach}
						continue
						;;
					*)
						echo "local_mpt${i}=\"${_mountpt}\""
						i=$(( i + 1 ))
						;;
				esac
			;;
		esac
	done )
}

# return 0 if no such mount point in fstab.local exist
# return 1 if exist
# $1 - test mpt
# example:
# if ! check_for_localmpt_fstab /tmp; then
#		echo "EXIST"
# fi
check_for_localmpt_fstab()
{
	local _res=0 i local_mpt

	[ -z "${1}" ] && return 0

	for i in $( ${SEQ_CMD} 0 128 ); do
		eval local_mpt="\$local_mpt${i}"
		[ -z "${local_mpt}" ] && break
		[ "${local_mpt}" = "${1}" ] && _res=1 && break
	done

	return ${_res}
}

## MAIN
# check for CRLF at the end of file
[ ! -r ${fstab} ] && err 1 "${N1_COLOR}no such fstab file: ${N2_COLOR}${fstab}${N0_COLOR}"
_ret=$( ${TAIL_CMD} -c1 "${fstab}" )

if [ -n "${_ret}" ]; then
	# append CRLF for proper while read loop
	printf '\n' >> "${fstab}"
fi

init_localmpt_fstab

# always rewrite CBSDROOT first
replacewdir file0="${fstab}" old="CBSDROOT"

# first pass: test for fstab is valid
${CAT_CMD} ${fstab} | while read _device _mountpt _fs _mode _a _b; do
	case ":${_device}" in
		:#* | :)
			continue
			;;
		*)
			case "${_fs}" in
				external)
					if [ ! -x "${_device}" ]; then
						err 1 "${W1_COLOR}${CBSD_APP} error: ${N1_COLOR}external script not found or not executable, check fstabs: ${N2_COLOR}${_device}${N0_COLOR}"
						continue
					fi
					;;
				geli)
					is_mounted ${gelidir} && continue
					err 1 "Error: ${gelidir} is not initializated. Please use: 'cbsd geli mode=initmaster' first"
					;;
				zfs)
					if [ ${allow_zfs} -ne 1 ]; then
						${ECHO} "${W1_COLOR}Warning: ${N1_COLOR}mountfstab: jail has a zfs dataset:: ${N2_COLOR}${_device}${N0_COLOR}"
						${ECHO} "${W1_COLOR}Warning: ${N1_COLOR}mountfstab: but allow_zfs options is not enabled. please use: ${N2_COLOR}cbsd jset jname=${jname} allow_zfs=1${N0_COLOR}"
						continue
					fi
					# we need special route for jails with zfs attach, make sign for jstart script
					${TOUCH_CMD} ${with_zfs_attach}
					continue
					;;
				*)
					[ -z "${_device}" ] && continue
					# append workdir if it not full path
					prefix=$( substr --pos=0 --len=1 --str=${_device} )
					[ "${prefix}" != "/" ] && _device="${workdir}/${_device}"
					;;
			esac
			;;
	esac
done

${CAT_CMD} ${fstab} | while read _device _mountpt _fs _mode _a _b; do
	[ "${_fs}" = "zfs" ] && continue
	case ":${_device}" in
		:#* | :)
			continue
			;;
	esac

	# SKIP mount when mount point also exist in fstab.local. fstab.local is preferred
	if [ ${have_local} -eq 1 ]; then
		if ! check_for_localmpt_fstab ${_mountpt}; then
			continue
		fi
	fi

	mnt=0
	prefix=$( substr --pos=0 --len=1 --str="${_device}" )

	if [ "${_fs}" = "geli" ]; then
		# prepare src part
		if [ "${prefix}" != "/" ]; then
			if [ -z "${jname}" ]; then
				_device="${workdir}/${_device}"
			else
				_device="${jailsysdir}/${jname}/${_device}"
			fi
		fi
		attachgeli file="${_device}" dst="${jroot}${_mountpt}" mode="${_mode}"
		continue
	fi

	if [ "${_fs}" = "external" ]; then
		${_device} -p ${jroot}${_mountpt} -o ${_mode} ${jname}
		ret=$?
		[ ${ret} -ne 0 ] && ${ECHO} "${W1_COLOR}${CBSD_APP} warning: ${N1_COLOR}unable to create mountpoint via external ${_device}: ${N2_COLOR}${jroot}${_mountpt}${N0_COLOR}"
		continue
	fi

	if [ "${_fs}" = "nullfs" ]; then
		if [ "${prefix}" != "/" ]; then
			_device="${workdir}/${_device}"
		else
			if ! [ -d "${_device}" ]; then
				if [ ! -c "${_device}" -o ! -b "${_device}" ]; then
					# notes: Unix socket not supported by nullfs, only regular files
					if [ ! -f "${_device}" ]; then
						${ECHO} "${W1_COLOR}${CBSD_APP} warning: ${N1_COLOR}mountfstab: no such source (directory or file) for jail ${jname}: ${N2_COLOR}${_device}${N0_COLOR}"
						continue
					fi
				fi
			fi
		fi
	fi

	if is_mounted "${jroot}${_mountpt}"; then
		echo "${jroot}${_mountpt} already mounted" 1>&2
		mnt=1
	fi

	[ ${mnt} -eq 1 ] && continue

	# /usr/compat - work-around for migrate from /usr/compat -> /compat introduced in CBSD 11.0.16
	[ "${_mountpt}" = "/usr/compat" ] && _mountpt="/compat"

	_dst_is_dir=1

	# single file ?
	if [ -f "${_device}" ]; then
		_dst_is_dir=0
	fi

	if [ ! -d "${jroot}${_mountpt}" ]; then
		if [ ${_dst_is_dir} -eq 1 ]; then
			${MKDIR_CMD} -p "${jroot}${_mountpt}"
			_ret=$?
		else
			${TOUCH_CMD} "${jroot}/${_mountpt}"
			_ret=$?
		fi
		if [ ${_ret} -ne 0 ]; then
			${ECHO} "${W1_COLOR}${CBSD_APP} warning: ${N1_COLOR}mountfstab: unable to create mountpoint for ${_fs} ${_device}: ${N2_COLOR}${jroot}${_mountpt}${N0_COLOR}"
			${ECHO} "${W1_COLOR}${CBSD_APP} warning: ${N1_COLOR}mountfstab: read-only location? Skip mount for: ${N2_COLOR}${jroot}${_mountpt}${N0_COLOR}"
			continue
		fi
	fi

	${MOUNT_CMD} -t ${_fs} -o${_mode} "${_device}" "${jroot}${_mountpt}"
done

exit 0
