#!/bin/sh

_SCRIPT_NAME=efi-boot-update
_SCRIPT_VERSION=0.1

. /etc/rc.d/init.d/functions

usage () {

    echo "Usage: $0 OPTIONS"
    echo "Update EFI boot loaders"
    echo
    echo "  --version      Show version number"
    echo "  --help, -h     This help message"
    echo "  --mount, -m    Try to mount /boot/efi first"
    echo "  --verbose, -v  Verbose output"
    echo "  --force        Force file updates"
    echo "  --auto         Automatic run from packages %post"
}

msg () {
    echo -E "efi-boot-update: $*" >&2
}

verbose () {
    if is_yes "$VERBOSE" ; then 
        echo -E "efi-boot-update: $*" >&2
    fi
}

verbose_cmd () {
    if is_yes "$VERBOSE" ; then
        echo -E "+$*" >&2
    fi
    "$@"
}


list_remove () {

    for item in $1 ; do
        if [ "$item" = "$2" ] ; then
            continue
        fi
        echo -nE "$item "
    done
}

update_file () {
    local cmd
    local src
    local dest
    while [ -n "$1" ] ; do
        case $1 in 
            --missingok)
                shift
                [ -e "$1" ] || return 0
                ;;
            --*)
                msg "update_file: ignoring unknown option: $1"
                shift
                ;;
            *)
                break
                ;;
        esac
    done
    src="$1"; shift
    dst="$1"; shift
    if [ -n "$*" ] ; then
        msg "update_file: unexpected arguments: $*"
        return 1
    fi
    if [ -z "$src" ] ; then
        msg "update_file: no source file"
        return 1
    fi
    if [ -z "$dst" ] ; then
        dst=`basename "$src"`
    fi
    if [ "${dst#/}" = "${dst}" ] ; then
        # relative path
        dst="$DESTDIR/$dst"
    fi
    if is_yes "$FORCE_UPDATES" ; then
        is_yes "$VERBOSE" && echo +cp --force --preserve=timestamps "$src" "$dst"
        cp --force --preserve=timestamps "$src" "$dst"
    else
        is_yes "$VERBOSE" && echo +cp --update --preserve=timestamps "$src" "$dst"
        cp --update --preserve=timestamps "$src" "$dst"
    fi
}

get_efibootmgr_opts() {
    local efi_disk
    local efi_partnum
    efi_disk=$(mount | awk '$3=="/boot/efi" {print $1}' 2>/dev/null)
    EFIBOOTMGR_OPTS="--gpt"
    if [ -n "$efi_disk" ] ; then
        efi_partnum="$(echo $efi_disk|sed -e's;^.*[^0-9]\([0-9]\+\)$;\1;')"
        efi_disk="$(echo $efi_disk|sed -e's;^\(.*\)[0-9]\+$;\1;')"
        if [ -b "$efi_disk" -a -n "$efi_partnum" ] ; then 
            EFIBOOTMGR_OPTS="$EFIBOOTMGR_OPTS --disk $efi_disk"
            EFIBOOTMGR_OPTS="$EFIBOOTMGR_OPTS --part $efi_partnum"
        fi
    fi
    echo -n $EFIBOOTMGR_OPTS
}

find_bootmgr_entry () {

    $EFIBOOTMGR | awk -v find="$1" '
/^Boot[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\*?/ { 
                                sub(/^Boot/,""); 
                                sub(/\*/,""); 
                                num=$1; 
                                $1=""; 
                                gsub(/^[ \t]+|[ \t]+$/,""); 
                                if ($0 == find) print num
                            }'
}
        
remove_bootmgr_entry () {
    local bootnum
    bootnum=$(find_bootmgr_entry "$1")
    [ -n "$bootnum" ] || return 0
    verbose_cmd $EFIBOOTMGR $EFIBOOTMGR_OPTS --quiet --delete-bootnum -b "$bootnum"
    echo -n "$bootnum"
}

add_bootmgr_entry () {
    local label="$1"
    local binary="$2"
    local args="$3"
    local bootnum
    bootnum=$(find_bootmgr_entry "$label")

    if [ "${binary#/}" = "${binary}" ] ; then
        # relative path
        binary="$DESTDIR/$binary"
    fi
    binary="${binary#/boot/efi}"
    binary="$(echo -nE "$binary"|sed -e's;/;\\;g')"

    if [ -n "$bootnum" ] ; then
       # efibootmgr doesn't seem to update the arguments
       # we need to remove old entry and create a new one 
       verbose_cmd $EFIBOOTMGR $EFIBOOTMGR_OPTS --quiet --delete-bootnum -b "$bootnum"
    fi
    verbose_cmd $EFIBOOTMGR $EFIBOOTMGR_OPTS --create \
                        --quiet --label "$label" --loader "$binary" -u "$args"
    bootnum="$(find_bootmgr_entry "$label")"
    echo -n "$bootnum"
}

safe_string () {
    echo -n "$*" | tr -c '[a-zA-Z0-9_]' '_'
}

ALLOW_AUTO="no"
FORCE_UPDATES="no"
MOUNT_EFI_PARTITION="no"
LABEL_PREFIX=""
VERBOSE="no"

[ -e /etc/efi-boot/update.conf ] && . /etc/efi-boot/update.conf

AUTO_RUN=no

while [ -n "$*" ] ; do
    local arg
    arg="$1"
    shift
    case $arg in
        --help|-h)
            usage
            exit 0
            ;;
        --version)
            echo "$_SCRIPT_NAME $_SCRIPT_VERSION"
            exit 0
            ;;
        --mount|-m)
            MOUNT_EFI_PARTITION=yes
            ;;
        --verbose|-v)
            VERBOSE=yes
            ;;
        --force)
            FORCE_UPDATES="yes"
            ;;
        --auto)
            is_yes "$ALLOW_AUTO" || exit 0
            AUTO_RUN=yes
            ;;
        *)
            usage >&2
            exit 2
            ;;
    esac
done

if ! mountpoint -q /boot/efi ; then
    mkdir -p /boot/efi
    if is_yes "$MOUNT_EFI_PARTITION" ; then
        # first try via fstab
        if ! mount /boot/efi 2>/dev/null ; then
            local efi_device
            efi_device="$(/sbin/blkid -o device -l -t PARTUUID="54f69bcc-954d-4f97-8fef-80b359f9e4aa")"
            if [ -z "$efi_device" ] ; then
                msg "EFI system partition not found."
                exit 1
            fi
            mount -t vfat "$efi_device" /boot/efi
        fi
    fi
    if ! mountpoint -q /boot/efi ; then
        msg "EFI system partition not mounted."
        exit 1
    fi
fi

if [ -x /usr/sbin/efibootmgr ] ; then
    modprobe -q efivars
    EFIBOOTMGR=/usr/sbin/efibootmgr
    if ! $EFIBOOTMGR >/dev/null 2>&1 ; then
        msg "efibootmgr does not work (efivars interface not available?)"
        msg "won't update boot manager configuration"
        EFIBOOTMGR=/bin/true
    else
        EFIBOOTMGR_OPTS="$(get_efibootmgr_opts)"
    fi
else
    msg "efibootmgr missing, won't update the boot manager configuration"
    EFIBOOTMGR=/bin/true
fi

for bootloader_conf in /etc/efi-boot/update.d/*.conf ; do
    if [ ! -e "$bootloader_conf" ] ; then
        continue
    fi
    ENABLED=yes
    CONFIG_NAME="$(basename "$bootloader_conf" .conf)"
    LABEL="$CONFIG_NAME"
    ARCH="$(uname -m)"
    BINARY=""
    ARGS=""
    install_files() {
        /bin/true
    }

    . "$bootloader_conf" || continue

    LABEL="$LABEL_PREFIX$LABEL"

    if ! is_yes "$ENABLED" ; then
        remove_bootmgr_entry "$LABEL" >/dev/null
        continue
    fi

    local efi_arch
    if [[ "$ARCH" = i?86 || "$ARCH" = pentium[45] || "$ARCH" = "athlon" ]] ; then
        # %ix86
        efi_arch=ia32
    elif [[ "$ARCH" = "x86_64" || "$ARCH" = "amd64" || "$ARCH" = "ia32e" ]] ; then
        # %x8664
        efi_arch=x64
    else
        efi_arch="$ARCH"
    fi

    PLATFORM_PATH="EFI/$(echo -nE "$PLATFORM_DIR"|sed -e's/@ARCH@/'"$efi_arch"'/')"
    local escaped_EFI_PLATFORM_PATH="$(echo -nE "$PLATFORM_PATH"|sed -e's;/;\\\\;g')"
    DESTDIR="/boot/efi/$PLATFORM_PATH"
    ARGS="$(echo -nE "$ARGS"|sed -e's/@ARCH@/'"$efi_arch"'/;s/@EFI_PLATFORM_PATH@/'"$escaped_EFI_PLATFORM_PATH"'/g')"

    verbose "ARGS: '$ARGS'"

    mkdir -p "$DESTDIR"

    verbose "Updating $LABEL..."
    install_files
    if [ -n "$BINARY" ] ; then
            bootnum="$(add_bootmgr_entry "$LABEL" "$BINARY" "$ARGS")"
            eval "_$(safe_string ${CONFIG_NAME})_bootnum=\"$bootnum\""
    fi
done

if [ -n "$ORDER" -a "$EFIBOOTMGR" != "/bin/true" ] ; then
    # set up the configured boot order, not removing any existing entries
    tail="$($EFIBOOTMGR | awk '/^BootOrder:/ {gsub(/,/," ",$2); print $2}')"
    head=""
    for config_name in $ORDER ; do
        eval "bootnum=\$_$(safe_string ${config_name})_bootnum"
        if [ -z "$bootnum" ] ; then
            verbose "Cannot find '$config_name' config - won't add to boot order."
            continue
        fi
        tail="$(list_remove "$tail" "$bootnum")"
        head="$head $bootnum"
    done
    bootorder="$(echo -n $head $tail | sed -e's/ /,/g')"
    if [ -n "$bootorder" ] ; then
        verbose_cmd $EFIBOOTMGR $EFIBOOTMGR_OPTS --quiet --bootorder "$bootorder"
    fi
fi

if ! is_yes "$ALLOW_AUTO" && ! is_yes "$AUTO_RUN"; then
    msg "ALLOW_AUTO is not enabled in /etc/efi-boot/update.conf,"
    msg "files will not be automatically updated on upgrades."
fi

# vi: ft=sh sw=4 sts=4 et
