#!/bin/sh
# remount EVMS-created volumes
# see https://bugzilla.altlinux.org/28181

# destdir must be defined by this time
check_destdir() {
	[ -n "$destdir" ] || exit 0
	[ -d "$destdir" ] || exit 1
}

remount_chroot() {
	# no dups here:
	# - bring lvm down;
	# - free the appropriate volumes (could be on mdraid too);
	# - bring mdraid down;
	# - free the appropriate block devices
	(
		set -x
		check_destdir &&
		save_blkid_state &&
		populate_fstab &&
		copy_chroot_binaries &&
		umount_chroot &&
		stop_luks &&
		reset_devmapper &&
		stop_mdraid &&
		reset_devmapper &&
		part_probe &&
		start_multipath &&
		start_mdraid &&
		start_lvm &&
		start_luks &&
		mount_chroot &&
		systemd_tmpfiles_chroot &&
		set_active \
	) >& /tmp/remount.log || return $?
}

remount_destination() {
	# remount destdir after alterator-vm
	# no mount chroot filesystem (/dev, /proc, /sys)
	# no dups here:
	# - bring lvm down;
	# - free the appropriate volumes (could be on mdraid too);
	# - bring mdraid down;
	# - free the appropriate block devices
	(
		set -x
		check_destdir &&
		save_blkid_state &&
		umount_destination &&
		stop_luks &&
		reset_devmapper &&
		stop_mdraid &&
		reset_devmapper &&
		part_probe &&
		start_multipath &&
		start_mdraid &&
		start_lvm &&
		start_luks &&
		mount_destination &&
		set_active \
	) >& /tmp/remount.log || return $?
}

# avoid automatic rpm shell.req dependency
MULTIPATHD=/sbin/multipathd
MDADM=/sbin/mdadm
LVM=/sbin/lvm
CRYPTSETUP=/sbin/cryptsetup
CRYPTSETUP_KEY=/tmp/empty
PUTFILE=/usr/share/make-initrd/tools/put-file
BLKID="blkid -c /dev/null"

# alterator-vm should leave LUKS containers
# with initial empty password, see #28200
:> "$CRYPTSETUP_KEY"

# for installer-feature-desktop-other-fs, see also #29005
save_blkid_state() {
	find /dev/mapper -type l \
	| xargs -r $BLKID \
	> /tmp/blkid.dm
}

populate_fstab() {
	[ ! -f /tmp/fstab ] ||
		cat /tmp/fstab >> "$destdir/etc/fstab"
}

copy_chroot_binaries() {
	useputfile=
	if [ -x "$destdir$PUTFILE" ]; then
		useputfile='yes'
		binddir="$(mktemp -d "$destdir/tmp/copy_chroot_binaries.XXXXXXXXX")"
		workdir="${binddir#$destdir}"
		mount --bind / "$binddir"
	else
		echo "remount: file does not exist or is not available for execution: $destdir$PUTFILE" >&2
	fi

	# these are normally missing in installer environment
	# NB: /sbin writes rely on aufs in fact, would use /tmp otherwise
	#     but that might clobber cases when the binaries have to differ
	for i in "$MDADM" "$LVM" "$CRYPTSETUP"; do
		if [ ! -x "$i" -a -x "$destdir$i" ]; then
			echo "remount: copying $i" >&2
			if [ -n "$useputfile" ]; then
				chroot "$destdir" "$PUTFILE" "$workdir" "$i" ||:
			else
				cp -p "$destdir$i" "$i"
			fi
		fi
	done

	if [ -e "$destdir/etc/lvm/lvm.conf" ]; then
		echo "remount: copying /etc/lvm/lvm.conf" >&2
		if [ -n "$useputfile" ]; then
			chroot "$destdir" "$PUTFILE" "$workdir" "/etc/lvm/lvm.conf" ||:
		else
			if [ -f "$destdir/etc/lvm/lvm.conf" ]; then
				mkdir -p /etc/lvm &&
				cp -p "$destdir/etc/lvm/lvm.conf" /etc/lvm
			else
				echo "remount: /etc/lvm/lvm.conf no file -- not copying!" >&2
			fi
		fi
	fi

	if [ -n "$useputfile" ]; then
		umount "$binddir"
		rmdir "$binddir"
	fi
}

umount_chroot() {
	fuser -vv -k -m "$destdir"

	chroot "$destdir" swapoff -a

	umount "$destdir/dev/pts"
	chroot "$destdir" umount -a -v

	if [ -d "$destdir"/sys/firmware/efi/efivars ]; then
		umount -v "$destdir"/sys/firmware/efi/efivars || :
	fi

	if [ -d /run/udev ]; then
		umount -v "$destdir/run/udev"
	fi

	umount -v "$destdir/run"
	umount -v "$destdir/tmp"

	# defuse
	umount -a -v -t fuse.gvfsd-fuse

	# for the log
	cat /proc/mounts

	# recursive unmount destination directory
	umount -Rv "$destdir"

	# check that the unmount was successful
	! mountpoint "$destdir" || return 1
}

reset_devmapper() {
	# evms_deactivate does essentially the same but is normally missing
	dmsetup remove_all ||:
}

list_partitions() {
	local drive="$1"
	sfdisk --dump "$drive" 2>/dev/null | awk -F ':' '/^[/]dev[/]/ { print $1 }'
}

validate_drive_partitions_devnodes() {
	# Remove all partitions' device nodes which belong to the given drive
	# if there are any stale partitions' device nodes associated with
	# that drive.

	# XXX: evms messes up kernel blockdev ownership/reference counts.
	# As a result some partitions' device nodes might become stale,
	# like this:
	#
	# # ls -l /dev/nvme0n1
	# brw-rw---- root disk 259,0 16:03 /dev/nvme0n1
	# brw-r----- root root 253,0 16:03 /dev/nvme0n1p1
	#
	# The major number of `nvme0n1p1` (253) does not match the major number
	# of the whole drive `nvme0n1` (259), and the minor number (0) is just
	# plain wrong. The stale devices nodes return ENXIO when opening them,
	# as a result `lsblk` is unable to find a partition by filesystem UUID,
	# and installation fails. To avoid the problem stale device nodes
	# must be removed, and the correct one must be created instead (possibly
	# indirectly, i.e. with blockdev --rereadpt).
	#
	# XXX: removing the stale device nodes is necessary, but NOT enough.
	# All partitions' device nodes (NOT the partitions themselves) which
	# belong to the drive in question must be removed for `blockdev --rereadpt`
	# to re-create the missing device nodes (presumably the existing device
	# node keeps a reference to the parent kobject and the kernel will not
	# emit `new block device` uevent as a result).
	#
	# Example. Assume there are the following device nodes
	# # ls -l /dev/nvme0n1
	# brw-rw---- root disk 259,0 16:03 /dev/nvme0n1
	# brw-r----- root root 253,0 16:03 /dev/nvme0n1p1
	# brw-r----- root root 253,0 16:03 /dev/nvme0n1p2
	# brw-r----- root root 253,0 16:03 /dev/nvme0n1p3
	# brw-rw---- root disk 259,7 16:03 /dev/nvme0n1p4
	#
	# Here /dev/nvme0n1p{1,2,3} are stale (wrong major number, invalid minor one),
	# and `/dev/nvme0n1p4` is valid. If one removes just stale /dev/nvme0n1p{1,2,3}
	# `blockdev --rereadpt /dev/nvme0n1` will NOT create the valid device nodes.
	# For that to work all /dev/nvme0n1p* must be removed, including a valid /dev/nvme0n1p4

	local drive="$1"
	local partitions part
	local major_minor
	local rescan='no'
	partitions=`list_partitions "$drive"`
	for part in $partitions; do
		if ! test -b "$part"; then
			rescan='yes'
			echo "device node $part is missing, rescan \"$drive\""
			break
		fi
		major_minor=`stat -c '%Hr:%Lr' "$part"`
		if ! test -L "/sys/dev/block/${major_minor}"; then
			echo "device node $part is possibly stale"
			# Will get ENXIO on a state device
			if ! blockdev --getsz "$part"; then
				rescan='yes'
				echo "device node $part is stale, rescan \"$drive\""
				break
			fi
		fi
	done
	if [ "$rescan" = 'yes' ]; then
		# XXX: if there are no partitions `xargs -r rm -f` is a nop, and
		# `rm -f $partitions` is a syntax error
		echo $partitions | xargs -r rm -f
		blockdev --rereadpt "$drive"
	fi
}

validate_all_partitions_devnodes() {
	local drive

	find /sys/block -type l -printf "%f\n" | \
	while read drive; do
		validate_drive_partitions_devnodes "/dev/$drive"
	done
}


part_probe() {
	# remove stale/broken device nodes broken by evms
	validate_all_partitions_devnodes
	# re-read partition table for kernel
	partprobe -s ||:
	# delay 5 second for kernel
	sleep 5
}

stop_mdraid() {
	# saving state is only important *after* evms
	if [ -f /proc/mdstat -a -x "$MDADM" ]; then
		"$MDADM" --examine --scan > /tmp/mdadm.conf
		"$MDADM" -v --stop --scan
	fi
}

start_mdraid() {
	if [ -s /tmp/mdadm.conf -a -x "$MDADM" ]; then
		# an arbitrary value of the year: packages installed already
		sysctl -w dev.raid.speed_limit_max=1000000
		# chroot's mdadm.conf populated by 45-mdadm.sh
		"$MDADM" -v --assemble --run --scan --config=/tmp/mdadm.conf ||:
	fi
}

start_multipath() {
	if [ -x "$MULTIPATHD" ]; then
		"$MULTIPATHD" reconfigure ||:
	fi
}

start_lvm() {
	if [ -x "$LVM" ]; then
		"$LVM" pvscan ||:
		"$LVM" vgscan ||:
		"$LVM" vgchange -ay --noudevsync ||:
		"$LVM" lvscan ||:
		"$LVM" lvchange -ay --noudevsync ||:
	fi
}

stop_luks() {
	if [ -x "$CRYPTSETUP" ]; then
		pushd /dev/mapper
		for i in *_luks; do
			"$CRYPTSETUP" close "$i"
		done
		popd
	fi
}

start_luks() {
	if [ -x "$CRYPTSETUP" ]; then
		for device in $($BLKID -o device); do
			[ "$($BLKID -o value -s TYPE "$device")" = "crypto_LUKS" ] || continue
			"$CRYPTSETUP" isLuks "$device" || continue
			echo "" | "$CRYPTSETUP" luksOpen --test-passphrase "$device" || continue
			"$CRYPTSETUP" --key-file "$CRYPTSETUP_KEY" luksOpen "$device" "$(basename "$device")_luks"
		done
	fi
}

mount_destfs() {
	# depends on /tmp/fstab just like 10-fstab.sh
	local mpoint="$1"
	mountpoint -q "$destdir""$mpoint" && return 0
	local destfs="$(awk -v mpoint="$mpoint" '{ if ($2==mpoint) print $1 }' < /tmp/fstab)"
	case "$destfs" in
	UUID=*)
		destfs="`$BLKID -U ${destfs#UUID=}`"
		;;
	LABEL=*)
		destfs="`$BLKID -L ${destfs#LABEL=}`"
		;;
	/*)
		# all good as is
		;;
	*)
		echo "remount: unknown filesystem for $mpoint: $destfs" >&2
		return 2
		;;
	esac

	if [ -z "$destfs" ]; then
		echo "remount: unable to re-identify device for $mpoint"
		echo "by ${UUID:+UUID=$UUID}${LABEL:+LABEL=$LABEL}"
		time $BLKID
		return 3
	fi >&2

	mountopts="$(grep "[[:space:]]$mpoint[[:space:]]" /tmp/fstab | awk '{ print $4 }')"

	mount -v "$destfs" "$destdir$mpoint" -o "$mountopts" || return 3
}

mount_chroot() {
	mount_destfs / || return $?

	mount -v -o bind /dev "$destdir/dev"
	mount -v -o bind /dev/pts "$destdir/dev/pts"
	mount -v -t sysfs sysfs "$destdir/sys"
	mount -v -t proc proc "$destdir/proc"
	
	mount runfs -t tmpfs -o mode=755 "$destdir/run"
	mount tmpfs -t tmpfs -o mode=755 "$destdir/tmp"

	if [ -d /run/udev ]; then
		mkdir -p "$destdir/run/udev"
		mount -v -o bind /run/udev "$destdir/run/udev"
	fi

	if [ -d /sys/firmware/efi/efivars ]; then
		mount -v -o bind /sys/firmware/efi/efivars \
		"$destdir/sys/firmware/efi/efivars" || :
	fi

	chroot "$destdir" mount -a -v ||:
	chroot "$destdir" swapon -a ||:

	# NB: use "sleep 3600" here to debug :)
}

# unmount destdir after alterator-vm
umount_destination() {
	swapoff -a
	fuser -vv -k -m "$destdir"

	# recursive unmount destination directory
	umount -Rv "$destdir"

	# check that the unmount was successful
	! mountpoint "$destdir" || return 1
}

# mount destdir without make chroot, after umount_destination
mount_destination() {
	[ -s /tmp/fstab ] || return 4
	for mpoint in $(awk '{ print $2 }' < /tmp/fstab | grep / | sort); do
		mount_destfs $mpoint || return $?
	done
}

systemd_tmpfiles_chroot() {
	if [ -s $destdir/sbin/systemd-tmpfiles ]; then
		systemd_tmpfiles=/sbin/systemd-tmpfiles
	elif [ -s $destdir/sbin/systemd-tmpfiles.standalone ]; then
		systemd_tmpfiles=/sbin/systemd-tmpfiles.standalone
	else
		return 5
	fi
	chroot "$destdir" $systemd_tmpfiles --create
}

# some firmwares, notably intel/dell ones, refuse to boot
# off drives without active partitions; provide some
# NB: lvm/luks don't count as plain /boot is expected then
# NB: we don't want EVMS names so do this *late*
set_active() {
	cat /proc/mounts | grep -q "$destination"/boot/efi && return 0
	mountpoint -q $destdir || return 6
	local BOOTDEV=
	[ -d "$destdir"/boot ] || BOOTDEV="$(df -l --output=source "$destdir" | tail -1)"
	[ -n "$BOOTDEV" ] || BOOTDEV="$(df -l --output=source "$destdir"/boot | tail -1)"
	[ -n "$BOOTDEV" ] || return 7
	BOOTDEV="${BOOTDEV#/dev/}"
	case "$BOOTDEV" in
		md*) BOOTDEV="$(ls /sys/block/"$BOOTDEV"/slaves/)";;
	esac
	[ -n "$BOOTDEV" ] || return 0
	for d in $BOOTDEV; do
		sfdisk -q -A "/dev/${d%%[0-9]*}" "${d##*[^0-9]}" 2>/dev/null ||:
	done
}
