#!/bin/sh

# /lib/bilibop/lockfs_mount_helper {{{
# Mount helper script for 'lockfs' filesystem type entries in /etc/fstab.
# This script cannot be run manually. The expected way to run it is the
# following:
# 1. Enable bilibop-lockfs:
#    * set BILIBOP_LOCKFS to "true" in /etc/bilibop/bilibop.conf or
#    * append 'lockfs' parameter in the boot commandline
# 2. One time '/' is an aufs mountpoint, the temporary /etc/fstab is
#    modified to replace filesystem types (third field) of some entries
#    by 'lockfs' (options are modified too to remember the original
#    fstype).
# 3. /sbin/mount.lockfs is created if it don't already exist. This can be a
#    symlink to /lib/bilibop/lockfs_mount_helper if this helper is executable,
#    or a copy of the helper (followed by chmod +x) if the helper is not
#    executable. If the helper is missing, /sbin/mount.lockfs will be a
#    poor fallback to call mount normally.
# 4. /etc/fstab is parsed by 'mount -a', and then mount calls mount.lockfs
#    with the proper arguments when a 'lockfs' fstype is encountered.
# }}}

PATH="/sbin:/bin"

usage() {
	cat <<EOF
${0##*/}: mount helper script for bilibop-lockfs.
This script can not be run manually, but only by a mount process,
and only if bilibop-lockfs is enabled.
EOF
}

# mount_fallback() =========================================================={{{
# What we want is: mount a device on its original mountpoint and rewrite the
# fstab entry to keep it coherent. This function should be called in case of
# error or if the device is whitelisted. Device, mountpoint, type and options
# must be given as argument (in this order), each of them being inserted
# between double quotes.
mount_fallback() {
    ${DEBUG} && echo "> mount_fallback $@" >&2
    sed -i "s;^\s*[^#][^ ]\+\s\+${2}\s\+lockfs\s.*;${1} ${2} ${3:-auto} ${4:-defaults} 0 0;" /etc/fstab
    exec mount ${1} ${2} ${3:+-t ${3}} ${4:+-o ${4}}
}
# ===========================================================================}}}

# Works only if the parent process is /bin/mount:
if	[ "$(readlink -f /proc/${PPID}/exe)" != "/bin/mount" ]
then	usage >&2
	exit 3
fi


. /lib/bilibop/common.sh
get_bilibop_variables
get_udev_root

# Works only if the root filesystem is already managed by bilibop-lockfs:
if	is_aufs_mountpoint -q / && [ -f "${BILIBOP_RUNDIR}/lock" ]
then
	LOCKFS="true"
	robr="$(aufs_readonly_branch /)"
	rwbr="$(aufs_writable_branch /)"
else
	echo "${0##*/}: bilibop-lockfs is disabled." >&2
	exit 1
fi

# Some configurations can have been overridden from the boot commandline:
for	param in $(cat /proc/cmdline)
do
	case	"${param}" in
		lockfs=*)
			for	policy in $(IFS=',' ; echo ${param#lockfs=})
			do
				case	"${policy}" in
					hard|soft)
						BILIBOP_LOCKFS_POLICY="${policy}"
						;;
					all)
						BILIBOP_LOCKFS_WHITELIST=""
						;;
				esac
			done
			;;
	esac
done

# the mount command always provides arguments to the helper scipts in this
# order: FILESYSTEM MOUNTPOINT -o OPTIONS. We take advantage of this fixed
# format.
if	[ -b "${1}" ]
then
	device="${1}"
	# Check if this device is whitelisted:
	if	[ -n "${BILIBOP_LOCKFS_WHITELIST}" ]
	then
		# Query ID_FS_* udev environment variables of the device:
		eval $(query_udev_envvar $(readlink -f ${device}))
		if	[ -z "${ID_FS_USAGE}" ]
		then	eval $(blkid -o udev -p ${device})
		fi
		[ "${ID_FS_USAGE}" = "filesystem" -o "${ID_FS_USAGE}" = "crypto" ] &&
		for	skip in ${BILIBOP_LOCKFS_WHITELIST}
		do
			case	"${skip}" in
				UUID=${ID_FS_UUID}|LABEL=${ID_FS_LABEL}|TYPE=${ID_FS_TYPE})
					LOCKFS="false"
					break
					;;
			esac
		done
	fi

elif	[ -f "${1}" ]
then
	lofile="${1}"
	LOFILE="${robr}${lofile}"

else
	# There is no block device to mount (here 'block device' includes
	# files associated to a loop device). Bind mounts and remote fs
	# should have been discarded by the bilibop-lockfs script in the
	# initramfs...
	LOCKFS="false"
fi

mntpnt="${2}"
options="${4}"


# Parse mount options. Two cases:
# 1. the block device will be mounted with the same options than in the
#    original fstab entry, plus 'ro'.
# 2. the tmpfs will be mounted with only some options of the previous:
#    ro, nodev, noexec, nosuid, if they exist.

for	opt in $(IFS=',' ; echo ${options})
do
	# 1. Options for the readonly branch:
	case	"${opt}" in
		fstype=*)
			eval "${opt}"
			;;
		rw)
			;;
		*)
			robr_opts="${robr_opts:+${robr_opts},}${opt}"
			;;
	esac

	# 2. Options for the writable branch:
	case	"${opt}" in
		ro|nodev|noexec|nosuid)
			rwbr_opts="${rwbr_opts:+${rwbr_opts},}${opt}"
			;;
		*)
			;;
	esac
done


# If bilibop-lockfs is not enabled (the device is whitelisted), rewrite the
# fstab entry and do a normal mount:
if	[ "${LOCKFS}" != "true" ]
then
	mount_fallback "${device:-${lofile}}" "${mntpnt}" "${fstype}" "${robr_opts}"
	exit $?
fi


# Each readonly branch is mounted under the subtree of the main readonly
# branch (/aufs/ro) and each writable branch is mounted under the subtree
# of the main writable branch (/aufs/rw):
robr="${robr}${mntpnt}"
rwbr="${rwbr}${mntpnt}"


# If the policy is not explicitly set to 'soft', set the block device as
# readonly, and use 'rr' aufs option to improve performances:
if	[ "${BILIBOP_LOCKFS_POLICY}" = "soft" ]
then
	RO="ro"
else
	RO="rr"
	[ -b "${device}" ] && blockdev --setro ${device}
fi

# Try to mount the readonly branch. In case of failure, undo what has been
# done before, do a normal mount, rewrite the fstab entry to be coherent
# with that, and exit:
if	! mount ${fstype:+-t ${fstype}} -o ${robr_opts:+${robr_opts},}ro ${device:-${LOFILE}} ${robr}
then
	[ "${RO}" = "rr" ] && [ -b "${device}" ] && blockdev --setrw "${device}"
	mount_fallback "${device:-${lofile}}" "${mntpnt}" "${fstype}" "${robr_opts}"
	exit 3
fi

# The amount of RAM to allow to this mountpoint:
SIZE=
for	size in ${BILIBOP_LOCKFS_SIZE}
do
	case	"${size}" in
		${mntpnt}=[0-9]*[0-9KkMmGg%])
			SIZE="${size#${mntpnt}=}"
			break
			;;
	esac
done

# Create the mountpoint (it should not exist before this step):
if	[ ! -d "${rwbr}" ]
then
	mkdir -p ${rwbr}
	grep -q "^${mntpnt}$" ${BILIBOP_RUNDIR}/lock ||
	echo "${mntpnt}" >>${BILIBOP_RUNDIR}/lock
fi

# Try to mount the writable branch, and in case of failure, undo what
# has been done before, etc.
if	! mount -t tmpfs -o ${rwbr_opts:+${rwbr_opts},}${SIZE:+size=${SIZE},}mode=0755 tmpfs ${rwbr}
then
	umount ${robr}
	[ "${RO}" = "rr" ] && [ -b "${device}" ] && blockdev --setrw "${device}"
	mount_fallback "${device:-${lofile}}" "${mntpnt}" "${fstype}" "${robr_opts}"
	exit 3
fi


# Fix permissions and ownership of the writable branch (and catch the values;
# they will be reused later):

mod="$(LC_ALL=C chmod -v --reference="${robr}" "${rwbr}" | sed 's;.* \([0-7]\{4\}\) (.\+)$;\1;')"
own="$(LC_ALL=C chown -v --reference="${robr}" "${rwbr}" | sed 's;.* \([^:]\+:[^:]\+\)$;\1;')"

owner="${own%:*}"
if	[ "${owner}" != "root" ]
then	uid="$(grep "^${owner}:" /etc/passwd | sed 's;^\([^:]*:\)\{2\}\([^:]\+\):.*;\2;')"
fi

group="${own#*:}"
if	[ "${group}" != "root" ]
then	gid="$(grep "^${group}:" /etc/group | sed 's;^\([^:]*:\)\{2\}\([^:]\+\):.*;\2;')"
fi


# Try to mount the aufs now. In case of failure, undo what has been done
# before, etc.
if	! mount -t aufs -o br:${rwbr}=rw:${robr}=${RO} none ${mntpnt}
then
	umount ${robr}
	umount ${rwbr}
	[ "${RO}" = "rr" ] && [ -b "${device}" ] && blockdev --setrw "${device}"
	mount_fallback "${device:-${lofile}}" "${mntpnt}" "${fstype}" "${robr_opts}"
	exit 3
fi

# Special case for 'crypt', 'crypt_LUKS' and 'crypto_LUKS' fstypes, when
# BILIBOP_LOCKFS_POLICY is hard: two block devices have to be set readonly:
# the second one is created by mount.crypt by using dm-crypt, and its dm
# name is the full path of the underlying device, whose the '/' have been
# replaced by '_'.
if	[ "${BILIBOP_LOCKFS_POLICY}" != "soft" ]
then
	case	"${fstype}" in
		crypt|crypt_LUKS|crypto_LUKS)
			DM_NAME="$(echo "${device}" | sed 's,/,_,g')"
			[ -b "${udev_root}/mapper/${DM_NAME}" ] ||
			DM_NAME="$(readlink -f "${device}" | sed 's,/,_,g')"
			[ -b "${udev_root}/mapper/${DM_NAME}" ] &&
			blockdev --setro $(readlink -f "${udev_root}/mapper/${DM_NAME}")
			;;
	esac
fi

# All is OK. So we can rewrite fstab entry to reflect the real mounts. This
# can be important for clean unmounts at shutdown (for the case a filesystem
# is remounted rw during a session).
robr_line="${device:-${LOFILE}} ${robr} ${fstype:-auto} ${robr_opts:+${robr_opts},}ro 0 0"
rwbr_line="tmpfs ${rwbr} tmpfs ${rwbr_opts:+${rwbr_opts},}${SIZE:+size=${SIZE},}${uid:+uid=${uid},}${gid:+gid=${gid},}mode=${mod} 0 0"
aufs_line="none ${mntpnt} aufs br:${rwbr}=rw:${robr}=${RO} 0 0"

sed -i "s;^\s*[^#][^ ]\+\s\+${mntpnt}\s\+lockfs\s.*;${robr_line}\n${rwbr_line}\n${aufs_line};" /etc/fstab

