#!/bin/sh

PREREQ=""

prereqs()
{
	echo "$PREREQ"
}

case $1 in
	prereqs)
		prereqs
		exit 0
	;;
esac

# Lock root filesystem by mounting it readonly with aufs, and modify fstab on
# the fly to prepare to automatically lock other filesystems later in the boot
# process.
# We assume that /proc, /sys and /dev are correctly mounted.

# /proc/cmdline will be parsed several times. We call 'cat' only one time:
readonly CMDLINE="$(cat /proc/cmdline)"

# Set verbosity from boot commandline:
for	param in ${CMDLINE}
do
	case	"${param}" in
		quiet)
			quiet="y"
			break
			;;
	esac
done

# Load functions:
. /scripts/functions
. /lib/bilibop/lockfs.sh
get_bilibop_variables ${rootmnt}


# The bilibop functions assume that udev_root can be different from /dev; but
# this applies only for the running system, not for the initramfs. So, we have
# to fix it here:
get_udev_root
UDEV_ROOT="${udev_root}"
udev_root="/dev"


# Check if BILIBOP_LOCKFS is overridden from the boot commandline. To use
# lockfs in single-user mode, it is necessary to use 'lockfs=force'.
for	param in ${CMDLINE}
do
	case	"${param}" in
		single|S|1)
			single="true"
			;;
		[02-6])
			single="false"
			;;
		nolockfs)
			BILIBOP_LOCKFS="false"
			_force="false"
			;;
		lockfs)
			BILIBOP_LOCKFS="true"
			;;
		lockfs=*)
			BILIBOP_LOCKFS="true"
			for	opt in $(IFS=',' ; echo ${param#lockfs=})
			do
				case	"${opt}" in
					default)
						BILIBOP_LOCKFS_POLICY=""
						BILIBOP_LOCKFS_WHITELIST=""
						BILIBOP_LOCKFS_SIZE=""
						BILIBOP_LOCKFS_SWAP_POLICY=""
						BILIBOP_LOCKFS_NOTIFY_POLICY=""
						_force="false"
						SIZE=""
						;;
					force)
						_force="true"
						;;
					hard|soft)
						BILIBOP_LOCKFS_POLICY="${opt}"
						;;
					[1-9]*)
						SIZE="$(printf ${opt} | grep '^[1-9][0-9]*[KkMmGg%]\?$')"
						;;
					all)
						BILIBOP_LOCKFS_WHITELIST=""
						;;
					-/*)
						BILIBOP_LOCKFS_WHITELIST="${BILIBOP_LOCKFS_WHITELIST:+${BILIBOP_LOCKFS_WHITELIST} }${opt#-}"
						;;
				esac
			done
			;;
		noswap)
			BILIBOP_LOCKFS_SWAP_POLICY="hard"
			;;
	esac
done

# 'lockfs=force' boot option is available only for single-user sessions:
if	[ "${single}" = "true" ]
then	BILIBOP_LOCKFS="false"
	[ "${_force}" = "true" ] &&
	BILIBOP_LOCKFS="true"
fi

# Collect information that will be used later:
BILIBOP_ROOT="$(underlying_device_from_file ${rootmnt})"
BILIBOP_PART="$(underlying_partition ${BILIBOP_ROOT})"
BILIBOP_DISK="$(physical_hard_disk ${BILIBOP_PART})"

# Check if the drive is physically locked (write protected); if it is the case,
# this will override several variables.
if is_physically_locked ${BILIBOP_DISK##*/}; then
	mkdir -p "${BILIBOP_RUNDIR}"
	cat >${BILIBOP_RUNDIR}/plocked <<EOF
# It seems that ${BILIBOP_DISK} is physically locked.
BILIBOP_LOCKFS="true"
BILIBOP_LOCKFS_POLICY="hard"
BILIBOP_LOCKFS_SWAP_POLICY="hard"
BILIBOP_LOCKFS_WHITELIST=""
EOF
	. ${BILIBOP_RUNDIR}/plocked
fi

# Now, if BILIBOP_LOCKFS is not explicitly set to "true" or "false", then
# check the sysfs 'removable' flag to know what to do.
case	"${BILIBOP_LOCKFS}" in
	true)
		log_warning_msg "${0##*/}: Locking filesystem."
		;;
	false)
		log_warning_msg "${0##*/}: Nothing to do."
		case "${BILIBOP_LOCKFS_NOTIFY_POLICY}" in
			never|lockfs) ;;
			*) plymouth_message "${0##*/} is disabled" ;;
		esac
		exit 0
		;;
	*)
		if	is_removable ${BILIBOP_DISK}
		then	log_warning_msg "${0##*/}: Locking filesystem."
		else	log_warning_msg "${0##*/}: Nothing to do."
			case "${BILIBOP_LOCKFS_NOTIFY_POLICY}" in
				never|lockfs) ;;
				*) plymouth_message "${0##*/} is disabled" ;;
			esac
			exit 0
		fi
		;;
esac


# Exit if ${rootmnt} is already mounted as an aufs branch (by fsprotect?):
if	is_aufs_mountpoint -q "${rootmnt}"
then	log_warning_msg "${0##*/}: Root filesystem is already mounted as aufs..."
	log_failure_msg "${0##*/}: Exit."
	plymouth_message "${0##*/}: ERROR"
	exit 0
fi


# Load the aufs module:
modprobe aufs >/dev/null 2>&1

# Check if the aufs filesystem is now supported:
if	! grep -q '\<aufs\>' /proc/filesystems
then
	log_failure_msg "${0##*/}: No aufs kernel support."
	log_failure_msg "${0##*/}: Exit."
	plymouth_message "${0##*/}: ERROR"
	exit 1
fi

# If BILIBOP_LOCKFS_POLICY is not explicitly set to 'soft', then apply a hard
# policy: set readonly branch as 'rr' (real readonly) instead of just 'ro' and
# set the root device and all its parent devices until the whole disk itself
# as readonly, with blockdev(8):
if	[ "${BILIBOP_LOCKFS_POLICY}" = "soft" ]
then	RO="ro"
else	RO="rr"
	blockdev_root_subtree ro ${BILIBOP_ROOT} ${BILIBOP_DISK}
	BILIBOP_LOCKFS_POLICY="hard"
fi

# Determine tmpfs size for the aufs writable branch:
if	[ -z "${SIZE}" ]
then
	for	size in ${BILIBOP_LOCKFS_SIZE}
	do
		case	"${size}" in
			/=[1-9]*)
				SIZE="$(printf ${size#/=} | grep '^[1-9][0-9]*[KkMmGg%]\?$')"
				break
				;;
		esac
	done
fi

log_begin_msg "${0##*/}: Setting up aufs branches"

# Prepare directories:
BASEDIR="/aufs"
HOSTDIR="${BASEDIR}/ro"
TEMPDIR="${BASEDIR}/rw"
AUFSDIR="${BASEDIR}/aufs"

[ -d "${BASEDIR}" ] || mkdir "${BASEDIR}"
[ -d "${HOSTDIR}" ] || mkdir "${HOSTDIR}"
[ -d "${TEMPDIR}" ] || mkdir "${TEMPDIR}"
[ -d "${AUFSDIR}" ] || mkdir "${AUFSDIR}"

mount -o bind ${rootmnt} ${HOSTDIR}

if	! mount -t tmpfs -o mode=0755${SIZE:+,size=${SIZE}} tmpfs ${TEMPDIR}
then
	# Don't forget to undo what has been done before !
	umount ${HOSTDIR}
	[ "${BILIBOP_LOCKFS_POLICY}" = "soft" ] ||
	blockdev_root_subtree rw ${BILIBOP_ROOT} ${BILIBOP_DISK}

	log_failure_msg "${0##*/}: Error occured when setting aufs writable branch."
	log_failure_msg "${0##*/}: Exit."
	plymouth_message "${0##*/}: ERROR"
	exit 1
fi

# Now do the job:
if	mount -t aufs -o br:${TEMPDIR}=rw:${HOSTDIR}=${RO} none ${AUFSDIR}
then
	umount ${rootmnt}
	mount -o move ${AUFSDIR} ${rootmnt}
	mkdir ${rootmnt}${BASEDIR}
	mkdir ${rootmnt}${HOSTDIR}
	mkdir ${rootmnt}${TEMPDIR}
	mount -o move ${HOSTDIR} ${rootmnt}${HOSTDIR}
	mount -o move ${TEMPDIR} ${rootmnt}${TEMPDIR}
else
	# Again, don't forget to undo what has been done before:
	[ "${BILIBOP_LOCKFS_POLICY}" = "soft" ] ||
	blockdev_root_subtree rw ${BILIBOP_ROOT} ${BILIBOP_DISK}
	umount ${HOSTDIR}
	umount ${TEMPDIR}

	_log_msg "failed.\n"
	log_failure_msg "${0##*/}: Persistent root filesystem is writable."
	plymouth_message "${0##*/}: ERROR"
	exit 1
fi

# Create a file to say the root filesystem is locked:
[ -d "${BILIBOP_RUNDIR}" ] || mkdir "${BILIBOP_RUNDIR}"
>${BILIBOP_RUNDIR}/lock

log_end_msg
log_success_msg "${0##*/}: Root filesystem is now locked (${BILIBOP_LOCKFS_POLICY} policy)."
case "${BILIBOP_LOCKFS_NOTIFY_POLICY}" in
	never|nolockfs) ;;
	*) plymouth_message "${0##*/}: ${BILIBOP_LOCKFS_POLICY} policy is enabled" ;;
esac

# Avoid filesystems check:
>${rootmnt}/fastboot

# This is for the case LVM is used for something else than $ROOT and $resume,
# and cryptsetup is not used (no /conf/conf.d/cryptroot in the initrd); in
# such a case, only $ROOT and $resume are activated from initrd; others are
# activated from the system with an initscript; but:
# 1. if BILIBOP_LOCKFS_POLICY is 'hard', this initscript will have no effect
# 2. we need to make devices available before parsing ${rootmnt}/etc/fstab
activate_bilibop_lv

# Rebuild fstab to automatically mount other filesystems as readonly aufs
# branches:
log_warning_msg "${0##*/}: Modifying temporary static filesystem table (fstab)."
FSTAB="${rootmnt}/etc/fstab"
>>${FSTAB}			# touch the file

comment="# Original line commented by ${0##*/}:"
replace="# ...and replaced by:"

# Comment the line about the root filesystem, which must not be managed later
# by initscripts or whatever:
sed -i "s|^\s*[^[:blank:]]\+\s\+/\s.*|\n${comment}\n#&\n|" ${FSTAB}

# Add /etc/fstab to the list of files that have been modified. This is not
# needed now, but will be used by further purposes (as the ability to sync
# on the readonly branch the files that have been modified on the writable
# branch, but those listed in ${BILIBOP_RUNDIR}/lock):
lock_file "/etc/fstab"

# Now parse fstab and modify some entries (and optionally modify crypttab
# too):
parse_and_modify_fstab

# Be sure the lockfs mount helper script will be executed when needed:
grep -q '[[:blank:]]lockfs[[:blank:]]' ${FSTAB} &&
check_mount_lockfs "${rootmnt}"

# Avoid breakage of read-only settings by LVM tools:
if	[ -f "/etc/lvm/bilibop" ] &&
	[ "${BILIBOP_LOCKFS_POLICY}" != "soft" ]
then
	eval $(grep '^[[:blank:]]*LVM_SYSTEM_DIR=' ${rootmnt}/etc/environment)
	LVM_CONF="${rootmnt}${LVM_SYSTEM_DIR:=/etc/lvm}/lvm.conf"
	initialize_lvm_conf "${UDEV_ROOT}"
	set_readonly_lvm_settings
	blacklist_bilibop_devices
	>>${LVM_CONF}
	lock_file "${LVM_SYSTEM_DIR}/lvm.conf"
fi


# Now modify some other files, or execute specific actions if wanted by the
# sysadmin: set the hostname, modify MAC addresses, set autologin, and so on.
# For example:
# touch ${rootmnt}/* ${rootmnt}/bin/* ${rootmnt}/sbin/*
# find ${rootmnt}/etc ${rootmnt}/root -type f -xdev -exec touch {} \;
# find ${rootmnt} -type d -xdev -exec touch {} \;
# find ${rootmnt}/lib -xdev -exec touch {} \;
# and maybe more would allow the admin to rescue the session even if the
# device is wildly unplugged.
# TODO: this is Still In Development
# Maybe we have to provide a sample file (skeleton) including instructions to
# write a script that can be executed both from the initramfs environment or
# from the running system.
# XXX: is it standards compliant ?
for	param in ${CMDLINE}
do
	case	"${param}" in
		config)
			BILIBOP_LOCKFS_RUN_SCRIPTS="true"
			;;
		noconfig)
			BILIBOP_LOCKFS_RUN_SCRIPTS="false"
			;;
	esac
done

[ "${BILIBOP_LOCKFS_RUN_SCRIPTS}" = "true" ] &&
if	[ -d "${rootmnt}/etc/bilibop/lockfs.d" ]
then
	for	exe in ${rootmnt}/etc/bilibop/lockfs.d/[0-9][0-9]_[a-z]*[a-z].sh
	do
		[ -x "${exe}" ] && ${exe} ${rootmnt}
	done
fi

:
