#!/bin/bash
# License: GPL 
# Author: Steven Shiau <steven _at_ nchc org tw>, Ceasar Sun <ceasar _at_ nchc ogr tw>
# Description: Program to prepare grub uEFI network booting image

#
DRBL_SCRIPT_PATH="${DRBL_SCRIPT_PATH:-/usr/share/drbl}"
. $DRBL_SCRIPT_PATH/sbin/drbl-conf-functions
. /etc/drbl/drbl-ocs.conf
. $DRBL_SCRIPT_PATH/sbin/ocs-functions

# Settings
grub_mkimg_compress_opt="-C xz"
#efi_required_mod="part_gpt part_msdos hfsplus fat ext2 ntfs btrfs normal chain boot configfile linux appleldr minicmd multiboot iso9660 acpi efi_gop efi_uga elf loadbios loopback video_fb usb search fixvideo png gfxterm gfxterm_background gfxterm_menu echo videotest video reboot halt gfxmenu gettext tftp efinet net gzio xzio"
# Rerfer to the file systems list in /usr/lib/grub/x86_64-efi/fs.lst. This is for client's local disk boot.
efi_required_mod="normal tftp efinet chain echo net gzio xzio linux \
efi_gop efi_uga png gfxterm gfxterm_background gfxterm_menu \
serial part_gpt part_msdos boot multiboot progress search \
$GRUB_FS_MOD_drbl_ocs configfile test sleep tr reboot halt"

prog="$0"
################
##### Main #####
################
check_if_root
ask_and_load_lang_set

# Find the grub2 commands: grub-mkimage, grub-mknetdir
if type grub2-mkimage >/dev/null 2>&1; then
  GRUB_MKIMG=grub2-mkimage
elif type grub-mkimage >/dev/null 2>&1; then
  GRUB_MKIMG=grub-mkimage
else
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "Command grub2-mkimage or grub-mkimage not found!"
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  echo "Skip creating grub2 uEFI network boot image."
  exit 1
fi
if type grub2-mknetdir >/dev/null 2>&1; then
  GRUB_MKNETD=grub2-mknetdir
elif type grub-mknetdir >/dev/null 2>&1; then
  GRUB_MKNETD=grub-mknetdir
else
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "Command grub2-mknetdir or grub-mknetdir not found!"
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  echo "Skip creating grub2 uEFI network boot modules."
  exit 1
fi
#
grub_efi_tmpd="$(mktemp -d /tmp/grub-efi.XXXXXX)"
# Prepare a grub.cfg so that the eltorito image could find it.
# Because if/elif/else/fi is only supported in normal module, and in this embedded, preconfig only the command in rescue mode is supported, here we use this unique, subtle way. i.e.
# Quoted from Andrei Borzenkov:
# "if" is not external command - it must be recognized and processed by
# script parser and at this point rescue script parser is still active.
# OTOH "configfile" is more or less standard external command. So first
# "if" results in error, then "configfile" succeeds. configfile
# implicitly loads and calls normal.mod so it appears to work. Although
# not exactly as envisioned :)
# //NOTE// So when configfile command succeeds, it won't return to this
# preconfig file. Therefore there is no need to have if/elif/else/fi
# commands.
# For more details, check:
# http://lists.gnu.org/archive/html/help-grub/2015-09/msg00035.html

# Also check grub 2.02 manual (not 2.00, since the variables are very different):
# http://dev.gentoo.org/~floppym/grub.html

cat <<-GRUB_END > $grub_efi_tmpd/grub-header.cfg
set prefix=(tftp)/grub-efi.cfg
echo "Grub CPU and platform: \$grub_cpu, \$grub_platform"
echo 'Network status: '
net_ls_cards
net_ls_addr
net_ls_routes

tr --set pretty_mac x: x- \$net_default_mac

echo "Loading config file \$prefix/grub.cfg-01-\$pretty_mac..."
configfile \$prefix/grub.cfg-01-\$pretty_mac

echo "Loading config file \$prefix/grub.cfg-\$net_default_ip..."
configfile \$prefix/grub.cfg-\$net_default_ip

echo "Loading config file: \$prefix/grub.cfg"
configfile \$prefix/grub.cfg

echo "Could not find config file \$prefix/grub.cfg-\$pretty_mac, \$prefix/grub.cfg-\$net_default_ip or \$prefix/grub.cfg!"
sleep 15

GRUB_END

if [ -z "$GRUB_EFINB_DIR" ]; then
  [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
  echo "\"\$GRUB_EFINB_DIR\" _NOT_ defined!"
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  echo "$msg_program_stop!"
  exit 1
fi
echo "Removing the old $GRUB_EFINB_DIR if it exists..."
if [ -d "$GRUB_EFINB_DIR" ]; then
  rm -rf $GRUB_EFINB_DIR
fi
mkdir -p $GRUB_EFINB_DIR

# Get the grub version
grub_v="$(LC_ALL=C $GRUB_MKIMG --version | awk -F" " '{print $NF}')"
if [ -n "$grub_v" ]; then
  echo "$grub_v" > $pxecfg_pd/GRUB_VERSION
else
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "Warngin! Grub version _NOT_ found!"
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
fi

# Prepare some files, e.g. background img and fonts
# Unicode is located at:
# Debian: /usr/share/grub/unicode.pf2 
# CetnOS: /boot/efi/EFI/centos/fonts/unicode.pf2
echo "Preparing background img and font..."
unicode_font="$(LC_ALL=C find /usr/share/grub/ /boot/efi/EFI/ -name "unicode.pf2" -print 2>/dev/null | uniq | head -n 1)"
if [ -n "$unicode_font" ]; then
  cp -fv $unicode_font $pxecfg_pd/
fi
cp -fv $grub_bg_img $pxecfg_pd/

# Generate the template grub.cfg
gen-grub-efi-nb-menu

# For x86-64 arch, we create bootx64.efi and bootia32.efi.
# For i386 arch, we only create bootia32.efi.
# First we find the installed OS arch
sys_arch="$(LC_ALL=C getconf LONG_BIT)"
dest_bootloader=""
case "$sys_arch" in 
  32)  echo "System architecture is 32-bit."
       dest_bootloader="i386"
       ;;
  64)  echo "System architecture is 64-bit."
       dest_bootloader="i386 x86_64";;
   *) 
       [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
       echo "Unknown system architecture in drbl-gen-grub-efi-nb!"
       [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
       echo "$msg_program_stop!"
       exit 1
esac

for i in $dest_bootloader; do
  export EFI_ARCH=$i
  case "$EFI_ARCH" in
    i386)   
            if [ ! -e /usr/lib/grub/i386-efi/moddep.lst ]; then
	      # On some system, like CentOS 7, there is no i386 efi from grub2.
	      continue
	    fi 
            out_f="$pxecfg_pd/bootia32.efi" ;;
    x86_64) out_f="$pxecfg_pd/bootx64.efi" ;;
  esac
  # Checking the required modules
  for im in $efi_required_mod; do
    if [ ! -e "/usr/lib/grub/$EFI_ARCH-efi/${im}.mod" ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "Missing grub2 module ${im}.mod... Exluding ${im}.mod..."
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      efi_required_mod="$(echo $efi_required_mod | sed -r -e "s/$im //g")"
    fi
  done
  echo "Creating the uEFI network booting bootable image $out_f..."
  $GRUB_MKIMG $grub_mkimg_compress_opt -O ${EFI_ARCH}-efi -o $out_f --prefix='(tftp)/grub-efi.cfg/' -c $grub_efi_tmpd/grub-header.cfg $efi_required_mod
  rc=$?
  if [ "$rc" -ne 0 ]; then
    # When grub-mkimage fails, an empty output file will still be created. We'd better to clean it.
    rm -f $out_f
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "Warning! Failed to create $out_f!"
    echo "This server won't be able to serve uEFI network boot clients."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    sleep 5
  else
    # Create a tag file
    echo "Created by program \"$prog\" with command:" > ${out_f}.info
    echo "$GRUB_MKIMG $grub_mkimg_compress_opt -O ${EFI_ARCH}-efi -o $out_f --prefix='(tftp)/grub-efi.cfg/' -c $grub_efi_tmpd/grub-header.cfg $efi_required_mod" >> ${out_f}.info
    echo "The contents of $grub_efi_tmpd/grub-header.cfg:" >> ${out_f}.info
    echo "$msg_delimiter_star_line" >> ${out_f}.info
    cat $grub_efi_tmpd/grub-header.cfg >> ${out_f}.info
    echo "$msg_delimiter_star_line" >> ${out_f}.info
  fi
done

# Suppress the output to avoid confusion.
echo "Preparing the grub modules in $GRUB_EFINB_DIR..."
$GRUB_MKNETD --net-directory=$GRUB_EFINB_DIR --subdir=/ >/dev/null
echo "The uEFI network booting is ready."

# Clean the temp dir
if [ -e "$grub_efi_tmpd" -a -n "$(echo $grub_efi_tmpd | grep -E "grub-efi")" ]; then
  rm -rf $grub_efi_tmpd
fi
