#!/bin/bash
# Super Grub Disk - supergrub-mkcommon
# Copyright (C) 2009,2010,2011,2012,2013,2014,2015  Adrian Gibanel Lopez.
#
# Super Grub Disk is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Super Grub Disk is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Super Grub Disk.  If not, see <http://www.gnu.org/licenses/>.

function init_overlay_dir () {

  mkdir -p "$overlay/boot/grub/"
  cp -r menus/* "$overlay/boot/grub/"
}

function copy_boot_isos_directory () {

  if [ -d ${BOOT_ISOS_DIRECTORY} ] ; then
    cp -r ${BOOT_ISOS_DIRECTORY} "$overlay/boot/"
  fi

}

function copy_copyright () {

  cp AUTHORS COPYING "$overlay/boot/grub/sgd/"

}

function generate_and_copy_mo_files () {

  # Generate mo files and add them to overlay
  for pofile in po/*.po; do
    basename=${pofile##*/}
    lang_code="${basename%.po}"
    info_cfg="menus/sgd/sgd_locale/${lang_code}_info.cfg"
    msgfmt "$pofile" --output-file="$overlay/boot/grub/sgd/sgd_locale/${lang_code}.mo"
    if ! [[ -f "$info_cfg" ]]; then
      echo "Thank you for providing a new translation for Super GRUB2 Disk!"
      echo "Before this translation can be used in Super GRUB2 Disk we need to know what"
      echo "to call it in our language selection menu."
      echo "We need to know the name of the language you've translated, in that language."
      echo "For example, for Spanish you would enter \"Español\" (without the quotes)."
      echo -n "Please enter the name of the language for $pofile: "
      read language_name
      cp info_cfg_header.cfg "$info_cfg"
      echo "lang_code='${lang_code}'" >> "$info_cfg"

      # This will fail if $language_name contains a single quote character.
      # TODO: Fix aforementioned problem by escaping quotes in $language_name.
      echo "language_name='${language_name}'" >> "$info_cfg"

      echo "A file, ${info_cfg}, was created with this information."
      echo "Thanks again for contributing a new translation!"
      echo; echo
    fi
  done

}

function check_unifont () {
  local unifont_basename="unicode.pf2"
  local unifont_destdir="${overlay}/boot/grub/fonts"
  local unifont_destfile="${unifont_destdir}/${unifont_basename}"
  mkdir -p -- "${unifont_destdir}" || return $?

  local font_dirs="\
  /boot/grub/fonts \
  /usr/share/grub/fonts \
  /usr/share/grub \
  "

  for dir in ${font_dirs}; do
    if [ -e "${dir}/${unifont_basename}" ]; then
      cp -- "${dir}/${unifont_basename}" "${unifont_destdir}"/; return $?
    fi
    if [ -e "${dir}/${unifont_basename}".gz ]; then
      <"${dir}/${unifont_basename}".gz gzip -d > "${unifont_destfile}"; return $?
    fi
  done

  # Find unifont font file to create grub font. This is needed for gfxterm in grub, which
  # in turn is needed for displaying non-ASCII characters for non-English translations.
  # This unifont finding code was copied from grub's configure.ac.
  local font_exts="\
  bdf \
  pcf \
  ttf \
  "

  font_dirs="\
  . \
  /usr/src \
  /usr/share/fonts/X11/misc \
  /usr/share/fonts/unifont \
  /usr/share/fonts/truetype/unifont \
  "

  for n_font_ext in font_exts; do
    for ext in ${n_font_ext} ${n_font_ext}.gz; do
      for dir in ${font_dirs}; do
        if test -f "${dir}/unifont.${ext}"; then
          font_source="${dir}/unifont.${ext}"
          break 3
        fi
      done
    done
  done

  if [[ -n "${font_source}" ]]; then
    "${GRUB_MKFONT_BINARY}" "${font_source}" --output="${unifont_destfile}"
  else
    echo "Error: Unifont not found. Unifont is needed for Super GRUB2 Disk" >&2
    echo "to properly display non-ASCII characters. Aborting without creating an iso." >&2
    exit 1
  fi

}

function generate_filename_hashes ()  {
  local ISO_FILENAME="$1"
  local TMP_PRE_CHECKSUMS_DIR="$(pwd)"
  local SHORT_ISO_FILENAME="$(basename ${ISO_FILENAME})"
  local ISO_FILENAME_DIR="$(dirname ${ISO_FILENAME})"

  cd "${ISO_FILENAME_DIR}"

  md5sum ${SHORT_ISO_FILENAME} > ${SHORT_ISO_FILENAME}.md5
  sha1sum ${SHORT_ISO_FILENAME} > ${SHORT_ISO_FILENAME}.sha1
  sha256sum ${SHORT_ISO_FILENAME} > ${SHORT_ISO_FILENAME}.sha256

  cd "${TMP_PRE_CHECKSUMS_DIR}"

}

function generate_SG2D_UUID_FILE () {

  LIVEID_DIR_PREFIX="LIVEID"

  SG2D_UUID_SEED="\
${ISO_FILENAME}\
-\
${GRUB_MKRESCUE_BINARY}\
-\
${GRUB_MKFONT_BINARY}\
-\
${EFI_PLATFORMS}\
"

  SG2D_UUID=$(echo -n "${SG2D_UUID_SEED}" | md5sum | tr 'a-z' 'A-Z')

  SG2D_UUID_DIR1="$(echo ${SG2D_UUID} | cut -c1-8)"
  SG2D_UUID_DIR2="$(echo ${SG2D_UUID} | cut -c9-16)"
  SG2D_UUID_DIR3="$(echo ${SG2D_UUID} | cut -c17-24)"
  SG2D_UUID_FILE4="$(echo ${SG2D_UUID} | cut -c25-32)"

  SG2D_UUID_DIR="${LIVEID_DIR_PREFIX}/${SG2D_UUID_DIR1}"'/'"${SG2D_UUID_DIR2}"'/'"${SG2D_UUID_DIR3}"
  SGD2_UUID_FILE="${SG2D_UUID_DIR}"'/'"${SG2D_UUID_FILE4}"

  mkdir -p "$overlay/${SG2D_UUID_DIR}/"
  touch "$overlay/${SGD2_UUID_FILE}"

}

function get_vendor_grub_cfg_content () {
  nvendor=$1

  cat <<EOF
set sb_vendor=$nvendor
export sb_vendor
search --set=root --file /${SGD2_UUID_FILE}
set prefix=(\$root)/boot/grub
configfile (\$root)/boot/grub/grub.cfg
EOF

}

function secureboot-sha256sum-check() {
  local nefiplatform="$1"
  local nvendor="$2"
  sha256sum -c ${SG2D_SECUREBOOT_SHA256SUMS_DIR}/${nvendor}-shim${nefiplatform}.efi.sha256 > /dev/null 2>&1 && \
  sha256sum -c ${SG2D_SECUREBOOT_SHA256SUMS_DIR}/${nvendor}-grub${nefiplatform}.efi.sha256 > /dev/null 2>&1 && \
  sha256sum -c ${SG2D_SECUREBOOT_SHA256SUMS_DIR}/${nvendor}-shim${nefiplatform}-sourcecode.tar.gz.sha256 > /dev/null 2>&1 && \
  sha256sum -c ${SG2D_SECUREBOOT_SHA256SUMS_DIR}/${nvendor}-grub${nefiplatform}-sourcecode.tar.gz.sha256 > /dev/null 2>&1
}

function secureboot-sha256sum-create() {
  local nefiplatform="$1"
  local nvendor="$2"
  sha256sum ${SHIM_FULLPATH_BINARY} > ${SG2D_SECUREBOOT_SHA256SUMS_DIR}/${nvendor}-shim${nefiplatform}.efi.sha256
  sha256sum ${GRUB_FULLPATH_BINARY} > ${SG2D_SECUREBOOT_SHA256SUMS_DIR}/${nvendor}-grub${nefiplatform}.efi.sha256
  sha256sum ${SHIM_FULLPATH_SOURCE_CODE_TAR_GZ} > ${SG2D_SECUREBOOT_SHA256SUMS_DIR}/${nvendor}-shim${nefiplatform}-sourcecode.tar.gz.sha256
  sha256sum ${GRUB_FULLPATH_SOURCE_CODE_TAR_GZ} > ${SG2D_SECUREBOOT_SHA256SUMS_DIR}/${nvendor}-grub${nefiplatform}-sourcecode.tar.gz.sha256
}

function download_secureboot_efis () {

  if [ ! -d "${SG2D_SECUREBOOT_BINARIES_DIR}/vendor" ] ; then
    mkdir -p "${SG2D_SECUREBOOT_BINARIES_DIR}/vendor"
  fi

  # TODO: Skip downloading a vendor if all of the sha256sum files matches the binary files.

  PRE_DOWNLOAD_DIR="$(pwd)"

  cd "${SG2D_SECUREBOOT_DOWNLOAD_DIR}"
  for nefiplatform in ${SG2D_SECUREBOOT_EFI_PLATFORMS} ; do
    if [ -d "${nefiplatform}" ] ; then
      cd "${nefiplatform}"
        for nvendor in * ; do
          cd "${nvendor}"
          if [ -x "download-${nefiplatform}-${nvendor}" ] ; then
            if ! secureboot-sha256sum-check ${nefiplatform} ${nvendor} ; then
              echo "[DOWNLOAD-  START] ${nefiplatform} ${nvendor} SecureBoot binaries and source code"
              if [ ! -d "${SG2D_SECUREBOOT_BINARIES_DIR}/vendor/${nvendor}" ] ; then
                mkdir -p "${SG2D_SECUREBOOT_BINARIES_DIR}/vendor/${nvendor}"
              fi
              export SHIM_FULLPATH_BINARY="${SG2D_SECUREBOOT_BINARIES_DIR}/vendor/${nvendor}/shim${nefiplatform}.efi"
              export GRUB_FULLPATH_BINARY="${SG2D_SECUREBOOT_BINARIES_DIR}/vendor/${nvendor}/grub${nefiplatform}.efi"
              export SHIM_FULLPATH_SOURCE_CODE_TAR_GZ="${SG2D_SECUREBOOT_BINARIES_DIR}/vendor/${nvendor}/shim${nefiplatform}-sourcecode.tar.gz"
              export GRUB_FULLPATH_SOURCE_CODE_TAR_GZ="${SG2D_SECUREBOOT_BINARIES_DIR}/vendor/${nvendor}/grub${nefiplatform}-sourcecode.tar.gz"
              if ./"download-${nefiplatform}-${nvendor}"; then
                echo "[DOWNLOAD-     OK] ${nefiplatform} ${nvendor} SecureBoot binaries and source code"
                secureboot-sha256sum-create ${nefiplatform} ${nvendor}
              else
                echo "[DOWNLOAD-FAILURE] ${nefiplatform} ${nvendor} SecureBoot binaries and source code"
              fi
            else
              echo "[SKIP-DOWNLOAD] ${nefiplatform} ${nvendor} SecureBoot binaries and source code"
            fi
          fi
          cd ..
        done
      cd ..
    fi
  done

  cd "${PRE_DOWNLOAD_DIR}"

}

function copy_secureboot_default_efis () {

  # Return if this is not an actual efi platform
  if [ "${EFI_PLATFORMS}" == "none" ] ; then
    return 0;
  fi

  PRE_LOOP_DIR="$(pwd)"
  for nefiplatform in ${EFI_PLATFORMS} ; do
    cd ${SG2D_SECUREBOOT_BINARIES_DIR}/vendor
    for nvendor in ${SG2D_SECUREBOOT_DEFAULT_VENDOR} ; do
      if [ -e "${nvendor}/shim${nefiplatform}.efi" ] ; then
        if [ ! -d "${overlay}/EFI/Boot" ] ; then
          mkdir -p "${overlay}/EFI/Boot"
        fi
        cp "${nvendor}/shim${nefiplatform}.efi" "${overlay}/EFI/Boot/boot${nefiplatform}.efi"
        cp "${nvendor}/grub${nefiplatform}.efi" "${overlay}/EFI/Boot/grub${nefiplatform}.efi"
      else
        echo "You don't have a ${SG2D_SECUREBOOT_DEFAULT_VENDOR} secure-boot binary!"
        echo "This is not going to boot!"
      fi
    done
  done

  cd "${PRE_LOOP_DIR}"


}

function copy_secureboot_external_efis () {

  # Return if this is not an actual efi platform
  if [ "${EFI_PLATFORMS}" == "none" ] ; then
    return 0;
  fi

  PRE_LOOP_DIR="$(pwd)"
  for nefiplatform in ${EFI_PLATFORMS} ; do
    cd ${SG2D_SECUREBOOT_BINARIES_DIR}/vendor
    for nvendor in * ; do
      if [ -e "${nvendor}/shim${nefiplatform}.efi" ] ; then
        if [ ! -d "${overlay}/EFI/${nvendor}" ] ; then
          mkdir -p "${overlay}/EFI/${nvendor}"
        fi
        cp "${nvendor}/shim${nefiplatform}.efi" "${overlay}/EFI/${nvendor}"
        cp "${nvendor}/grub${nefiplatform}.efi" "${overlay}/EFI/${nvendor}"
        get_vendor_grub_cfg_content "${nvendor}" > "${overlay}/EFI/${nvendor}/grub.cfg"
      fi
    done
  done

  cd "${PRE_LOOP_DIR}"


}

function secureboot_cfg_top () {
cat << EOF
# Super Grub Disk - secureboot.cfg
# Copyright (C) 2022  Adrian Gibanel Lopez.
#
# Super Grub Disk is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Super Grub Disk is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Super Grub Disk.  If not, see <http://www.gnu.org/licenses/>.

set option_title="("\$sb_vendor") "\$"Change SecureBoot vendor"

function run_option {
EOF
}

function secureboot_cfg_bottom () {
cat << EOF
}
EOF
}

function generate_secureboot_menus () {

  PRE_LOOP_DIR="$(pwd)"

  for nefiplatform in ${EFI_PLATFORMS} ; do
cat << EOF
  menuentry "\${topbootmenu_prefix_str}EFI Platform: ${nefiplatform}\${topbootmenu_postfix_str}" {
    sleep 1
  }
EOF
    cd ${SG2D_SECUREBOOT_BINARIES_DIR}/vendor
    for nvendor in * ; do
      if [ -e "${nvendor}/shim${nefiplatform}.efi" ] ; then
      vendor_description=$(cat ${SG2D_SECUREBOOT_DOWNLOAD_DIR}/${nefiplatform}/${nvendor}/description | head -n 1)
cat << EOF
    menuentry "\${finaloption_tab_str}${vendor_description} (${nefiplatform})" {
      chainloader (\$sg2d_dev_name)/efi/${nvendor}/shim${nefiplatform}.efi
    }
EOF
      fi
    done
  done

  cd "${PRE_LOOP_DIR}"


}

function generate_secureboot_cfg () {

  secureboot_cfg_top
  generate_secureboot_menus
  secureboot_cfg_bottom

}

function build_secureboot_cfg () {

  # Return if this is not an actual efi platform
  if [ "${EFI_PLATFORMS}" == "none" ] ; then
    return 0;
  fi

  generate_secureboot_cfg > "$overlay/boot/grub/sgd/secureboot.cfg"

  cd "${PRE_LOOP_DIR}"

}

Lodetach ()
{
  DEVICE="${1}"
  ATTEMPT="${2:-1}"

  if [ "${ATTEMPT}" -gt 3 ]
  then
    echo "Failed to detach loop device (${DEVICE})."
    exit 1
  fi

  # Changes to block devices result in uevents which trigger rules which in
  # turn access the loop device (ex. udisks-part-id, blkid) which can cause
  # a race condition. We call 'udevadm settle' to help avoid this.
  if [ -x "$(which udevadm 2>/dev/null)" ]
  then
    udevadm settle
  fi

  # Loop back devices aren't the most reliable when it comes to writes.
  # We sleep and sync for good measure - better than build failure.
  sync
  sleep 1

  /sbin/losetup -d "${DEVICE}" || Lodetach "${DEVICE}" "$(expr ${ATTEMPT} + 1)"
}

function generate_usb_image_mbr () {

  SECUREBOOTIMAGE="$1"
  generate_usb_image mbr ${SECUREBOOTIMAGE}

}

function generate_usb_image_gpt () {

  SECUREBOOTIMAGE="$1"
  generate_usb_image gpt ${SECUREBOOTIMAGE}

}

function generate_usb_image () {

  MODE="$1"
  SECUREBOOTIMAGE="$2"


  # Many code was adapted from binary_hdd and losetup.sh from live-build package
  if [ "${MODE}" == "gpt" ] ; then
    PART0_NUMBER=1
    PART1_NUMBER=2
    PART2_NUMBER=3
  else # mbr
    PART0_NUMBER=0 # Not actually used
    PART1_NUMBER=1
    PART2_NUMBER=2
  fi

  if [ "${SECUREBOOTIMAGE}" == "yes" ] ; then
    # Make sure we have a lot of size for many Secure Boot images
    # and also make sure that final image size is less than 512 MB
    # Apparently you only get 476 MB when you buy a 512 MB usb
    # Let's remove 10 MB from there
    # And let's remove 265 MB from the SG2DISOS partition.
    # 466 - 265 = 201
    # If adding SecureBoot support for a vendor is adding 2.5 MB
    # It means that with 201 MB we can add about 80 of them
    # Good enough number of SecureBoot vendors for now.
    PART1_MINIMAL_DIM="201"
  else # Non secureboot image
    PART1_MINIMAL_DIM="33" # Minimal FAT32 size is about 32 megabytes
  fi

  # Step 1: Prepare the disk
  DU_DIM="$(du -ms ${overlay} | cut -f1)"
  PART0_START=1
  PART0_END=2
  PART1_REAL_DIM="$(expr ${DU_DIM} + ${DU_DIM} \* 3 / 100 + 1)"

  # We want two of thems for iso partition
  # Make sure the minimal size is 256 megabytes
  # else gparted cannot resize such small partitions
  # We finally used 265 megabytes
  PART2_MINIMAL_DIM="265"
  PART2_REAL_DIM=${PART2_MINIMAL_DIM} # No need currently for specific space
  if [ ${PART1_MINIMAL_DIM} -gt ${PART1_REAL_DIM} ] ; then
    PART1_REAL_DIM=${PART1_MINIMAL_DIM}
  fi
  PART1_START=3 # We start at 2 megabyte
  PART1_END=$((${PART1_START} + ${PART1_REAL_DIM}))
  REAL_DIM=$((${PART1_REAL_DIM} + ${PART2_REAL_DIM})) # Add both partitions
  REAL_DIM=$((${REAL_DIM} + 5)) # Add 5 extra megabytes just in case
  PART2_START=$((${PART1_END} + 1)) # One megabyte after first partition ending
  PART2_END=$((${PART2_START} + ${PART2_REAL_DIM})) # Fill requested space
  dd if=/dev/zero of=${ISO_FILENAME} bs=1024k count=0 seek=${REAL_DIM}
  echo "!!! The following error/warning messages can be ignored !!!"
  if [ "${MODE}" == "gpt" ] ; then
    /sbin/parted --script -- ${ISO_FILENAME} \
      mklabel gpt \
      mkpart primary ${PART0_START}mib ${PART0_END}mib \
      set ${PART0_NUMBER} bios_grub on \
      mkpart primary fat32 ${PART1_START}mib ${PART1_END}mib \
      set ${PART1_NUMBER} boot on \
      mkpart primary fat32 ${PART2_START}mib ${PART2_END}mib
  else
    /sbin/parted --script -- ${ISO_FILENAME} \
      mklabel msdos \
      mkpart primary fat32 ${PART1_START}mib ${PART1_END}mib \
      set ${PART1_NUMBER} boot on \
      set ${PART1_NUMBER} lba off \
      mkpart primary fat32 ${PART2_START}mib ${PART2_END}mib
  fi

  # Step 2: Prepare the isos filesystem
  FREELO="$(losetup --show -f -P ${ISO_FILENAME})"
  mkfs.vfat -F 32 -n "SG2DISOS" ${FREELO}p${PART2_NUMBER}
  SG2D_USB_TMP_MOUNT=$(mktemp -d)
  mount ${FREELO}p${PART2_NUMBER} ${SG2D_USB_TMP_MOUNT}
  mkdir -p ${SG2D_USB_TMP_MOUNT}/BOOTISOS
  cat << EOF > ${SG2D_USB_TMP_MOUNT}/BOOTISOS/README.TXT
1) Put here your GNU/Linux distribution isos based on:
loopback.cfg
and Super Grub2 Disk will find them and offer you to boot them.

More information at:
https://www.supergrubdisk.org/wiki/Loopback.cfg

2) You can use gparted or another tool to extend this second partition
to fill your usb device.
You will have to unmount or extract in a saffer manner the device
before running gparted (or similar tool).
EOF
  umount ${SG2D_USB_TMP_MOUNT}
  rmdir ${SG2D_USB_TMP_MOUNT}
  Lodetach ${FREELO}

  # Step 3: Prepare the filesystem
  FREELO="$(losetup --show -f -P ${ISO_FILENAME})"
  mkfs.vfat -F 32 -n "SG2DBOOT" ${FREELO}p${PART1_NUMBER}

  # Step 4: Mount boot partition
  SG2D_USB_TMP_MOUNT=$(mktemp -d)
  mount ${FREELO}p${PART1_NUMBER} ${SG2D_USB_TMP_MOUNT}

  # Step 5: Install hybrid Grub
  mkdir -p ${overlay}/EFI
  mkdir -p ${overlay}/boot/grub
  cat > ${overlay}/boot/grub/device.map <<EOF
(hd0)   ${FREELO}
EOF

  # Force the flush
  umount ${SG2D_USB_TMP_MOUNT}
  mount ${FREELO}p${PART1_NUMBER} ${SG2D_USB_TMP_MOUNT}

  ${GRUB_INSTALL_BINARY} --no-floppy --target=i386-efi --no-nvram --removable --efi-directory=${SG2D_USB_TMP_MOUNT} --grub-mkdevicemap=${overlay}/boot/grub/device.map --boot-directory="${SG2D_USB_TMP_MOUNT}/boot" --themes='' ${FREELO}
  ${GRUB_INSTALL_BINARY} --no-floppy --target=x86_64-efi --no-nvram --removable --efi-directory=${SG2D_USB_TMP_MOUNT} --grub-mkdevicemap=${overlay}/boot/grub/device.map --boot-directory="${SG2D_USB_TMP_MOUNT}/boot" --themes='' ${FREELO}
  ${GRUB_INSTALL_BINARY} --no-floppy --target=i386-pc --grub-mkdevicemap=${overlay}/boot/grub/device.map --boot-directory="${SG2D_USB_TMP_MOUNT}/boot" --themes='' ${FREELO}

  # Step 6: Copy SG2D contents
  cp -T -r ${overlay}/ ${SG2D_USB_TMP_MOUNT}

  # Step 7: Umount filesystem and detach device
  umount ${SG2D_USB_TMP_MOUNT}
  rmdir ${SG2D_USB_TMP_MOUNT}
  Lodetach ${FREELO}

}
