#!/usr/bin/env bash
#
# Copyright 2023 RnD Center "ELVEES", JSC
#
# SPDX-License-Identifier: GPLv3
#

# https://en.wikipedia.org/wiki/EFI_system_partition#Overview
declare -r ESP_GUID="C12A7328-F81F-11D2-BA4B-00A0C93EC93B"

help() {
    echo 'tar2dev-uefi - A tool to create an UEFI bootable system on block device'
    echo
    echo 'It creates GUID Partition Table (GPT) on block device and two specific partitions.'
    echo 'The first is EFI System Partition (ESP), the second one for the rootfs.'
    echo
    echo 'Copies the specified EFI directory which may have the OS loader or the UEFI fallback'
    echo 'files (bootaa64.efi for ARM64) to the ESP, unpacks the rootfs tarball to the second'
    echo 'partition.'
    echo
    echo 'Specifically for GRUB2 this tool creates the EFI/BOOT/grub.cfg file so GRUB2 can find the'
    echo 'right partition from which it can load the kernel by searching for'
    echo '/boot/<PARTUUID of rootfs>.findme file which will be created using this tool. This file'
    echo 'is created by tar2dev-uefi and needed to find the right root from GRUB2 perspective'
    echo 'e.g. (hd0,gpt2).'
    echo
    echo 'Usage: tar2dev-uefi [option] <rootfs-tarball> <EFI-directory-path> <device>'
    echo
    echo 'Options:'
    echo '  -h   Print this help'
    echo '  -s   The size of ESP in MiB (300 if not set)'
    echo
    echo 'Example 0:'
    echo '  tar2dev-uefi rootfs.tar.gz efi-part/EFI /dev/sdb'
    echo
    echo 'Example 1:'
    echo '  tar2dev-uefi -s 600 rootfs.tar.gz efi-part/EFI /dev/sdb'
    exit 1
}

set -e
source tar2dev

while getopts "hs:" opt; do
    case ${opt} in
        h) help;;
        s)
            [[ $OPTARG =~ ^[0-9]+$ ]] || \
              error "-s option requires a integer type argument"
            ARG_ESP_SIZE=$OPTARG ;;
        *) help;;
    esac
done
shift $((OPTIND-1))

[[ "$EUID" != "0" ]] && error "This script must be run as root"
[[ -z "$1" || -z "$2" || -z "$3" ]] && help

TAR=$1
EFI=$2
DEV=$3
ESP_SIZE=${ARG_ESP_SIZE:-300}

iswholedisk "$DEV" || error "'$DEV' is not a disk"
[[ -w "$DEV" ]] || error "'$DEV' is not writable"
[[ -e "$DEV" ]] || error "file $DEV dosn't exists"
[[ -d "$EFI" ]] || error "$EFI dosn't exists or isn't a directory"
[[ -e "$TAR" || -r "$TAR" ]] || error "file '$TAR' dosn't exists or isn't readable"

# Conditions are satisfied, ready to do the work.

unmountall "$DEV"

ESP_DIR=$(mktemp -d)
ROOTFS_DIR=$(mktemp -d)
trap 'echo "Clean up..."; unmountall "$DEV"; rm -rf "$ROOTFS_DIR" "$ESP_DIR"' EXIT

# Create UEFI bootable system
echo "Creating GPT ..."
hideifok "echo 'label: gpt' | sfdisk $DEV"
ESP_DEVICE="$DEV"1
ROOTFS_DEVICE="$DEV"2
echo "Creating ESP on $ESP_DEVICE and rootfs on $ROOTFS_DEVICE ..."
hideifok "echo -e \",${ESP_SIZE}M,$ESP_GUID,*\n,,,\" | sfdisk -f $DEV"
echo "Formating both partitions ..."
hideifok "mkfs.vfat $ESP_DEVICE"
hideifok "mkfs.ext4 -F -L root $ROOTFS_DEVICE"

# Copy rootfs to the device and generate `findme` file to be searched by GRUB2
ROOTFS_PART_UUID=$(sfdisk --part-uuid "$DEV" 2)
mount "$ROOTFS_DEVICE" "$ROOTFS_DIR"
tar -C "$ROOTFS_DIR" -xf "$TAR"
touch "$ROOTFS_DIR/boot/$ROOTFS_PART_UUID.findme"

# Copy efi-part/EFI directory and generate grub.cfg
DTB_PATH=$(find "$ROOTFS_DIR" -name 'mcom03*.dtb' | head --lines 1 | xargs dirname)
mount "$ESP_DEVICE" "$ESP_DIR"
cp -r "$EFI" "$ESP_DIR"
cp -r "$DTB_PATH" "$ESP_DIR/dtb" # U-Boot expects DTB in this directory

if [[ -f "$ROOTFS_DIR/boot/Image" ]]; then
    IMAGE_NAME=Image
elif [[ -f "$ROOTFS_DIR/boot/vmlinuz" ]]; then
    IMAGE_NAME=vmlinuz
else
    error "Couldn't find the proper kernel image name in /boot directory of" \
    "the rootfs, expected /boot/Image or /boot/vmlinuz files"
fi

cat > "$ESP_DIR"/EFI/BOOT/grub.cfg << EOF
set default="0"
set timeout="2"
menuentry 'Default' {
  #set debug=linux
  #set efi_linux_debug="efi=debug"
  search --file --set=root /boot/$ROOTFS_PART_UUID.findme
  if [ "\$?" = 0 ]; then
    echo "found rootfs on \$root"
    linux /boot/$IMAGE_NAME root=PARTUUID=${ROOTFS_PART_UUID,,*} console=ttyS0,115200n8 \
    earlycon roottype=ext4 rw rootwait \$efi_linux_debug
    if [ -f /boot/initrd.img ]; then
      initrd /boot/initrd.img
    fi
  fi
}
EOF
