#!/usr/bin/sh

# Copyright (c) 2017, Mellanox Technologies
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and documentation are those
# of the authors and should not be interpreted as representing official policies,
# either expressed or implied, of the FreeBSD Project.

set -e

PROGNAME=$(basename "$0")

usage()
{
    cat <<EOF
Usage:    $PROGNAME COMMAND [PARAMS]

COMMAND list:
    --help                          : print help.
    --add <DISTRO> <KERNEL> <ARGS>  : create a new boot entry.
    --cleanall                      : delete all boot entires.
EOF
}

add=
cleanall=
option=0

PARSED_OPTIONS=$(getopt -n "$PROGNAME" -o h -l help,add,cleanall -- "$@")
eval set -- "$PARSED_OPTIONS"

while true
do
  case $1 in
      -h | --help)
          usage
          exit 0
          ;;
      -a | --add)
          add=1
          option=$((option+1))
          shift
          ;;
      -c | --cleanall)
          cleanall=1
          option=$((option+1))
          shift
          ;;
      --)
          shift
          break
          ;;
  esac
done

# Check arguments. For now, a single option is implemented at a time.
if [ $option -ne 1 ]; then
    echo "Cannot support multiple options!"
    usage
    exit 1
fi

# This script requires that the kernel supports access to EFI non-volatile
# variables. The UEFI system requires several kernel configuration options.
# Since this script might be used after booting with initramfs only, we
# prefer to not enable those EFI configurations options. Alternatively, we
# use the efivarfs (EFI VARiable File System) interface (CONFIG_EFIVAR_FS)
# mounted using efivarfs kernel module at /sys/firmware/efi/efivars to
# remove EFI variables.

efivars=/sys/firmware/efi/efivars

boot_cleanall()
{
    # Clean all the dump-type0* variables generated by Linux crash.
    if [ -d "$efivars" ]; then
        chattr -i "$efivars"/dump-type0* 2>/dev/null || true
        rm -f "$efivars"/dump-type0* 2>/dev/null
    fi

    # First check whether boot order array variable is present and has
    # boot options
    boot_order=$(efibootmgr | grep BootOrder)
    if [ "$(echo "$boot_order" | cut -f1 -d':')" = "BootOrder" ]; then
        options=$(echo "$boot_order" | cut -f2 -d':' | sed -e 's/,/ /g')
        if [ -n "$options" ]; then
            # Clear boot options
            for opt in $options; do
                efibootmgr -b "$opt" -B
            done
        fi
    fi

    # Second round: there might be old boot entries not part of the
    # boot order array. Those entries might be installed after firmware
    # recovery. Boot entries are formatted as BootXXXX where XXXX refer
    # to an hexadecimal number. Thus extract that number only.
    options=$(efibootmgr | grep -F "*" | cut -f1 -d'*' | cut -c5-)
    # Check whether extra boot entries exist.
    if [ -n "$options" ]; then
        # Clear boot options
        for opt in $options; do
            efibootmgr -b "$opt" -B
        done
    fi
}

boot_add()
{
    # Read parameters.
    distro="$1"
    kernel="$2"
    bootargs="$3"

    # Set default parameters
    disk="/dev/mmcblk0"
    part="1"

    # Create the boot entry.
    echo "Distro      : $distro"
    echo "Kernel Image: $kernel"
    echo "Command line: $bootargs"
    efibootmgr -c -d $disk -p $part -l "$kernel" -L "$distro" -u "$bootargs"
}

# Linux kernel exposes EFI variables data to userspace via efivarfs
# interface. Thus mount it, if needed.
test "$(ls -A $efivars)" || mount -t efivarfs none $efivars

# Clean up boot options using 'efibootmgr'.
[ -n "$cleanall" ] && (boot_cleanall; exit $?)

# Add boot option
if [ -n "$add" ]; then
    # Check arguments
    if [ $# -lt 3 ]; then
        echo "$PROGNAME: invalid operand(s)"
        echo "Try: $PROGNAME --add <DISTRO> <KERNEL> <ARGS>"
        exit 1
    fi
    boot_add "$1" "$2" "$3"
    exit $?
fi
