## Authors:
##   Ajrat Makhmutov <rauty@altlinux.org>
##
## Copyright (C) 2024-2026  Basealt LLC
##
## This file is part of alterator-kopidel.
##
## alterator-kopidel is free software: you can redistribute it and/or modify it under the terms
## of the GNU General Public License as published by the Free Software Foundation,
## either version 3 of the License, or (at your option) any later version.
##
## alterator-kopidel is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
## without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
## See the GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along with alterator-kopidel.
## If not, see <https://www.gnu.org/licenses/>.

if [ -n "${__included_kopidel_steps-}" ]; then
	return 0
fi
readonly __included_kopidel_steps=1

. "${KOPIDEL_LIBDIR:-/usr/lib/alterator-kopidel}/variables"
. "$KOPIDEL_LIBDIR/percentage-handlers"
. "$KOPIDEL_LIBDIR/boot-image-methods"
. "$KOPIDEL_LIBDIR/check-incoming-parameters"
. alterator-sh-functions

declare -A STEPS
SUBSTEPS=()

step_error() {
	echo "ERROR: $1" >&2
}

run_step() (
	if [ -n "$in_workdir" ]; then
		mkdir -pv "$in_workdir"
		fast_check_in_workdir &>>"$in_workdir/build.log" || return 1
	elif [ -n "$in_exdrive" ]; then
		mkdir -pv "$EXDRIVE_WORKDIR"
		in_workdir="$EXDRIVE_WORKDIR"
		fast_check_in_exdrive &>>"$in_workdir/build.log" || return 1
	else
		echo "No input workdir or exdrive" >&2
		return 1
	fi

	step="$1"
	echo "step start: $step" >> "$in_workdir/build.log"
	echo "step_progress: 0"

	cd "$in_workdir" || return 1

	"$step" 2>&1 | tee -a "$in_workdir/build.log" |
		{ grep -Ex --line-buffered 'step_progress: [0-9]*' || true; }

	if [ "${PIPESTATUS[0]}" -ne 0 ]; then
		return 1
	fi

	echo "step_progress: 100"
	echo "step end: $step" >> "$in_workdir/build.log"
)

SUBSTEPS+=( prepare_workdir::create_initrd_image )
prepare_workdir::create_initrd_image() (
	start="${1:-0}"
	end="${2:-100}"
	pushd image || return 1

	mkdir -pv .disk
	cp -vf "$KOPIDEL_DATADIR/initrd.mk" .disk/
	cp -vf "$KOPIDEL_DATADIR/bootchain" .disk/

	mkdir -pv boot
	rm -vf boot/initrd*.img

	make-initrd \
		--config=.disk/initrd.mk \
		--bootdir=boot/ \
		--kernel="$KERNEL_VERSION" \
		BOOTCHAIN_PATH=.disk \
		KERNEL_CONFIG="$BOOTDIR/config-$KERNEL_VERSION" \
		-v 2>&1 |
		awk -v start="$start" -v end="$end" '{
			print
			if (++n % 15 == 0) {
				p = start + int(n / 15)
				if (p >= end) p = end - 1
				print "step_progress: " p
				fflush()
			}
		}'

	if [ "${PIPESTATUS[0]}" -ne 0 ]; then
		step_error "make-initrd failed to create the initrd image."
		return 1
	fi

	if [ ! -s "boot/initrd-$KERNEL_VERSION.img" ]; then
		step_error "make-initrd created no boot/initrd-$KERNEL_VERSION.img."
		return 1
	fi
)

prepare_workdir::installed_deps_closure() (
	declare -A seen=()
	queue=("$@")
	while [ "${#queue[@]}" -gt 0 ]; do
		pkg="${queue[0]}"
		queue=("${queue[@]:1}")
		[ -n "${seen[$pkg]:-}" ] && continue
		seen["$pkg"]=1
		while read -r dep; do
			[ -n "$dep" ] && [ -z "${seen[$dep]:-}" ] && queue+=( "$dep" )
		done < <(rpm -q --requires "$pkg" 2>/dev/null |
			sed 's/[[:space:]].*//' |
			grep -vE '^(rpmlib|rtld|config|executable)\(' |
			grep -v '^$' |
			xargs -r rpm -q --whatprovides --qf '%{NAME}\n' 2>/dev/null |
			grep -v 'no package')
	done
	if [ "${#seen[@]}" -gt 0 ]; then
		printf '%s\n' "${!seen[@]}"
	fi
)

prepare_workdir::path_through_real_dirs() (
	if [ -L "$1" ]; then
		echo "$(realpath "$(dirname "$1")")/$(basename "$1")"
	else
		realpath "$1"
	fi
)

SUBSTEPS+=( prepare_workdir::create_squashfs_files_list )
prepare_workdir::create_squashfs_files_list() (
	readonly SQUASHFS_PACKAGES_LOG="${TMPDIR:-/tmp}/squashfs-packages-log.txt"

	cp -fv "$KOPIDEL_DATADIR/squashfs-files-default.txt" squashfs-files.txt
	rm -fv "$SQUASHFS_PACKAGES_LOG"

	mapfile -t squashfs_top_packages < <(
		rpm -qa --qf "%{NAME}\n" |
			grep -E -x -f "$KOPIDEL_DATADIR/squashfs-packages-default.txt")
	mapfile -t squashfs_packages < <(
		prepare_workdir::installed_deps_closure "${squashfs_top_packages[@]}" |
			sort -u)
	if [ "${#squashfs_packages[@]}" -eq 0 ]; then
		echo "WARNING: the dependency closure is empty, using the top-level packages only." \
			>> "$SQUASHFS_PACKAGES_LOG"
		squashfs_packages=( "${squashfs_top_packages[@]}" )
	fi
	echo "The altinst packages: ${#squashfs_top_packages[@]} top-level, ${#squashfs_packages[@]} with the dependencies." \
		>> "$SQUASHFS_PACKAGES_LOG"

	for squashfs_package in "${squashfs_packages[@]}"; do
		if [ -z "$(rpm -q "$squashfs_package" 2>>"$SQUASHFS_PACKAGES_LOG")" ]; then
			continue
		fi

		echo "Add the $squashfs_package to the altinst image" >> "$SQUASHFS_PACKAGES_LOG"
		rpm -ql "$squashfs_package" | while read -r squashfs_file; do
			prepare_workdir::path_through_real_dirs "$squashfs_file" >> squashfs-files.txt
		done
	done

	find "$(realpath -m "$MODULES_DIR/$KERNEL_VERSION")" >> squashfs-files.txt
)

SUBSTEPS+=( prepare_workdir::create_squashfs_altinst )
prepare_workdir::create_squashfs_altinst() (
	start="${1:-0}"
	end="${2:-100}"
	(( rsync_end = start + (end - start) * 2 / 7 ))
	pushd image || return 1

	if [ ! -r "$in_workdir/squashfs-files.txt" ]; then
		step_error 'No readable file squashfs-files.txt in workdir.'
		return 1
	fi

	rm   -rf squashfs-root
	mkdir -v squashfs-root
	rm -vf "$in_workdir/rsync-squashfs-root.log"
	expected_lines="$(rsync -avAXHW --dry-run --exclude="$in_workdir" \
		--files-from="$in_workdir/squashfs-files.txt" \
		/ "squashfs-root/" 2>&1 | wc -l)"
	rsync -avAXHW \
		--exclude="$in_workdir" \
		--files-from="$in_workdir/squashfs-files.txt" \
		/ "squashfs-root/" 2>&1 \
		| expected_percentage_handler "$expected_lines" "$start" "$rsync_end" \
		"$in_workdir/rsync-squashfs-root.log"

	pushd squashfs-root || return 1
		rm -v usr/sbin/init
		if [ -f usr/sbin/install2-init ]; then
			mv -v usr/sbin/install2-init sbin/init
		else
			mv -v usr/libexec/install2/install2-init sbin/init
		fi

		mkdir -pv sys mnt tmp proc
	popd || return 1

	chroot squashfs-root /usr/bin/bash -c "alternatives-validate; alternatives-update"

	rm -fv altinst
	mksquashfs squashfs-root altinst -no-recovery -comp xz -b 262144 -percentage | default_percentage_handler "$rsync_end" "$end"

	if ! test_bool "$in_lazy_cleanup"; then
		rm -rf squashfs-root
	fi
)

STEPS[prepare_workdir]="$(_ "Preparing the working directory")"
prepare_workdir() (
	mkdir -pv image/.disk
	rpm -qi alterator-kopidel > image/.disk/alterator-kopidel.txt

	mkdir -pv image/boot
	rm -vf image/boot/vmlinuz-*
	cp -v "$BOOTDIR/vmlinuz-$KERNEL_VERSION" image/boot/

	mkdir -pv image/Metadata
	cp -vf "$KOPIDEL_DATADIR/autoinstall.scm" image/Metadata/autoinstall.scm

	echo "step_progress: 3"
	prepare_workdir::create_initrd_image 3 20       || return 1
	echo "step_progress: 20"
	prepare_workdir::create_squashfs_files_list     || return 1
	echo "step_progress: 30"
	prepare_workdir::create_squashfs_altinst 30 100 || return 1
	echo "step_progress: 100"
)

readonly TAR_SOME_FILES_DIFFER=1

STEPS[create_copied_fs]="$(_ "Copying the file system")"
create_copied_fs() (
	"$KOPIDEL_LIBEXECDIR/update-ignored-files.sh" "$in_ignored_files" || return 1

	mkdir -pv image/Metadata
	rm -rf copied-fs
	rm -vf image/Metadata/copied-fs.tar*
	rm -vf "$in_workdir/copied-fs-files.log"

	expected_lines="$(rsync -avAXHW --dry-run \
		--exclude="$in_workdir" \
		--exclude="$WORKDIR_NAME" \
		--exclude-from="$IGNORED_FILES" \
		/ "copied-fs/" 2>/dev/null | wc -l)"

	if test_bool "$in_compress_copied"; then
		tar cvf image/Metadata/copied-fs.tar.xz --absolute-names \
			--use-compress-program='xz -T0' \
			--exclude="$in_workdir" \
			--exclude="**/$WORKDIR_NAME" \
			--exclude-from="$IGNORED_FILES" \
			-C / / | expected_percentage_handler "$expected_lines" 0 99 \
			"$in_workdir/copied-fs-files.log"
	else
		tar cvf image/Metadata/copied-fs.tar --absolute-names \
			--exclude="$in_workdir" \
			--exclude="**/$WORKDIR_NAME" \
			--exclude-from="$IGNORED_FILES" \
			-C / / | expected_percentage_handler "$expected_lines" 0 99 \
			"$in_workdir/copied-fs-files.log"
	fi

	tar_status="${PIPESTATUS[0]}"
	if [ "$tar_status" -gt "$TAR_SOME_FILES_DIFFER" ]; then
		step_error "create_copied_fs: tar failed with $tar_status, see above."
		return 1
	fi
)

print_fstab_only_spaces() {
	sed -E -e 's/\t+/ /g' -e 's/ +/ /g' /etc/fstab
}

SUBSTEPS+=( create_disk_partition_info::subvolumes_info_by_uuid )
create_disk_partition_info::subvolumes_info_by_uuid() (
	UUID="${1:?}"
	result=

	result="$(print_fstab_only_spaces | grep "^UUID=$UUID .*[, ]subvol=/" |
		while read -r line; do
			name="$(echo "$line" | sed -e 's@.*subvol=/@@' -e 's@[, ].*@@')"
			mountpoint="$(echo "$line" | cut -d ' ' -f 2)"

			if [[ -z "$name" || -z "$mountpoint" ]]; then
				continue
			fi
			echo -n " (\"$name\" . \"$mountpoint\")"
		done)"

	if [ -n "$result" ]; then
		result="(subvols$result)"
	fi
	echo "$result"
)

SUBSTEPS+=( create_disk_partition_info::lvm_info_by_uuid )
create_disk_partition_info::lvm_info_by_uuid() (
	UUID="${1:?}"
	device= vg_name= lv_name= result=

	device="$(blkid -o device -t UUID="$UUID" 2>/dev/null)"
	if [ -z "$device" ]; then
		echo ""
		return
	fi

	vg_name="$(lvs --noheadings -o vg_name "$device" 2>/dev/null | tr -d ' ')"
	lv_name="$(lvs --noheadings -o lv_name "$device" 2>/dev/null | tr -d ' ')"

	if [ -n "$vg_name" ]; then
		result="(vg . \"$vg_name\")"
	fi
	if [ -n "$lv_name" ]; then
		[ -n "$result" ] && result+=" "
		result+="(lv . \"$lv_name\")"
	fi

	echo "$result"
)

create_disk_partition_info::min_size() (
	uuid="$1"
	max_size="$2"
	fsused="$3"
	shift 3

	min_size="$(du -bsc --exclude-from="$IGNORED_FILES" --exclude="$WORKDIR_NAME" "$@" 2>/dev/null | tail -n 1 | cut -f 1)"
	if [[ -z "$min_size" || "$min_size" == 0 ]]; then
		echo "WARNING: min_size of the partition with uuid $uuid is not calculated with du!" >&2
		(( min_size = max_size / 2 ))
	else
		(( min_size /= BYTES_IN_SECTOR ))
	fi

	if [[ -n "$fsused" && "$min_size" -gt "$(( fsused / BYTES_IN_SECTOR ))" ]]; then
		echo "WARNING: min_size $min_size greater than FSUSED $(( fsused / BYTES_IN_SECTOR )) of the partition with uuid $uuid!" >&2
		(( min_size = fsused / BYTES_IN_SECTOR ))
	fi

	if [ "$max_size" -lt "$min_size" ]; then
		echo "ERROR: max_size $max_size less than min_size $min_size" >&2
		min_size="$max_size"
	fi
	echo "$min_size"
)

create_disk_partition_info::methods_field() (
	type="$1"
	uuid="$2"
	declare -A type_blkid_to_methods=( [part]="plain" [lvm]="lvm" )
	methods="${type_blkid_to_methods["$type"]}"

	lvm_info=""
	if [[ "$type" == "lvm" ]]; then
		lvm_info="$(create_disk_partition_info::lvm_info_by_uuid "$uuid")"
	fi

	if [ -n "$lvm_info" ]; then
		echo "(methods ($methods $lvm_info))"
	else
		echo "(methods $methods)"
	fi
)

create_disk_partition_info::profile_entry() (
	declare -A fsname_blkid_to_evms=( [ext4]="Ext4" [xfs]="XFS" [btrfs]="BtrFS" [vfat]="FAT32" [ntfs]="NTFS" [swap]="SWAPFS" )
	fstype="$(echo "$1" | cut -d ' ' -f 1)"
	size="$(  echo "$1" | cut -d ' ' -f 2)"
	uuid="$(  echo "$1" | cut -d ' ' -f 3)"
	type="$(  echo "$1" | cut -d ' ' -f 4)"
	fsused="$(echo "$1" | cut -d ' ' -f 5)"

	if [[ -z "$fstype" || -z "$size" || -z "$uuid" ]]; then
		return 1
	fi

	if [[ "$fstype" == "LVM2_member" || "$fstype" == "exfat" ]]; then
		return 1
	fi

	if ! [[ "$type" == part || "$type" == lvm ]]; then
		echo "Unknown TYPE: $type" >&2
		return 1
	fi

	evms_fstype="${fsname_blkid_to_evms["$fstype"]}"
	if [ -z "$evms_fstype" ]; then
		echo "Unknown FSTYPE: $fstype" >&2
		return 1
	fi

	mountpoint="$(print_fstab_only_spaces | grep "^UUID=$uuid " | grep -v "[, ]subvol=/" | head -n 1 | cut -d ' ' -f 2)"
	subvolumes="$(create_disk_partition_info::subvolumes_info_by_uuid "$uuid")"
	if [[ -z "$mountpoint" && -z "$subvolumes" ]]; then
		echo "Mountpoint of partition with uuid $uuid not found in the /etc/fstab!" >&2
		return 1
	fi

	paths=()
	if [ -n "$mountpoint" ]; then
		paths+=( "$mountpoint" )
	fi
	if [ -n "$subvolumes" ]; then
		mapfile -t -O "${#paths[@]}" paths < <(print_fstab_only_spaces | grep "^UUID=$uuid .*[, ]subvol=/" | cut -d ' ' -f 2)
	fi

	(( max_size = size / BYTES_IN_SECTOR ))
	if [[ "$fstype" == swap || "$mountpoint" == /boot/efi ]]; then
		min_size="$max_size"
	else
		min_size="$(create_disk_partition_info::min_size "$uuid" "$max_size" "$fsused" "${paths[@]}")"
	fi

	echo "               (\"$mountpoint\" (size $min_size . $max_size ) (fsim . \"$evms_fstype\") $(create_disk_partition_info::methods_field "$type" "$uuid") $subvolumes)"
)

STEPS[create_disk_partition_info]="$(_ "Generating disk partition information")"
create_disk_partition_info() (
	vm_profile=image/Metadata/vm-profile.scm

	mkdir -pv image/.disk image/Metadata
	lsblk --raw -b -o 'FSTYPE,SIZE,UUID,TYPE,FSUSED' > image/.disk/lsblk
	cp -vf /etc/fstab image/.disk/

	rm -vf "$vm_profile"
	cp -v "$KOPIDEL_DATADIR/vm-profile-start.scm" "$vm_profile"

	lsblk -n --raw -b -o 'FSTYPE,SIZE,UUID,TYPE,FSUSED' | sort -k4,4r -k2,2n | while read -r line; do
		create_disk_partition_info::profile_entry "$line" >> "$vm_profile" || true
	done

	echo '               )))' >> "$vm_profile"

	echo "--- lsblk --raw -b ---"
	cat image/.disk/lsblk
	echo "--- blkid ---"
	blkid 2>/dev/null || true
	echo "--- vm-profile.scm ---"
	cat "$vm_profile"
	echo "--- end create_disk_partition_info ---"
)

STEPS[create_install_scripts]="$(_ "Creating installation scripts")"
create_install_scripts() (
	rm -vf image/Metadata/install-scripts.tar
	rm -rf install-scripts

	mkdir -pv \
		install-scripts/initinstall.d \
		install-scripts/postinstall.d \
		install-scripts/preinstall.d

	clear_scripts=( postinstall.d/00-remove-installer-pkgs.sh )
	clear_scripts+=( preinstall.d/00-cp-diskinfo.sh )
	clear_scripts+=( preinstall.d/05-copy-fstab )
	clear_scripts+=( preinstall.d/20-sysconfig.sh )

	install -v -m 755 "$KOPIDEL_DATADIR/90-dmsetup-remove_all.sh" install-scripts/initinstall.d/

	if test_bool "$in_oem_mode"; then
		install -v -m 755 "$KOPIDEL_DATADIR/91-alterator-setup"        install-scripts/postinstall.d/
		install -v -m 755 "$KOPIDEL_DATADIR/92-oem-remove-human-users" install-scripts/postinstall.d/
	fi

	install -v -m 755 "$KOPIDEL_DATADIR/51-kopidel-grub.sh" install-scripts/postinstall.d/
	if test_bool "$in_grub_efi_removable"; then
		sed -i "s/@GRUB_DEVICE_EFI@/efiremovable/" install-scripts/postinstall.d/51-kopidel-grub.sh
	else
		sed -i "s/@GRUB_DEVICE_EFI@/efi/" install-scripts/postinstall.d/51-kopidel-grub.sh
	fi

	install -v -m 755 "$KOPIDEL_DATADIR/00-preinstall.sh" install-scripts/postinstall.d/

	pushd install-scripts || return 1
	echo '#!/bin/sh' | tee "${clear_scripts[@]}" > /dev/null
	chmod 755 "${clear_scripts[@]}"
	popd || return 1

	mkdir -pv image/Metadata
	tar cf image/Metadata/install-scripts.tar -C install-scripts .

	if ! test_bool "$in_lazy_cleanup"; then
		rm -rf install-scripts
	fi
)

razlivochniy_loop_dev() (
	losetup -j razlivochniy.img | head -n 1 | cut -d ':' -f 1
)

razlivochniy_dev() (
	if [ -n "$in_exdrive" ]; then
		echo "$in_exdrive"
	else
		razlivochniy_loop_dev
	fi
)

attach_razlivochniy() (
	dev="$(razlivochniy_dev)"
	if [ -z "$dev" ]; then
		dev="$(losetup --show -Pf razlivochniy.img)"
	fi
	echo "$dev"
)

razlivochniy_part() (
	lsblk --paths -n --raw -b -o 'NAME,PARTLABEL' "$1" | sed -n "s/ $2\$//p" | head -n 1
)

SUBSTEPS+=( create_razlivochniy_mountpoint::mount_razlivochniy )
create_razlivochniy_mountpoint::mount_razlivochniy() (
	dev="$(attach_razlivochniy)"
	root_pdev="$(razlivochniy_part "$dev" ROOT)"
	efi_pdev="$(razlivochniy_part "$dev" ESP)"

	mkdir -pv image
	mount "$root_pdev" image
	if [ -n "$efi_pdev" ]; then
		mkdir -pv image/boot/efi
		mount "$efi_pdev" image/boot/efi
	fi
)

umount_mounted_parts() (
	for part in "$1"*; do
		if findmnt -rn -S "$part" > /dev/null 2>&1; then
			umount "$part" || return 1
		fi
	done
)

run_parted() (
	if ! parted "$@"; then
		step_error "create_razlivochniy_mountpoint: parted $* failed."
		return 1
	fi
)

create_razlivochniy_mountpoint::take_exdrive() (
	if ! umount_mounted_parts "$in_exdrive"; then
		step_error "create_razlivochniy_mountpoint: $in_exdrive is in use and its partitions cannot be unmounted."
		findmnt --raw -n -o SOURCE,TARGET | grep "^$in_exdrive" >&2 || true
		return 1
	fi
)

create_razlivochniy_mountpoint::create_img() (
	rm -fv razlivochniy.img razlivochniy.iso
	if test_bool "$in_compress_copied"; then
		if [ ! -d ready-to-use-image ]; then
			echo "Incorrect using create_razlivochniy_mountpoint step with compressing:" >&2
			echo "razlivochniy.img should be created after creating image compressing copied fs" >&2
			echo "with ready to use installer files. This is necessary to" >&2
			echo "create the minimal size razlivochniy.img." >&2
			return 1
		fi
		payload_mb="$(du -sm ready-to-use-image | cut -f 1)"
	else
		payload_mb="$("$KOPIDEL_LIBEXECDIR/required-space.sh")"
		(( payload_mb = (payload_mb + INSTALLER_FILES_SIZE) / BYTES_IN_MB ))
	fi
	img_size="$(razlivochniy_img_size_mb "$payload_mb")"

	dd if=/dev/zero of=razlivochniy.img bs=1M count="$img_size" &
	dd_pid="$!"
	dd_percentage_handler 0 90 "$dd_pid" razlivochniy.img "$(( img_size * BYTES_IN_MB ))"
)

create_razlivochniy_mountpoint::partition() (
	pdev="$1"
	run_parted "$pdev" --script mklabel gpt || return 1
	start_mb=1
	part_num=1

	if is_img_legacy_bootable; then
		run_parted "$pdev" --script mkpart non-fs "${start_mb}MiB" 2MiB || return 1
		run_parted "$pdev" --script name "$part_num" "BIOS" || return 1
		run_parted "$pdev" --script set "$part_num" bios_grub on || return 1
		(( start_mb += 1 ))
		(( part_num += 1 ))
	fi

	if is_img_efi_bootable; then
		run_parted "$pdev" --script mkpart primary fat32 "${start_mb}MiB" "$(( ESP_SIZE_MB + start_mb ))MiB" || return 1
		run_parted "$pdev" --script name "$part_num" "ESP" || return 1
		run_parted "$pdev" --script set "$part_num" esp on || return 1
		(( start_mb += ESP_SIZE_MB ))
		(( part_num += 1 ))
	fi

	run_parted "$pdev" --script mkpart primary ext4 "${start_mb}MiB" 100% || return 1
	run_parted "$pdev" --script name "$part_num" "ROOT" || return 1
)

create_razlivochniy_mountpoint::format() (
	dev="$1"
	partprobe "$dev"
	if ! udevadm wait --timeout=30 --settle "$dev"; then
		echo "WARNING: udev events for $dev didn't settle in 30 seconds" >&2
	fi

	root_pdev="$(razlivochniy_part "$dev" ROOT)"
	efi_pdev="$(razlivochniy_part "$dev" ESP)"

	echo "efi_pdev=$efi_pdev"
	echo "root_pdev=$root_pdev"

	if [ -z "$root_pdev" ]; then
		step_error "create_razlivochniy_mountpoint: the partitioning of $dev left no ROOT partition."
		return 1
	fi

	if ! mkfs.ext4 -m 0 "$root_pdev"; then
		step_error "create_razlivochniy_mountpoint: mkfs.ext4 failed on $root_pdev."
		return 1
	fi
	if [ -n "$efi_pdev" ]; then
		if ! mkfs.vfat -F32 "$efi_pdev"; then
			step_error "create_razlivochniy_mountpoint: mkfs.vfat failed on $efi_pdev."
			return 1
		fi
	fi
)

STEPS[create_razlivochniy_mountpoint]="$(_ "Creating partitions and mount points")"
create_razlivochniy_mountpoint() (
	umount_razlivochniy || true
	rm -rf image
	mkdir -pv image

	if [ -n "$in_exdrive" ]; then
		create_razlivochniy_mountpoint::take_exdrive || return 1
		create_razlivochniy_mountpoint::partition "$in_exdrive" || return 1
	else
		create_razlivochniy_mountpoint::create_img || return 1
		create_razlivochniy_mountpoint::partition razlivochniy.img || return 1
	fi

	create_razlivochniy_mountpoint::format "$(attach_razlivochniy)" || return 1
	create_razlivochniy_mountpoint::mount_razlivochniy || return 1
)

STEPS[save_ready_to_use_image]="$(_ "Moving ready-to-use files into an image")"
save_ready_to_use_image() (
	if ! [[ -f image/altinst &&
		-f image/Metadata/copied-fs.tar.xz &&
		-f image/Metadata/vm-profile.scm ]]; then
		step_error "save_ready_to_use_image: not ready to use image."
		return 1
	fi
	rm -rf ready-to-use-image
	mv image ready-to-use-image
)

STEPS[use_ready_to_use_image]="$(_ "Filling the image with the ready-to-use files")"
use_ready_to_use_image() (
	if [ ! -d ready-to-use-image ]; then
		step_error "use_ready_to_use_image: no ready-to-use-image dir."
		return 1
	fi
	if ! cp -RT ready-to-use-image image; then
		step_error "use_ready_to_use_image: failed to fill the image."
		return 1
	fi
	if ! test_bool "$in_lazy_cleanup"; then
		rm -rf ready-to-use-image
	fi
)

STEPS[install_grub]="$(_ "Installing GRUB")"
install_grub() (
	dev="$(razlivochniy_dev)"
	root_pdev="$(razlivochniy_part "$dev" ROOT)"
	root_pdev_uuid="$(blkid -s UUID -o value "$root_pdev")"

	modules="linux normal iso9660 f2fs ext2 fat exfat ntfs ntfscomp part_gpt search font configfile gfxterm"
	rescue_modules="tar ls minicmd reboot halt cat"
	look_args=( --locales="" --fonts="unicode" )
	efi_dir_args=( --boot-directory="image/boot" --efi-directory="image/boot/efi" --removable )
	efi_module_args=( --install-modules="$modules $rescue_modules" --modules="$modules" )

	if is_img_legacy_bootable; then
		if ! grub-install \
			--target=i386-pc \
			--boot-directory="image/boot" \
			--install-modules="$modules biosdisk $rescue_modules" \
			--modules="$modules biosdisk" \
			"${look_args[@]}" \
			"$dev"; then
			step_error "install_grub: grub-install failed for legacy BIOS."
			return 1
		fi
	fi

	if is_img_efi_bootable; then
		if command -v grub-efi-install > /dev/null 2>&1; then
			if ! grub-efi-install "${efi_dir_args[@]}" -- "${efi_module_args[@]}" "${look_args[@]}"; then
				step_error "install_grub: grub-efi-install failed."
				return 1
			fi
		else
			if ! grub-install \
				--target="$(grub_efi_target)" \
				"${efi_dir_args[@]}" \
				"${efi_module_args[@]}" \
				"${look_args[@]}" \
				"$dev"; then
				step_error "install_grub: grub-install failed for EFI."
				return 1
			fi
		fi
	fi

	pushd image || return 1

	if ! cp -f "$KOPIDEL_DATADIR/grub.cfg" boot/grub/grub.cfg; then
		step_error "install_grub: failed to put the grub.cfg into the image."
		return 1
	fi

	sed -i "s/@KFLAVOUR@/$KERNEL_VERSION/g" boot/grub/grub.cfg
	sed -i "s/@UUID@/$root_pdev_uuid/g" boot/grub/grub.cfg

	menuentry_install="$(_ 'Install a "razlivochniy obraz".')"
	sed -i "s/@MENUENTRY_INSTALL@/$menuentry_install/g" boot/grub/grub.cfg

	popd
)

STEPS[umount_razlivochniy]="$(_ "Unmounting an image")"
umount_razlivochniy() (
	sync
	if [ -n "$in_exdrive" ]; then
		umount_mounted_parts "$in_exdrive"
	else
		if [ ! -f razlivochniy.img ]; then
			echo "No razlivochniy.img for umount" >&2
			return 1
		fi
		loop_dev="$(razlivochniy_loop_dev)"
		if [ -e "$loop_dev" ]; then
			umount_mounted_parts "$loop_dev" || return 1
			losetup -d "$loop_dev"
		fi
	fi
)

build_plan() (
	if [ -n "$in_workdir" ] && test_bool "$in_compress_copied"; then
		printf '%s\n' \
			'prepare_workdir                1' \
			'create_copied_fs               25' \
			'create_disk_partition_info     57' \
			'create_install_scripts         58' \
			'save_ready_to_use_image        59' \
			'create_razlivochniy_mountpoint 60' \
			'use_ready_to_use_image         80' \
			'install_grub                   90' \
			'umount_razlivochniy            99'
	else
		printf '%s\n' \
			'create_razlivochniy_mountpoint 1' \
			'prepare_workdir                20' \
			'create_copied_fs               40' \
			'create_disk_partition_info     80' \
			'create_install_scripts         81' \
			'install_grub                   82' \
			'umount_razlivochniy            95'
	fi
)
