#!/bin/bash -efu

check_pmbr_records()
{
	enter "check_pmbr_records"

	# The disk should have a GUID/GPT label
	partn="sfdisk -l"
	debug "RUN: $partn -- $whole"
	LC_ALL=C $partn -- "$whole" >"$tmpf" 2>&1 ||:
	fdump "$tmpf"
	#
	if ! grep -qs 'Disklabel type: gpt' "$tmpf"; then
		leave "check_pmbr_records"
		return 1
	fi

	# The disk should have a Protective MBR
	partn="sfdisk -l --label-nested=mbr"
	debug "RUN: $partn -- $whole"
	LC_ALL=C $partn -- "$whole" >"$tmpf" 2>/dev/null ||:
	fdump "$tmpf"
	#
	if ! grep -qs 'Disklabel type: dos' "$tmpf"; then
		leave "check_pmbr_records"
		return 1
	fi

	# Create a second temporary file with only the list of partitions
	cat -- "$tmpf" |sed '1,/^Device /d' |sed '/^$/,$d' >"$tmpf"_1

	# PMBR should have only two specific records
	partn="$(wc -l -- "$tmpf"_1 |sed 's/ .*$//')"
	if [ "$partn" = 2 ] &&
	   grep -qsE ' ee GPT$' "$tmpf"_1 &&
	   grep -qsE ' 0 Empty$' "$tmpf"_1
	then
		run rm -f -- "$tmpf"_1
		leave "check_pmbr_records"
		return 0
	fi

	run rm -f -- "$tmpf"_1
	leave "check_pmbr_records"
	return 1
}

remove_isomd5sum()
{
	local i rc=0 image="$1"

	checkisomd5 --md5sumonly -- "$image" >/dev/null 2>&1 ||
		return 0
	message "isomd5sum signature has been found, it will be removed"

	# FIXME: The block 'volume_info' really starts with 0x8373 (33651), it is
	#        filled with spaces and has length of 512 bytes, but as I see it,
	#        only the first 206 bytes actually changing with the implantisomd5
	#        utility. At the same time APPDATA_SIZE is 512 bytes, APPDATA_OFFSET
	#        is 883 bytes (and 32768 + 883 = 33651), it is defined in this file:
	#        https://github.com/rhinstaller/isomd5sum/blob/master/utilities.h
	#        So, the next snippet filled the whole field by the default value.
	#        See also ECMA-119, the section 8.4.32, it is about APPDATA usage.
	for i in $(seq 1 16); do
		# 16*32 = 512 spaces
		printf '                                '
	done |
	dd "of=$image" bs=1 seek=33651 conv=notrunc 2>/dev/null || rc="$?"
	run sync

	if [ "$rc" != 0 ] ||
	   checkisomd5 --md5sumonly -- "$image" >/dev/null 2>&1
	then
		message "unable to remove an isomd5sum signature"
	fi
}

create_disk_slice()
{
	enter "create_disk_slice"

	longtermop_start
	create_disk_slice_inner
	longtermop_end

	leave "create_disk_slice"
}

create_disk_slice_inner()
{
	local devname

	# First, use the exported value
	devname="$(stage2_getenv DEVICE)"

	# Fallback: use if DEVICE not exported to stage2
	if [ -z "$devname" ] && [ -n "$OEM_CDROOT" ]; then
		devname="$(mountpoint -d -- "$OEM_CDROOT" ||:)"
		devname="/sys/dev/block/$devname/uevent"
		[ ! -f "$devname" ] && devname="" ||
			devname="$(grep -sE '^DEVNAME=' "$devname" |cut -f2- -d=)"
	fi
	if [ -z "$devname" ] || [ ! -b "/dev/$devname" ]; then
		message "stage2-compatible live-rw boot media not found"
		return 0
	fi

	local whole

	# if DEVICE is a partition, try to find the appropriate parent disk
	whole="$(parent_disk "$devname")"

	if [ -n "$whole" ]; then
		debug "whole disk: '$whole'"
	else
		message "/dev/$devname is a whole disk, partitions appending is inpossible"
		return 0
	fi

	local devpath partn

	# Check for DEVICE is not read-only
	devpath="$(devname2sysfs "${whole#/dev/}")"
	[ -r "$devpath/ro" ] && read -r partn <"$devpath/ro" ||
		partn=0
	if [ "$partn" != 0 ]; then
		message "$whole is read-only, partitions appending is inpossible"
		return 0
	fi

	local tmpf

	# Use for store output
	tmpf="$(run mktemp -t ||:)"
	if [ ! -f "$tmpf" ]; then
		message "mktemp failed"
		return 0
	fi
	debug "writing the temporary file: '$tmpf'"

	# Prepare for the dialogs
	IM_start_output ponder errmsg
	IM_ponder_start "[ Creating the persistent storage... ]"

	local size_fixed=
	local fix_pmbr=1

	# Check for having valid PMBR records
	check_pmbr_records || fix_pmbr=

	# Check for unpartitioned disk space
	message "searching unpartitioned space on the drive $whole"
	debug "RUN: sfdisk -F -- $whole"
	LC_ALL=C sfdisk -F -- "$whole" >"$tmpf" 2>&1 ||:
	fdump "$tmpf"

	# Try to fix backup GPT PMBR problem, if it exists
	partn="The backup GPT table is not on the end of the device"
	if grep -qs "$partn" "$tmpf"; then
		partn="sfdisk -f --relocate gpt-bak-std"
		debug "RUN: $partn -- $whole"
		LC_ALL=C $partn -- "$whole" >"$tmpf" 2>&1 ||:
		fdump "$tmpf"
		debug "RUN: sfdisk -F -- $whole"
		LC_ALL=C sfdisk -F -- "$whole" >"$tmpf" 2>&1 ||:
		fdump "$tmpf"
		size_fixed=1
	fi

	local start lenght
	local minsize=$((1024 * 1024 * 1024))

	# Determinate free space
	lenght="$(head -n1 -- "$tmpf" |
			grep -sE '^Unpartitioned space ' |
			cut -f6 -d' ')"
	debug "unpartitioned space on the $whole: $lenght bytes"

	# Free space must be at least 1 GiB
	if [ "$lenght" -lt "$minsize" ]; then
		partn="not enough free space to create a new R/W-partition"
		run rm -f -- "$tmpf"
		message "$partn"

		# Remove an isomd5sum signature if an original image changed
		[ -z "$size_fixed" ] ||
			remove_isomd5sum "$whole"

		IM_ponder_stop
		[ -n "$NOASKUSER" ] ||
			IM_errmsg "N${partn:1}! Don't use sessions with this boot media."
		return 0
	fi

	# Try to create a new partition
	message "creating a new partition on the drive $whole"
	partn="sfdisk -f -a --no-reread --no-tell-kernel"
	debug "RUN: echo ',' |$partn -- $whole"
	echo "," |LC_ALL=C $partn -- "$whole" >"$tmpf" 2>&1 ||:
	fdump "$tmpf"
	devname="$(grep -s ": Created a new partition " "$tmpf" |cut -f1 -d:)"
	message "new partition device: '$devname'"
	run rm -f -- "$tmpf"

	# On success only
	if [ -n "$devname" ]; then
		devpath="$(run sfdisk -f -l -- "$whole" |
				grep -s "$devname " |
				sed 's/  */ /g')"
		start="$(echo "$devpath" |cut -f2 -d' ')"
		lenght="$(echo "$devpath" |cut -f4 -d' ')"
		partn="${devname#"$whole"}"
		partn="${partn#p}"

		# Tell the kernel about the changes
		run addpart "$whole" "$partn" "$start" "$lenght" 1>&2 ||:
		partn=0

		# Wait until the kernel re-reads the partition table
		while [ "$partn" -lt "$timeout" ]; do
			[ ! -b "$devname" ] ||
				break
			partn=$((1 + $partn))
			sleep 1
		done
	fi

	# Tricking old BIOS into booting from GUID/GPT
	if [ -n "$fix_pmbr" ]; then
		debug "adding a boot record to the PMBR table"
		printf '\200\0\0\0\0\0\0\0\0\0\0\0\001\0\0\0' |
		dd "of=$whole" bs=1 seek=462 conv=notrunc 2>/dev/null
		run sync
	fi

	# Remove an isomd5sum signature if it exists
	remove_isomd5sum "$whole"

	# The device check should be skipped during this boot
	rw_slice_created=1

	# On fail only
	if [ ! -b "$devname" ]; then
		partn="can't create a new partition on the drive $whole"
		message "$partn"
		IM_ponder_stop
		[ -n "$NOASKUSER" ] ||
			IM_errmsg "C${partn:1}! Don't use sessions with this boot media."
		return 0
	fi

	# Format a new partition
	message "creating the filesystem on $devname"

	local opts="-t ext4 -O ^has_journal,sparse_super2"
	opts="$opts -E packed_meta_blocks=1,num_backup_sb=1"

	if run mke2fs $opts -L "$OEM_LIVE_STORAGE" -- "$devname" 1>&2; then
		debug "in create_disk_slice(): mke2fs success"

		# Tell udevd about the changes
		run udevadm trigger -- "$devname" 1>&2 ||:
		run udevadm settle --timeout=5 1>&2 ||:
		partn=0

		# Wait until udevd creates the symlink
		while [ "$partn" -lt "$timeout" ]; do
			[ ! -L "/dev/disk/by-label/$OEM_LIVE_STORAGE" ] ||
				break
			partn=$((1 + $partn))
			sleep 0.5
		done

		IM_ponder_stop
	else
		debug "in create_disk_slice(): mke2fs failed (rc=$?)"

		# Try to mark the new partition as a _BAD_ storage
		run e2label "$devname" "$OEM_BAD_STORAGE" 1>&2 ||
			run wipefs -q -a -- "$devname" 1>&2 ||:

		# Just tell udevd about the changes
		run udevadm trigger -- "$devname" 1>&2 ||:
		run udevadm settle --timeout=5 1>&2 ||:

		partn="Can't create the filesystem on the partition $devname"
		partn="$partn! Don't use sessions with this boot media."
		IM_ponder_stop
		[ -n "$NOASKUSER" ] ||
			IM_errmsg "$partn"
		run rm -f -- "/dev/disk/by-label/$OEM_LIVE_STORAGE"
	fi
}

check_disk_slice()
{
	[ -z "$rw_slice_created" ] ||
		return 0
	enter "check_disk_slice"

	local tmpf rc=0

	# Use for store output
	tmpf="$(run mktemp -t ||:)"
	if [ ! -f "$tmpf" ]; then
		message "mktemp failed"
		return 1
	fi
	debug "writing the temporary file: '$tmpf'"

	# Prepare for the dialogs
	IM_start_output ponder errmsg
	IM_ponder_start "[ Checking the persistent storage... ]"

	longtermop_start
	message "checking the partition '$1'"
	debug "RUN: e2fsck -p -y -E discard -- $1"
	e2fsck -p -y -E discard -- "$1" >"$tmpf" 2>&1 || rc="$?"
	fdump "$tmpf"
	run rm -f -- "$tmpf"
	longtermop_end
	IM_ponder_stop

	case "$rc" in
	0)	# No errors detected: do nothing
		;;

	1)	# No errors for now: just a message
		message "all file system errors corrected (rc=1)"
		rc=0
		;;

	2)	# This is the kernel requirements
		tmpf="all file system errors corrected (rc=2)"
		IM_fatal "$tmpf, but the system must be rebooted"
		;;

	*)	# Obviously, there are more serious problems
		tmpf="file system errors left uncorrected (rc=$rc)"

		message "$tmpf"

		tmpf="F${tmpf:1}! You have to repair the partition $1"
		tmpf="$tmpf before using this boot media for LiveCD sessions."
		[ -n "$NOASKUSER" ] ||
			IM_errmsg "$tmpf"
		;;
	esac

	leave "check_disk_slice"

	return $rc
}

mount_disk_slice()
{
	enter "mount_disk_slice"

	local dev
	local opts="noatime,nodiratime,barrier=0,delalloc,errors=remount-ro"

	dev="$(readlink-e "$1" 2>/dev/null ||:)"
	[ ! -b "$dev" ] || mountpoint -q -- "$prevdir.rw" || ! check_disk_slice "$dev" ||
		run mount -o "$opts" -- "$dev" "$prevdir.rw"

	leave "mount_disk_slice"
}

liverw_disk_slice()
{
	enter "liverw_disk_slice"

	local live="/dev/disk/by-label/$OEM_LIVE_STORAGE"
	local bad="/dev/disk/by-label/$OEM_BAD_STORAGE"

	case "$method" in
	disk|cdrom)
		[ -L "$live" ] || [ -L "$bad" ] ||
			create_disk_slice
		[ ! -L "$live" ] ||
			mount_disk_slice "$live"
		;;
	esac

	leave "liverw_disk_slice"
}

liverw_disk_slice
