#!/bin/sh

. /usr/share/alterator/build/backend3.sh

unset \
	LANG \
	LANGUAGE \
	LC_CTYPE \
	LC_NUMERIC \
	LC_TIME \
	LC_COLLATE \
	LC_MONETARY \
	LC_MESSAGES \
	LC_PAPER \
	LC_NAME \
	LC_ADDRESS \
	LC_TELEPHONE \
	LC_MEASUREMENT \
	LC_IDENTIFICATION \
	LC_ALL \
	||:

PROG="alterator-lilo"
destdir="/mnt/destination"
lilo_template="/usr/share/install3/data/lilo.conf.template"
lilo_conf="/etc/lilo.conf"
lilo_conf_new="/tmp/lilo.conf"
blkid_args="-c /dev/null -w /dev/null"

workdir=
sections=
conf=
fdisk_out=

raidtypes=
raidboot=

##
## Common functions
##

debug() {
	printf %s\\n "$PROG: $*" >&2
}

error() {
	local out="$(printf %s\\n "$*" |simple_quote)"
	printf '(error "%s")\n' "$out"
}

empty() {
	printf '()\n'
}

device_exists() {
	[ ! -e "$1" ] || return 0
	error "$1: device not found"
	return 1
}

update_cmd_params() {
	local vga= args= cnf="$1"
	debug "update_cmd_params(): start"

	vga="$(sed -ne 's|^\(.* \)\?vga=\([^ ]\+\)\( .*\)\?$|\2|p' /proc/cmdline)"
	[ -z "$vga" ] || sed -i -e "s,^vga=.*,vga=\"$vga\"," "$cnf"

	for p in $(cat /proc/cmdline); do
		case "$p" in
			noapic|nolapic|acpi=*|pci=*|mem=*|irqpoll)
			args="$args $p" ;;
		esac
	done
	[ -z "$args" ] || sed -i -e "s,^append=\"\?\([^\"]*\)\"\?,append=\"$args \1\"," "$cnf"
}

update_rootdev() {
	local cnf="$1" dev mpoint dummy
	while read dev mpoint dummy; do
		[ "$mpoint" = "/" ] || continue
		sed -i -e "s,^root=.*,root=\"$dev\"," "$cnf"
		break
	done < /etc/fstab
}

convert_bootdev() {
	local devname="$1" cnf="$2" bootdev= boot_uuid=
	debug "convert_bootdev(): start"

	bootdev="$devname"
	if [ "$bootdev" = "${bootdev#/dev/md}" ]; then
		if boot_uuid="UUID=$(blkid $blkid_args -o value -s UUID "$devname" 2>/dev/null)"; then
			bootdev="$boot_uuid"
		fi
	fi

	debug "convert_bootdev(): device='$devname', bootdev='$bootdev'"
	sed -i -e "s,^boot=.*,boot=\"$bootdev\"," "$cnf"
}

partitions_list() {
	grep '^/dev/' "$fdisk_out"
}

set_bootable() {
	local bootdev="$1" disk= partition= i=0

	while read line; do
		case "$line" in
			Disk*)
				i=0
				disk="$(printf %s\\n "$line" |sed -n -e 's/^Disk \([^:]\+\):.*/\1/p')"
				;;
			/dev/*)
				i="$(($i+1))"
				partition="${line%% *}"
				if [ "$bootdev" = "$partition" -a -n "$disk" ]; then
					debug "set_bootable(): set bootable flag on '$partition' partittion (dev='$disk', num='$i')"
					sfdisk "$disk" -A $i
					break
				fi
				;;
		esac
	done < "$fdisk_out"
}

set_bootable_raid() {
	[ -n "$raidboot" ] || return 0
	local d

	md-list "${raidboot#/dev/}" |
	while read d; do
		set_bootable "$d"
	done
}

bootable_flag() {
	local bootdev="$1"

	# Check raid
	[ -z "$raidtypes" ] || set_bootable_raid

	# Don't set bootable flag if some partitions already marked as bootable.
	partitions_list |grep "^$bootdev" |cut -d\  -f1,2 |grep -qs '[[:space:]]\*$' &&
		return 0

	if grep -qs "^Disk $bootdev:" "$fdisk_out"; then
		[ "$(fdisk -l "$bootdev" 2>/dev/null |grep '^/dev' |wc -l)" -gt 0 ] ||
			return 0

		sfdisk "$bootdev" -A 1
	else
		set_bootable "$bootdev"
	fi
}

write_liloconf() {
	local bootdev="$(sed -n -r -e 's,^boot=\"?([^\"]+)\"?,\1,p' "$lilo_conf_new")"
	if [ -z "$bootdev" ]; then
		error "'boot=' not found"
		return 1
	fi

	[ "$bootdev" != "${bootdev#/}" ] || bootdev="/dev/$bootdev"
	device_exists "$bootdev" || return 1

	# Convert boot device to UUID
	convert_bootdev "$bootdev" "$lilo_conf_new" || return 1

	# Test lilo.conf
	if ! out="$(lilo -t -H -C "$lilo_conf_new" </dev/null 2>&1 >/dev/null)"; then
		error "Lilo test: $out"
		return 1
	fi
	debug "write_liloconf(): lilo test passed"

	# Set bootable flag
	bootable_flag "$bootdev"

	# Write lilo.conf
	[ ! -f "$lilo_conf" ] || mv -f -- "$lilo_conf" "$lilo_conf".save
	mv -f -- "$lilo_conf_new" "$lilo_conf"

	# Real install bootloader
	lilo -H </dev/null >/dev/null 2>&1
}

exit_handler() {
	local rc=$?
	trap - EXIT
	rm -rf -- "$workdir"
	exit $rc
}

##
## Functions to work with lilo.conf
##

show_section() {
	local label="$1" curtype= line= section= lbl= found=
	while read -r line; do
		[ -n "$line" ] || continue
		case "${line%%=*}" in
			image|other) [ -z "$found" ] && section= || break ;;
			label)
				lbl="${line#label=}"
				lbl="${lbl#\"}"
				lbl="${lbl%\"}"
				[ "$lbl" != "$label" ] || found=1
				;;
		esac
		section="${section:+$section
}$line"
	done < "$conf"
	[ -z "$found" ] || printf %s\\n "$section"
}

show_section_type() {
	grep -qs '^other=' "$1" &&
		printf other ||
		printf image
}

show_globals() {
	local line=
	while read -r line; do
		[ -n "$line" ] || continue
		case "${line%%=*}" in
			image|other) break ;;
		esac
		printf %s\\n "$line"
	done < "$conf"
}

show_default() {
	local def_label="$(sed -r -n -e '/[[:space:]]#/d' -e 's,.*default=\"?([^\"]+)\"?,\1,p' "$globals" |tail -1)"
	[ -n "$def_label" ] ||
		def_label="$(head -1 "$order" |cut -f2)"
	printf %s "$def_label"
}

show_sections() {
	local line= type= label=
	while read -r line; do
		[ -n "$line" ] || continue

		case "${line%%=*}" in
			image|other) type="${line%%=*}"
				;;
			label)
				label="${line#label=}"
				label="${label#\"}"
				label="${label%\"}"

				[ -z "$type" ] ||
					printf '%s\t%s\n' "$type" "$label"

				label= type=
				;;
		esac
	done < "$conf"
}

replace_global() {
	sed -i -e "/^$1/d"  "$globals"
	[ -z "$2" ] || printf %s\\n "$2" >> "$globals"
}

write_globals() {
	case "$in_target" in
		boot)
			if [ -n "$raidboot" ]; then
				[ "$raidboot" = "$in_devname" ] &&
					replace_global "raid-extra-boot" "raid-extra-boot=\"mbr-only\"" ||
					replace_global "raid-extra-boot"
			fi
			replace_global "boot" "boot=\"$in_devname\""
			;;
		default)
			replace_global "default" "default=\"$in_default\""
			;;
		globals)
			if [ "$in_method" != "insecure" ]; then
				replace_global "password" "password=\"$in_password1\""
				replace_global "\(restricted\|mandatory\)" "$in_method"
			else
				replace_global "\(restricted\|mandatory\|password\)"
			fi
			in_extraboot="$(printf %s "$in_extraboot" |tr -d '[:space:]')"
			[ -n "$in_extraboot" ] &&
				replace_global "raid-extra-boot" "raid-extra-boot=\"$in_extraboot\"" ||
				replace_global "raid-extra-boot"

			replace_global "boot" "boot=\"$in_boot\""
			replace_global "map" "map=\"$in_map\""
			replace_global "timeout" "timeout=\"$in_timeout\""
			replace_global "install" "install=\"$in_install\""
			replace_global "append" "append=\"$in_append\""
			;;
	esac

}

write_image_section() {
	{
		printf 'image="%s"\n' "$in_image"
		printf 'label="%s"\n' "$in_label"
		printf 'root="%s"\n' "$in_root"
		[ -z "$in_initrd" ] || printf 'initrd="%s"\n' "$in_initrd"
		[ -z "$in_append" ] || printf 'addappend="%s"\n' "$in_append"
		[ -z "$in_alias" ]  || printf 'alias="%s"\n' "$in_alias"
	} > "$sections/$in_label"
}

write_other_section() {
	{
		printf 'other="%s"\n' "$in_other"
		printf 'label="%s"\n' "$in_label"
		[ -z "$in_alias" ]   || printf 'alias="%s"\n' "$in_alias"
		[ -z "$in_table" ]   || printf 'table="%s"\n' "$in_table"
		[ -z "$in_boot_as" ] || printf 'boot-as="%s"\n' "$in_boot_as"
	} > "$sections/$in_label"
}

create_section() {
	local section_type="${in_type:-image}"
	grep -qs "^[^[:space:]]\+[[:space:]]$in_label\$" "$order" ||
		printf '%s\t%s\n' "$section_type" "$in_label" >> "$order"
	case "$section_type" in
		image) write_image_section ;;
		other) write_other_section ;;
	esac
}

update_section() {
	if [ -n "$in_oldlabel" -a "$in_oldlabel" != "$in_label" ]; then
		sed -i -e "s#^\(.*[[:space:]]\)$in_oldlabel\$#\1$in_label#g" "$order"
		rm -f -- "$sections/$in_oldlabel"
	fi
	create_section
}

remove_section() {
	[ -n "$in_label" ] || return 0
	sed -i -e "\#^.*[[:space:]]$in_label\$#d" "$order"
	rm -f -- "$sections/$in_label"
}

format_section() {
	local f="$1" line= first=1 format=
	while read -r line; do
		[ -n "$first" ] && first= && format='%s\n' || format='\t%s\n'
		printf "$format" "$line"
	done < "$f"
}

normalize_liloconf() {
	local keyword dummy orig_conf=
	debug "lilo_conf='$lilo_conf', conf='$conf'"

	[ ! -s "$lilo_conf" ] || orig_conf="$lilo_conf"

	sed -e 's#[[:space:]]\+# #g' -e 's#^[[:space:]]*\([^=]\+=\)\(.*\)#\1 \2#' "$lilo_template" $orig_conf |
	while read -r keyword dummy; do
		[ -n "$keyword" ] || continue
		keyword="$(printf %s\\n "$keyword" |tr '[:upper:]' '[:lower:]')"
		printf %s\\n "$keyword$dummy"
	done > "$conf"
}

parse_globals() {
	show_globals > "$globals"
}

parse_sections() {
	local label= sect_type=
	show_sections >"$order"
	[ ! -d "$sections" ] || rm -rf -- "$sections"
	mkdir -p -- "$sections"
	while read sect_type label; do
		show_section "$label" > "$sections/$label"
	done < "$order"

}

parse_liloconf() {
	parse_globals
	parse_sections
}

reset_changes() {
	case "$in_part" in
		sections)
			local def="$(show_default)"
			replace_global "default" "default=\"$def\""
			parse_sections
			;;
		globals) parse_globals
			;;
	esac
}

##
## Raid brain
##

detect_raidroot() {
	local raid_list= bootdev= md= mddev= uuid= boot_uuid=

	raidtypes="$(sed -n -e 's/^md.* \<\(raid[[:alnum:]]\+\)\>.*/\1/p' /proc/mdstat|sort -u)"
	[ -n "$raidtypes" ] ||
		return 0

	raid_list="$(sed -n -e 's,^\(md[^[:space:]]\+\) :.*,\1,p' /proc/mdstat |sort)"
	[ -n "$raid_list" ] ||
		return 0

	# Try to find /boot
	bootdev="$(sed -n -e 's,^\(/dev/[^[:space:]]\+\) /boot .*,\1,p' /proc/mounts)"

	# Try to find / if no /boot found
	[ -n "$bootdev" ] ||
		bootdev="$(sed -n -e 's,^\(/dev/[^[:space:]]\+\) / .*,\1,p' /proc/mounts |grep -v '^/dev/loop')"

	[ -n "$bootdev" ] ||
		return 0

	boot_uuid="$(blkid $blkid_args -o value -s UUID "$bootdev" 2>/dev/null)" ||
		return 0

	for md in $raid_list; do
		[ -e "/dev/$md" ] &&
			uuid="$(blkid $blkid_args -o value -s UUID "/dev/$md" 2>/dev/null)" &&
			[ "$boot_uuid" = "$uuid" ] ||
			continue

		mddev="$md"
	done

	if [ -n "$mddev" ]; then
		if grep -qs "^$mddev :.* \<raid1\>" /proc/mdstat; then
			replace_global "boot" "boot=\"$mddev\""
			replace_global "raid-extra-boot" "raid-extra-boot=\"mbr-only\""
		fi
		raidboot="$mddev"
	fi
}

##
## List functions
##

show_sections_list() {
	local def= def_label= label= sect_type= show_type="${1:-}"
	def_label="$(show_default)"

	while read sect_type label; do
		[ -n "$label" -a -f "$sections/$label" ] || continue

		if [ -n "$show_type" ]; then
			[ "$show_type" = "$sect_type" ] || continue
		fi

		label="$(sed -r -n  -e '/[[:space:]]#/d' -e 's#.*label="?([^"]+)"?#\1#p' "$sections/$label")"
		[ -n "$label" ] || continue

		[ "$label" != "$def_label" ] && def="#f" || def="#t"

		printf '("%s" default %s)\n' "$label" "$def"
	done < "$order"
}

getinfo() {
	local dev= cur_dev= cur= id=
	debug "getinfo()"

	cur_dev="$(sed -n -r -e 's,^boot=\"?([^\"]+)\"?,\1,p' "$globals")" || return 0
	cur_dev="/dev/${cur_dev#/dev/}"

	# Show disks
	sed -n -e 's/^Disk \([^:]\+\): \([^,]\+\),.*/\1 \2/p' "$fdisk_out" |
	while read dev size; do
		[ "$cur_dev" != "$dev" ] && cur="#f" || cur="#t"
		printf '("%s" size "%s" disk #t cur %s)\n' "${dev#/dev/}" "$size" "$cur"
	done

	# Show partitions
	partitions_list |cut -d\  -f1,6 |
	while read dev id; do
		case "$id" in
			5|82) continue ;;
		esac
		[ "$cur_dev" != "$dev" ] && cur="#f" || cur="#t"
		size="$(fdisk -l "$dev" 2>/dev/null |sed -ne 's/^Disk[[:space:]]\+[^:]\+:[[:space:]]\+\([^,]\+\),.*/\1/p')"
		printf '("%s" size "%s" disk #f cur %s)\n' "${dev#/dev/}" "$size" "$cur"
	done
}

##
## Functions to work with other systems
##

increment() {
	local var="$1" cur
	cur="$(eval echo \$$var)"
	[ -n "$cur" ] || cur=0
	eval "$var=\$((\$$var + 1))"
}

find_others() {
	local conf="$1" dev boot start end blocks id system label
	local dev mpoint dummy exclude=" "
	debug "find_others(): start, lilo.conf='$conf'"

	# Remember self partitions
	while read dev mpoint dummy; do
		[ -z "${mpoint%%$destdir}" ] || continue
		exclude="$exclude$dev "
	done < /proc/mounts
	debug "exclude='$exclude'"

	partitions_list |
	while read dev boot start end blocks id system; do
		# We need only bootable partitions
		[ "$boot" = "*" ] || continue

		# Exclude self mountpoints
		[ -n "${exclude##* $dev *}" ] || continue

		case "$id" in
			1|4|6|7|b|c|e|f|11|14|16|17|1b|1c|1e|86|87|c1|c4|c6|ef)
				label="Windows${win_n:+_$win_n}"
				increment win_n
				;;
			81|83|85|88|8e|f0|fd)
				label="Other${other_n:+_$other_n}"
				increment other_n
				;;
			*)
				continue
				;;
		esac
		bootsector "$dev" >&2 || continue
		debug "find_others(): label='$label' dev='$dev'"
		printf 'other="%s"\nlabel="%s"\n' "$dev" "$label" >>"$conf"
	done
}

##
## Main
##

trap exit_handler HUP PIPE INT QUIT TERM EXIT
workdir="$(mktemp -dt "${0##*/}.XXXXXXXXXX")" || exit 1
globals="$workdir/globals"
sections="$workdir/sections"
order="$workdir/label.order"
conf="$workdir/lilo.conf"
fdisk_out="$workdir/fdisk.out"

fdisk -l 2>/dev/null |
	grep '^\(/dev\|Disk \)' |
	grep -v '/dev/dm-' |
	sed -r \
	    -e 's#[[:space:]]+# #g' \
	    -e 's#^(/dev/[^[:space:]]+) ([[:digit:]])#\1 x \2#' > "$fdisk_out"

normalize_liloconf
update_rootdev    "$conf"
find_others       "$conf"
parse_liloconf
detect_raidroot
update_cmd_params "$globals"

on_message() {
        case "$in_action" in
		constraints)
			if [ "$in_target" != "globals" ]; then
				empty
				return
			fi
			cat <<-EOF
			(
			   method (exclude ("insecure" password1) exclude ("insecure" password2))
			   password1 (required #t equal password2)
			)
			EOF
			;;
		list)
			printf "("
			[ "$in_part" = "sections" ] &&
				show_sections_list $in_type ||
				getinfo
			printf ")\n"
			;;
		read)
			if [ "$in__objects" = "/" -a -z "$in_section" ]; then
				empty
				return
			fi

			[ "$in__objects" != "globals" ] &&
				section_path="$sections/$in_section" ||
				section_path="$globals"

			[ ! -f "$section_path" ] && empty && return 0

			printf "("
			sed -r -n -e '/^[[:space:]]*#/d' -e 's|^[[:space:]]*([^=]+)$|\1 #t|p' -e 's|^([^=]+)="?([^"]*)"?|\1 "\2"|p' "$section_path"
			printf ")\n"
			;;
		reset)
			reset_changes
			empty
			;;
		new-section)
			create_section
			empty
			;;
		update-section)
			update_section
			empty
			;;
		remove-section)
			remove_section
			empty
			;;
		write)
			write_globals || return 0
			empty
			;;
		commit)
			install -m600 "$globals" "$lilo_conf_new"
			while read -r t s; do
				[ -f "$sections/$s" ] || continue
				printf \\n >> "$lilo_conf_new"
				format_section "$sections/$s" >> "$lilo_conf_new"
			done < "$order"
			write_liloconf || return 0
			empty
			;;
		*)
			printf '#f\n'
			;;
        esac
}

message_loop
