#!/bin/bash
# License: GPL 
# Author: Steven Shiau <steven _at_ clonezilla org>
# Description: Program to update the UEFI NVRAM for the restored disk

# Load DRBL setting and functions
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

# Load the config in ocs-live.conf. This is specially for Clonezilla live. It will overwrite some settings of /etc/drbl/drbl-ocs.conf, such as $DIA...
[ -e "/etc/ocs/ocs-live.conf" ] && . /etc/ocs/ocs-live.conf

# Settings
clean_nvram_unused_boot_entry="yes"

# Settings
# For the known full path file to EFI boot file list:
# Ubuntu 12.10: /EFI/ubuntu/grubx64.efi
# Fedora 18: /EFI/fedora/shim.efi /EFI/fedora/grubx64.efi
# RHEL/CentOS 6.x: /EFI/redhat/grub.efi
# OpenSuSE 12.3: /EFI/opensuse/grubx64.efi" 
# MS Windows 7/8: /EFI/Boot/bootx64.efi
# CentOS 7.4: /EFI/centos/grubx64.efi
# //NOTE// Rememer to sync the variable efiblfiles in /etc/drbl/boot-local-efi.cfg and drbl/conf/boot-local-efi.cfg.
efiblfiles="shimx64.efi shim.efi grubx64.efi grub.efi elilo.efi bootx64.efi Boot/bootmgfw.efi grubaa64.efi bootaa64.efi bootarm.efi"
# Default label name
label_def="OS on hard drive"
# The log file later other program can refer
efi_boot_file_info=""

#
USAGE() {
    echo "$ocs - To update the UEFI NVRAM with EFI system partition on hard drive"
    echo "Usage:"
    echo "To run $ocs:"
    echo "$ocs [OPTION] DISK"
    echo "DISK name can be with or without /dev/, e.g., /dev/sda or sda."
    echo
    echo "OPTION:"
    echo "-l, --label LABEL     Use LABEL when writing label in EFI NVRAM."
    echo "-f, --efi-boot-file-info FNAME Output the EFI system partition and boot file info to file FNAME."
    echo "-r, --full-path-to-efi-file  FULL_PATH_TO_EFI_FILE   Use the label in FULL_PATH_TO_EFI_FILE, which is the output of command \"efibootmgr -v\"."
    echo "-s, --skip-clean-nvram-unused-boot-entry  Skip cleaning unused boot entry in NVRAM. Just keep the way it is."
    echo "//NOTE// The options -l and -r conflict with each other. Use one of them only."
    echo
    echo "Ex:"
    echo "To update the UEFI NVRAM with existing EFI system partition on /dev/sda, run"
    echo "   $ocs /dev/sda"
    echo
    echo "To update the UEFI NVRAM with the label in /home/partimag/centos7-efi/efi-nvram.dat on /dev/sda, run"
    echo "   $ocs -r /home/partimag/centos7-efi/efi-nvram.dat -f /tmp/efi_info.txt /dev/sda"
    echo
} # end of USAGE
#
check_if_apple_mac() {
  # Function to check if it's Apple Mac machine.
  local system_manufac product_nm ret_mac
  system_manufac="$(LC_ALL=C dmidecode -s system-manufacturer)"
  product_nm="$(LC_ALL=C dmidecode -s system-product-name)"
  if [ -n "$(echo $product_nm | grep -i "Mac")" ]; then
    echo "This machine is Mac."
    return 0
  elif [ -n "$(echo $system_manufac | grep -i "Apple")" ]; then
    echo "This machine was made by Apple."
    return 0
  fi
  # Final check, sometimes dmidecode won't give anything. Like it gives errors:
  # mmap: Can't map beyond end of file /sys/firmware/dmi/tables/DMI
  # Table is unreachable, sorry.
  if [ -z "$product_nm" -a -z "$system_manufac" ]; then
    if [ -n "$(LC_ALL=C strings /sys/firmware/dmi/tables/DMI 2>/dev/null | \
	    grep -i "Apple Inc.")" ]; then
      echo "This machine was made by Apple."
      return 0
    fi
  fi
  return 1
} # end of check_if_apple_mac
#
check_if_dell() {
  # Function to check if it's Dell machine.
  # Some users reported that efibootmgr bricked Dell's BIOS:
  # https://sourceforge.net/p/clonezilla/discussion/Open_discussion/thread/9fc9c4dee3/
  # https://sourceforge.net/p/clonezilla/discussion/Clonezilla_live/thread/76ba35a226/
  # https://sourceforge.net/p/clonezilla/bugs/310/
  local system_manufac product_nm
  system_manufac="$(LC_ALL=C dmidecode -s system-manufacturer)"
  product_nm="$(LC_ALL=C dmidecode -s system-product-name)"
  if [ -n "$(echo $product_nm | grep -i "Dell.")" ]; then
    echo "This machine is from Dell."
    return 0
  elif [ -n "$(echo $system_manufac | grep -i "Dell.")" ]; then
    echo "This machine was made by Dell."
    return 0
  fi
  # Final check, sometimes dmidecode won't give anything. Like it gives errors:
  # mmap: Can't map beyond end of file /sys/firmware/dmi/tables/DMI
  # Table is unreachable, sorry.
  if [ -z "$product_nm" -a -z "$system_manufac" ]; then
    if [ -n "$(LC_ALL=C strings /sys/firmware/dmi/tables/DMI 2>/dev/null | \
	    grep -i "Dell Inc.")" ]; then
      echo "This machine was made by Dell."
      return 0
    fi
  fi
  return 1
} # end of check_if_dell
#
get_efi_system_part() {
  # root@debian:/tmp# sgdisk -p /dev/sda
  # Disk /dev/sda: 125829120 sectors, 60.0 GiB
  # Logical sector size: 512 bytes
  # Disk identifier (GUID): 32C9A88F-10DA-4FDE-AD29-0C74501C2CD7
  # Partition table holds up to 128 entries
  # First usable sector is 34, last usable sector is 125829086
  # Partitions will be aligned on 2048-sector boundaries
  # Total free space is 109055933 sectors (52.0 GiB)
  # 
  # Number  Start (sector)    End (sector)  Size       Code  Name
  #    1            2048          411647   200.0 MiB   EF00  efi
  #    2          411648         1435647   500.0 MiB   0700  
  #    3         1435648        16775167   7.3 GiB     8E00  
  
  # For MS Windows 8
  # root@debian:~# sgdisk -p /dev/sda
  # Disk /dev/sda: 125829120 sectors, 60.0 GiB
  # Logical sector size: 512 bytes
  # Disk identifier (GUID): A5D2BC01-4FDF-492A-BAB7-A107321A88FD
  # Partition table holds up to 128 entries
  # First usable sector is 34, last usable sector is 125829086
  # Partitions will be aligned on 2048-sector boundaries
  # Total free space is 4029 sectors (2.0 MiB)
  # 
  # Number Start (sector)    End (sector)  Size       Code  Name
  #   1            2048          616447   300.0 MiB   2700  Basic data partition
  #   2          616448          819199   99.0 MiB    EF00  EFI system partition
  #   3          819200         1081343   128.0 MiB   0C01  Microsoft reserved part
  #   4         1081344       125827071   59.5 GiB    0700  Basic data partition
  #

  local test_disk_="$1"
  local start_n efi_part
  if [ -z "${test_disk_}" ]; then
    echo "No variable \$test_disk_ exists in function get_efi_system_part!"
    exit 1
  fi
  start_n="$(LC_ALL=C sgdisk -p ${test_disk_} 2>/dev/null | grep -n -E "^Number.*Name" | awk -F":" '{print $1}')"
  start_n="$((start_n + 1))"
  efi_part="$(LC_ALL=C sgdisk -p ${test_disk_} 2>/dev/null | tail -n +${start_n} | awk -F" " '/ EF00 / {print $1}')"
  if [ -n "$efi_part" ]; then
    echo "$efi_part"
  fi
} # end of get_efi_system_part
#
check_if_nvram_efi_boot_entry_exist() {
  # Function to check if the boot entry exists
  # Some examples:
  # 1. Fedora 19:
  # root@debian:~# efibootmgr -v
  # BootCurrent: 0001
  # BootOrder: 0004,0000,0001,0002,0003
  # Boot0000* EFI VMware Virtual SCSI Hard Drive (0.0)      ACPI(a0341d0,0)PCI(10,0)SCSI(0,0)
  # Boot0001* EFI VMware Virtual IDE CDROM Drive (IDE 1:0)  ACPI(a0341d0,0)PCI(7,1)ATAPI(1,0,0)
  # Boot0002* EFI Network   ACPI(a0341d0,0)PCI(11,0)PCI(1,0)MAC(000c291866c1,0)
  # Boot0003* EFI Internal Shell (Unsupported option)       MM(b,3f055000,3f3b5fff)
  # Boot0004* Fedora        HD(1,800,64000,55a8b515-52ef-4fdc-affd-80d5b441e028)File(\EFI\fedora\shim.efi)
  # 
  # 2. MS Windows
  #  root@debian:~# efibootmgr -v
  #  BootCurrent: 0001
  #  Timeout: 0 seconds
  #  BootOrder: 0000,0001
  #  Boot0000* Windows Boot Manager  HD(2,96800,31800,e72dce29-ea6a-4235-9eaa-fc11605559ca)File(\EFI\Microsoft\Boot\bootmgfw.efi)WINDOWS.........x...B.C.D.O.B.J.E.C.T.=.{.9.d.e.a.8.6.2.c.-.5.c.d.d.-.4.e.7.0.-.a.c.c.1.-.f.3.2.b.3.4.4.d.4.7.9.5.}....................
  #  Boot0001* UEFI:         ACPI(a0341d0,0)PCI(1a,0)USB(1,0)USB(2,0)HD(1,3f,752c1f,0007c65f)AMBO
  #  
  #  Boot0000* Windows       HD(2,96800,31800,e72dce29-ea6a-4235-9eaa-fc11605559ca)File(\EFI\Boot\bootx64.efi)
  #  Boot0001* UEFI:         ACPI(a0341d0,0)PCI(1a,0)USB(1,0)USB(2,0)HD(1,3f,752c1f,0007c65f)AMBO
  #
  # 3.  
  # For Mac
  # root@mac:~# efibootmgr -v
  # BootCurrent: 0000
  # Timeout: 5 seconds
  # BootOrder: 0080
  # Boot0080*       ACPI(a0341d0,0)PCI(1f,2)03120a00000000000000HD(2,64028,3a1ec0c0,00006572-1fb9-0000-5d0a-000037290000)File(\System\Library\CoreServices\boot.efi)
  # BootFFFF*       ACPI(a0341d0,0)PCI(1f,2)03120a00000000000000HD(2,64028,3a1ec0c0,00006572-1fb9-0000-5d0a-000037290000)File(\System\Library\CoreServices\boot.efi)

  # The following is from the uEFI specification "3.1.1 Boot Manager Programming"
  # Each load option entry resides in a Boot#### variable or a Driver#### variable where the #### is replaced by a unique option number in printable hexadecimal representation using the digits 0–9, and the upper case versions of the characters A–F (0000–FFFF). The #### must always be four digits, so small numbers must use leading zeros

  #  # We could use udevadm info to get the partition UUID:
  #  root@debian:~# LC_ALL=C udevadm info -q env -n /dev/sda2 | grep "UDISKS_PARTITION_UUID="
  #  UDISKS_PARTITION_UUID=E72DCE29-EA6A-4235-9EAA-FC11605559CA
  local efi_sys_part_uuid_in_nvram efi_sys_part_uuid_in_harddrive ret_code
  # Return code: 
  # 0: UUID exists in NVRAM, matching the one in hard drive, no need to update boot entry in NVRAM.
  # 2: UUID exists in NVRAM, not matching the one in hard drive, need to update boot entry in NVRAM.
  # 4: UUID does not exist in NVRAM, need to update boot entry in NVRAM.

  # efi_system_part is global variable
  
  # It might be duplicated uEFI boot entry in NVRAM for same ESP, just need to keep one.
  efi_sys_part_uuid_in_nvram="$(get_efi_hd_boot_entry_info -n $efi_system_part nvram uuid | uniq)"
  efi_sys_part_uuid_in_harddrive="$(get_part_uuid_in_harddrive $efi_system_part)"
  ret_code=""
  if [ -n "$efi_sys_part_uuid_in_nvram" ]; then
    if [ "$efi_sys_part_uuid_in_nvram" = "$efi_sys_part_uuid_in_harddrive" ]; then
      ret_code="0"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "EFI system partition UUID $efi_sys_part_uuid_in_nvram in EFI NVRAM matches the one on partition $efi_system_part."
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    else
      ret_code="2"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "The EFI system partition UUID $efi_sys_part_uuid_in_nvram in EFI NVRAM does _NOT_ match the one on partition $efi_system_part. UUID of $efi_system_part is: $efi_sys_part_uuid_in_harddrive"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    fi
  else
    ret_code="4"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "No partition boot entry from hard drive exists in EFI NVRAM."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "EFI boot entries on this system:"
    echo $msg_delimiter_star_line
    LC_ALL=C efibootmgr -v
    echo $msg_delimiter_star_line
  fi
  return $ret_code
} # end of check_if_nvram_efi_boot_entry_exist
#
check_efi_boot_loader_file() {
  # Function to find the EFI boot loader file
  # File nvram_chklist_table contains something like:
  # # label; uuid; boot_file
  # CentOS Linux; fdd78f33-3c25-48b0-9fc7-c5dc7f1b1eff; \EFI\centos\grubx64.efi;
  # CentOS Linux; fdd78f33-3c25-48b0-9fc7-c5dc7f1b1eff; \EFI\centos\shimx64.efi;
  # Inputted global variables: nvram_chklist_table, efi_system_part
  local lb_ uud_ bt_f_ esp_p_
  local mnt_pnt obreak efi_boot efi_boot_append dirs_list
  mnt_pnt="$(mktemp -d /tmp/efi_sys_part.XXXXXX)"
  # We will try to find the label/uuid/boot_file from the file $nvram_chklist_table.
  # However, if it contains none of them, we need to parse the ESP partition to get it.
  while read -r line; do
    [ -n "$(echo "$line" | grep -E "^[[:space:]]*#")" ] && continue
    lb_="$(echo "$line" | awk -F";" '{print $1}')"; lb_="$(echo $lb_)"
    # Avoid to output empty line.
    [ "$lb_" = "" ] && continue
    uud_="$(echo "$line" | awk -F";" '{print $2}')"; uud_="$(echo $uud_)"
    bt_f_="$(echo "$line" | awk -F";" '{print $3}')"; bt_f_="$(echo $bt_f_)"
    esp_p_="$(get_part_name_from_uuid_in_harddrive $uud_)"
    if [ -n "$esp_p_" -a -b "$esp_p_" ]; then
      # Make sure the boot file exists in the ESP partition.
      if [ -n "$(LC_ALL=C mount | awk -F" " '{print $1}' | grep -Ew ${esp_p_})" ]; then
        umount ${esp_p_}
        if [ "$?" -gt 0 ]; then
          [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
          echo "Failed to umount ${esp_p_}."
          [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
          echo "$msg_program_stop!"
          exit 1
        fi
      fi
      mount -o ro $esp_p_ $mnt_pnt
      # Revert the \ as / e.g. \EFI\fedora\shim.efi -> /EFI/fedora/shim.efi
      esp_boot_f_unix_format="$(echo "$bt_f_" | sed -r -e "s/\\\/\//g")"
      # Force to convert it for make sure it meet the requirement for EFI NVRAM, as the one got from EFI NVRAM might be "/" format. 
      # i.e. UEFI uses backward slash \ as path separator (similar to DOS paths)
      # Ref: https://wiki.archlinux.org/index.php/Unified_Extensible_Firmware_Interface#Booting_an_OS_using_UEFI
      # //NOTE// The efibootmgr-0.6.0-3 and above pkgs support passing unix-style paths with forward-slash / as path-separator for the -l option. 
      esp_p_="$(echo $esp_p_ | sed -r -e "s|\/|\\\|g")"
      if [ -e "$mnt_pnt/$esp_boot_f_unix_format" ]; then
        # Make sure it does not exist in NVRAM before outputting.
	if [ -z "$(comm -12 <(echo "$line" | sort) <(echo "$label_uuid_boot_f_in_nvram" | sort))" ]; then
	  echo "${lb_}; ${uud_}; ${bt_f_}" >> $nvram_2_update_table
	fi
      else
        # No reference one from EFI NVRAM, we have to find or guess...
        # Possible files examples:
        # Ubuntu Quantal: /EFI/ubuntu/grubx64.efi
        # Fedora 19: /EFI/fedora/shim.efi
        # MS Windows: /EFI/Boot/bootx64.efi
        obreak="no"
        efi_boot="no"
        efi_boot_append=""
        dirs_list=""
        for idir in $mnt_pnt/EFI/*; do
          # Since ESP partition is definitely a FAT partition. It's case insentive.
          # Hence no need to test $mnt_pnt/efi/ here or it will be duplicated.
          # However, later the dir name 
          # might be uppercase or lowercase. Hence we have to use "-i" of grep or
          # regular expression of sed to deal with that.
          # We only need dir in 2nd level of path
          if [ ! -d "$idir" ]; then
            continue
          fi
	  if [ -n "$(echo "$idir" | grep -Ei "$mnt_pnt/EFI/BOOT")" ]; then
            # Should not add /EFI/{boot,Boot,BOOT} now, make it as the last one later.
            efi_boot="yes"
            efi_boot_append="$idir"
            continue
          fi
          dirs_list="$dirs_list $idir"
        done
        if [ "$efi_boot" = "yes" ]; then
          dirs_list="$dirs_list $efi_boot_append"
        fi
        for dir in $dirs_list; do
          for file in $efiblfiles; do
              if test -f $dir/$file; then
                echo "Found EFI boot loader: $dir/$file"
                # esp_boot_f example: \EFI\Boot\bootx64.efi
                esp_boot_f="$(LC_ALL=C echo $dir/$file | sed -r -e "s|$mnt_pnt||g" | sed -r -e "s|\/|\\\|g")"
		esp_boot_os="$(LC_ALL=C echo $dir/$file | sed -r -e "s@$mnt_pnt/(EFI|efi)/@@g" | xargs dirname)"
                obreak="yes"
                break
              fi
          done
          if [ "$obreak" = "yes" ]; then
            break
          fi
        done
        # Make sure it does not exist in NVRAM before outputting.
	if [ -z "$(comm -12 <(echo "$line" | sort) <(echo "$label_uuid_boot_f_in_nvram" | sort))" ]; then
	  echo "${esp_boot_os}; ${uud_}; ${esp_boot_f}" >> $nvram_2_update_table
	fi
      fi
    fi
    if mountpoint ${mnt_pnt} &>/dev/null; then
      umount ${mnt_pnt}
    fi
  done < $nvram_chklist_table

  # If the file $nvram_2_update_table contains none of label/uuid/boot_file,
  # we need to parse the ESP partition to get it.
  # This might happen when the saved NVRAM does not have any ESP boot info,
  # e.g., it was saved from USB live system, but restored to hard drive.
  if  [ "$(LC_ALL=C grep -vE "^[[:space:]]*#" $nvram_2_update_table | wc -l)" -eq 0 ]; then
    # No reference one from EFI NVRAM, we have to find or guess...
    # Possible files examples:
    # Ubuntu Quantal: /EFI/ubuntu/grubx64.efi
    # Fedora 19: /EFI/fedora/shim.efi
    # MS Windows: /EFI/Boot/bootx64.efi
    uud_="$(ocs-get-dev-info $efi_system_part PARTUUID)"
    mount -o ro $efi_system_part $mnt_pnt
    obreak="no"
    efi_boot="no"
    efi_boot_append=""
    dirs_list=""
    for idir in $mnt_pnt/EFI/*; do
      # Since ESP partition is definitely a FAT partition. It's case insentive.
      # Hence no need to test $mnt_pnt/efi/ here or it will be duplicated.
      # However, later the dir name 
      # might be uppercase or lowercase. Hence we have to use "-i" of grep or
      # regular expression of sed to deal with that.

      # We only need dir in 2nd level of path
      if [ ! -d "$idir" ]; then
        continue
      fi
      if [ -n "$(echo "$idir" | grep -Ei "$mnt_pnt/EFI/BOOT")" ]; then
        # Should not add /EFI/{boot,Boot,BOOT} now, make it as the last one later.
        efi_boot="yes"
        efi_boot_append="$idir"
        continue
      fi
      dirs_list="$dirs_list $idir"
    done
    if [ "$efi_boot" = "yes" ]; then
      dirs_list="$dirs_list $efi_boot_append"
    fi
    for dir in $dirs_list; do
      for file in $efiblfiles; do
          if test -f $dir/$file; then
            echo "Found EFI boot loader: $dir/$file"
            # esp_boot_f example: \EFI\Boot\bootx64.efi
            esp_boot_f="$(LC_ALL=C echo $dir/$file | sed -r -e "s|$mnt_pnt||g" | sed -r -e "s|\/|\\\|g")"
    	    esp_boot_os="$(LC_ALL=C echo $dir/$file | sed -r -e "s@$mnt_pnt/(EFI|efi)/@@g" | xargs dirname)"
            obreak="yes"
            break
          fi
      done
      if [ "$obreak" = "yes" ]; then
        break
      fi
    done
    echo "${esp_boot_os}; ${uud_}; ${esp_boot_f}" >> $nvram_2_update_table

    if mountpoint ${mnt_pnt} &>/dev/null; then
      umount ${mnt_pnt}
    fi
  fi

  # Clean stale dir
  if [ -d "${mnt_pnt}" -a \
       -n "$(echo $mnt_pnt | grep -i "efi_sys_part")" ]; then
    rmdir "${mnt_pnt}" 
  fi
} # end of check_efi_boot_loader_file
#
output_efi_boot_info() {
  # Output the info so that later other program can reuse.
  # efi_os_label, efi_system_part_no and esp_boot_f (global variables) are for reporting to ocsmgrd.
  # We give initial value instead of none so that it's easier for ocsmgrd to cut.
  local lb_ boot_f_ lb_all boot_f_all
  efi_os_label="NO_EFI_OS_LABEL"
  efi_system_part_no="NO_EFI_PART"
  efi_sys_part_boot_file="NO_EFI_BOOT_FILE"
  if [ -n "$efi_boot_file_info" ]; then
    while read -r line; do
      [ -n "$(echo "$line" | grep -E "^[[:space:]]*#")" ] && continue
      # Avoid to output empty line.
      [ "$lb_" = "" ] && continue
      lb_="$(echo "$line" | awk -F";" '{print $1}')"; lb_="$(echo $lb_)"
      boot_f_="$(echo "$line" | awk -F";" '{print $3}')"; boot_f_="$(echo $boot_f_)"
      [ -n "$lb_" ] && lb_all="$lb_all $lb_"
      [ -n "$boot_f_" ] && boot_f_all="$boot_f_all $boot_f_"
    done < $nvram_2_update_table
    if [ -n "$lb_all" ]; then
      lb_all="$(echo $lb_all)"
      echo "efi_os_label=\"$lb_all\"" > $efi_boot_file_info
    fi
    if [ -n "$esp_no" ]; then
      esp_no="$(echo $esp_no)"
      echo "efi_system_part_no=\"$esp_no\"" >> $efi_boot_file_info
    fi
    if [ -n "$boot_f_all" ]; then
      boot_f_all="$(echo $boot_f_all)"
      # The boot_f_all got is not Unix path format. We have to convert it like:
      # \EFI\fedora\shim.efi -> /EFI/fedora/shim.efi
      echo "efi_sys_part_boot_file=\"$(echo "$boot_f_all" | sed -r -e "s|\\\|\/|g")\"" >> $efi_boot_file_info
    fi
  fi
} # end of output_efi_boot_info
#
do_clean_EFI_boot_entry_job() {
  local boot_num_2_remove="$1"
  if [ -z "$boot_num_2_remove" ]; then
    echo "boot_num_2_remove not assigned in function do_clean_EFI_boot_entry_job!"
    exit 1
  fi
  echo "Clean the boot entry $boot_num_2_remove by:"
  clean_boot_entry_cmd="efibootmgr -b $boot_num_2_remove -B"
  echo "Running: $clean_boot_entry_cmd"
  LC_ALL=C eval $clean_boot_entry_cmd
} # end of do_clean_EFI_boot_entry_job
#
clean_unused_uefi_boot_entry_in_nvram() {
  local do_clean_EFI_boot_entry
  local dup_bf_fp all_boot_num boot_nums_2_be_removed
  all_boot_num=$(get_efi_hd_boot_entry_info -a nvram boot_num)
  efi_tmpd="$(LC_ALL=C mktemp -d /tmp/efi_tmpd.XXXXXX)"
  echo "Trying to clean unused uEFI boot entry if it exists..."
  for ib in $all_boot_num; do
    do_clean_EFI_boot_entry="no"
    efi_f=""
    efi_partuuid=""
    efi_part=""
    # The file path got from get_efi_hd_boot_entry_info is like: \EFI\fedora\shim.efi
    # We have to convert it as something like: /EFI/fedora/shim.efi
    efi_f="$(get_efi_hd_boot_entry_info -b $ib nvram boot_file | sed -r -e "s|\\\|\/|g")"
    efi_partuuid="$(get_efi_hd_boot_entry_info -b $ib nvram uuid)"
    # When there are two devices having same UUID, we can only choose one of them so that later mount will work.
    # Normally this does not happen. However, if it is, we do not know which device will be kept in the machine,
    # hence assume it is the 1st one after sorting.
    efi_part="$(blkid -o full | grep -Ew "PARTUUID=\"${efi_partuuid}\"" | awk -F":" '{print $1}' | sort -V | head -n 1)"
    if [ -z "$efi_f" ]; then
      continue
    fi
    # Found EFI boot file listed in NVRAM.
    # The cases for useless uEFI boot entry:
    # (1) EFI boot file listed in NVRAM, but there is no such partition.
    # (2) EFI boot file listed in NVRAM, the partition exists, but there is no such boot file.
    if [ -z "$efi_part" ]; then
      # This is case 1.
      echo "Boot file $efi_f listed in NVRAM, but there is no any partition with PARTUUID \"$efi_partuuid\" contains it."
      do_clean_EFI_boot_entry="yes"
    elif mount -o ro $efi_part $efi_tmpd 2>&1; then
      if [ ! -e "$efi_tmpd/$efi_f" ]; then
        # This is case 2.
        echo "Boot file $efi_f does not exist in partition $efi_part."
        do_clean_EFI_boot_entry="yes"
      else
        echo "Found Boot file $efi_f in partition $efi_part."
      fi
    fi
    if mountpoint $efi_tmpd >/dev/null 2>&1; then
      umount $efi_tmpd
    fi
    if [ "$do_clean_EFI_boot_entry" = "yes" ]; then
      do_clean_EFI_boot_entry_job $ib
      echo $msg_delimiter_star_line
    fi
  done
  if [ -d "$efi_tmpd" ]; then
    rmdir $efi_tmpd
  fi
  # Process the duplicated uEFI boot entry, just need to keep one.
  dup_bf_fp="$(get_efi_hd_boot_entry_info -a nvram boot_file_full_path | sort | uniq -d)"
  for id in $dup_bf_fp; do
    all_boot_num="$(LC_ALL=C efibootmgr -v | grep -F "$id" | awk -F" " '{print $1}' | sed -r -e "s/^Boot//g" -e "s/\*$//g")"
    all_boot_num="$(echo $all_boot_num)"  # Make it in one line
    # Remove all the duplicated bootnum except the 1st one since we have to keep at least one.
    boot_nums_2_be_removed="$(echo $all_boot_num | awk -F" " '{for (i=2; i<=NF; ++i) print $i}')"
    boot_nums_2_be_removed="$(echo $boot_nums_2_be_removed)"  # Make it in one line
    for ib in $boot_nums_2_be_removed; do
      echo "Removing duplicated boot entry $ib..."
      do_clean_EFI_boot_entry_job $ib
    done
  done
} # end of clean_unused_uefi_boot_entry_in_nvram

########################
##### MAIN PROGRAM #####
########################
#
ocs_file="$0"
ocs=`basename $ocs_file`

while [ $# -gt 0 ]; do
 case "$1" in
   -l|--label)
           shift; 
           if [ -z "$(echo $1 |grep ^-.)" ]; then
             # skip the -xx option, in case 
             label_assigned="$1"
             shift;
           fi
           [ -z "$label_assigned" ] && USAGE && exit 1
           ;;
   -f|--efi-boot-file-info)
           shift; 
           if [ -z "$(echo $1 |grep ^-.)" ]; then
             # skip the -xx option, in case 
             efi_boot_file_info="$1"
             shift;
           fi
           [ -z "$efi_boot_file_info" ] && USAGE && exit 1
           ;;
   -r|--full-path-to-efi-file)
           shift; 
           if [ -z "$(echo $1 |grep ^-.)" ]; then
             # skip the -xx option, in case 
             full_path_to_efi_file="$1"
             shift;
           fi
           [ -z "$full_path_to_efi_file" ] && USAGE && exit 1
           ;;
   -s|--skip-clean-nvram-unused-boot-entry)  
	   clean_nvram_unused_boot_entry="no"
           shift;;
   -*)     echo "${0}: ${1}: invalid option" >&2
           USAGE >& 2
           exit 2 ;;
   *)      break ;;
 esac
done

disk_="$(format_dev_name_with_leading_dev $1)"

if [ -z "${disk_}" ]; then
  USAGE
  exit 1
fi
if ! is_whole_disk ${disk_}; then
  [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
  echo "This is _NOT_ a disk name: ${disk_}"
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  USAGE
  exit 1
fi
#
ask_and_load_lang_set $specified_lang

# //NOTE// 2017/09/20 No more need this, since efibootmgr works for Mac now.
## Check if it's Apple Mac machine
#if check_if_apple_mac; then
#  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
#  echo "Due to an issue of efibootmgr on Mac machine, EFI NVRAM boot entry update is skipped."
#  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
#  echo "$msg_program_stop!"
#  exit 1
#fi

# 2019/02/16
# Disable blacklisting Dell machine. We can avoid bricking the uEFI BIOS by newer mechanism to get only one uEFI boot label even when multiple OSs exist. Thanks to Dell US & Taiwan, and AMI Taiwan.
#if check_if_dell; then
#  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
#  echo "Due to an issue of efibootmgr on Dell's machine, EFI NVRAM boot entry update was skipped."
#  echo "For more info, please check: https://sourceforge.net/p/clonezilla/bugs/310/"
#  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
#  echo "$msg_program_stop!"
#  exit 1
#fi

echo "Updating boot entry of EFI NVRAM..."
# Check if it meets required kernel and module
if [ ! -d "/sys/firmware/efi/efivars/" ]; then
  [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
  echo "Directory /sys/firmware/efi/efivars/ _NOT_ found!"
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  echo "Please make sure:"
  echo "1. This machine uses UEFI, not legacy BIOS,"
  echo "2. You are using Linux kernel >= 3.9,"
  echo "3. The Linux kernel module efivars is loaded (modprobe efivars)."
  echo "$msg_program_stop!"
fi
esp_no="$(get_efi_system_part ${disk_})"
if [ -n "$esp_no" ]; then
  # Disk name -> partition device name e.g.
  # /dev/sda -> /dev/sda1
  # /dev/cciss/c0d0 -> /dev/cciss/c0d0p1
  # /dev/mmcblk0 -> /dev/mmcblk0p1
  # /dev/nvme0n1 -> /dev/nvme0n1p1
  # /dev/md126 -> /dev/md126p1
  # /dev/loop0 -> /dev/loop0p1
  case "${disk_}" in
      *cciss*|*mmcblk*|*md*|*rd*|*ida*|*nvme*|*nbd*|*loop*)
          efi_system_part="${disk_}p${esp_no}"
          ;;
      *)
          efi_system_part="${disk_}${esp_no}"
          ;;
  esac
  echo "EFI system partition: ${efi_system_part}"
else
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "EFI system partition was _NOT_ found on this hard drive: ${disk_}"
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  echo "$msg_program_stop!"
  exit 2
fi


# Prepare a table for efiboot
# nvram_chklist_table is the list from NVRAM or saved NVRAM file
# nvram_2_update_table is the file really to be used to run efibootmgr
nvram_chklist_table="$(mktemp /tmp/nvram_chklist_table.XXXXXX)"
nvram_2_update_table="$(mktemp /tmp/nvram_2_update_table.XXXXXX)"
echo "# label; uuid; boot_file" > $nvram_chklist_table
echo "# label; uuid; boot_file" > $nvram_2_update_table
label_uuid_boot_f_in_nvram="$(get_efi_hd_boot_entry_info -a -p ';' nvram label uuid boot_file)"
if [ -n "$full_path_to_efi_file" -a \
     -e "$full_path_to_efi_file" ]; then
  # For Windows, sometimes it just gives duplicated ones. We have to unique it, e.g.,
  # BootCurrent: 0001
  # Timeout: 2 seconds
  # BootOrder: 0007,0003,0002,0001,0000,0004,0005,0006
  # Boot0000* EFI VMware Virtual SATA Hard Drive (0.0)	PciRoot(0x0)/Pci(0x11,0x0)/Pci(0x4,0x0)/Sata(0,0,0)
  # Boot0001* EFI VMware Virtual SATA CDROM Drive (1.0)	PciRoot(0x0)/Pci(0x11,0x0)/Pci(0x4,0x0)/Sata(1,0,0)
  # Boot0002* EFI Network	PciRoot(0x0)/Pci(0x16,0x0)/Pci(0x0,0x0)/MAC(000c29144220,0)
  # Boot0003* Windows Boot Manager	HD(2,GPT,a9427f00-e1d1-4bc5-9cd0-e23833e6fe62,0xe1800,0x31800)/File(\EFI\Microsoft\Boot\bootmgfw.efi)
  # Boot0004* EFI VMware Virtual SATA Hard Drive (2.0)	PciRoot(0x0)/Pci(0x11,0x0)/Pci(0x4,0x0)/Sata(2,0,0)
  # Boot0005* EFI VMware Virtual NVME Namespace (NSID 1)	PciRoot(0x0)/Pci(0x18,0x0)/Pci(0x0,0x0)/NVMe(0x1,00-00-00-00-00-00-00-00)
  # Boot0006* EFI VMware Virtual NVME Namespace (NSID 2)	PciRoot(0x0)/Pci(0x18,0x0)/Pci(0x0,0x0)/NVMe(0x2,00-00-00-00-00-00-00-00)
  # Boot0007* Windows Boot Manager	HD(2,GPT,a9427f00-e1d1-4bc5-9cd0-e23833e6fe62,0xe1800,0x31800)/File(\EFI\Microsoft\Boot\bootmgfw.efi)WINDOWS.........x...B.C.D.O.B.J.E.C.T.=.{.9.d.e.a.8.6.2.c.-.5.c.d.d.-.4.e.7.0.-.a.c.c.1.-.f.3.2.b.3.4.4.d.4.7.9.5.}...,................
  get_efi_hd_boot_entry_info -a -p ';' $full_path_to_efi_file label uuid boot_file | sort | uniq >> $nvram_chklist_table
  # Append that from NVRAM if it's not the same as that from saved nvram data (efi-nvram.dat).
  if [ -z "$(comm -12 <(cat "$nvram_chklist_table" | sort) <(echo "$label_uuid_boot_f_in_nvram" | sort))" ]; then
    if [ -n "$label_uuid_boot_f_in_nvram" ]; then
      echo "$label_uuid_boot_f_in_nvram" >> $nvram_chklist_table
    fi
  fi
else
  # Only from NVRAM
  if [ -n "$label_uuid_boot_f_in_nvram" ]; then
    echo "$label_uuid_boot_f_in_nvram" >> $nvram_chklist_table
  fi
fi

#
if [ "$clean_nvram_unused_boot_entry" = "yes" ]; then
  clean_unused_uefi_boot_entry_in_nvram
fi

#
check_if_nvram_efi_boot_entry_exist # input global variable $efi_system_part
ret="$?"
# 0: UUID exists in NVRAM, matching the one in hard drive, no need to update boot entry in NVRAM.
# 2: UUID exists in NVRAM, not matching the one in hard drive, need to update boot entry in NVRAM.
# 4: UUID does not exist in NVRAM, need to update boot entry in NVRAM.
esp_boot_os=""
if [ "$ret" -eq 2 -o "$ret" -eq 4 ]; then
  # 2: UUID exists in NVRAM, not matching the one in hard drive, need to update boot entry in NVRAM.
  # 4: UUID does not exist in NVRAM, need to update boot entry in NVRAM.
  # Updating the boot entry for EFI NVRAM
  check_efi_boot_loader_file # We will get the file $nvram_2_update_table
  while read -r line; do
    [ -n "$(echo "$line" | grep -E "^[[:space:]]*#")" ] && continue
    # Avoid running empty parameter with efibootmgr
    label="$(echo "$line" | awk -F";" '{print $1}')"; label="$(echo $label)"
    [ "$label" = "" ] && continue
    uuid="$(echo "$line" | awk -F";" '{print $2}')"; uuid="$(echo $uuid)"
    esp_boot_f="$(echo "$line" | awk -F";" '{print $3}')"; esp_boot_f="$(echo $esp_boot_f)"
    cmd_update_add_boot_entry="efibootmgr -c -d ${disk_} -p ${esp_no} -L \"$label\" -l \"$esp_boot_f\""
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "Updating/Adding the boot entry in EFI NVRAM by command:"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$cmd_update_add_boot_entry"
    LC_ALL=C eval "(${cmd_update_add_boot_entry} && exit \${PIPESTATUS[0]})"
  done < $nvram_2_update_table
  if [ -n "$efi_boot_file_info" ]; then
    output_efi_boot_info
  fi
  echo $msg_delimiter_star_line
elif [ "$ret" -eq 0 ]; then
  # 0: UUID exists in NVRAM, matching the one in hard drive, no need to update boot entry in NVRAM.
  # EFI system partition UUID in EFI NVRAM matches the one on this system
  if [ -n "$efi_boot_file_info" ]; then
    output_efi_boot_info
  fi
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "No need to update the boot entry of EFI NVRAM."
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  echo $msg_delimiter_star_line
else
  # Unknown mode...
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "Unknown mode. No idea how to update EFI NVRAM for the EFI system partition."
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  echo "$msg_program_stop!"
fi

[ -e "$nvram_chklist_table" ] && rm -f $nvram_chklist_table
[ -e "$nvram_2_update_table" ] && rm -f $nvram_2_update_table
