# get out of POSIX mode in case we are running bash
unset POSIXLY_CORRECT

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# shellquote STRING
#
# Quote STRING so that STRING == $(echo "STRING")
#
lib_shellquote () {
  arg=${1//\\/\\\\}
  arg=${arg//\$/\\\$}
  arg=${arg//\"/\\\"}
  arg=${arg//\`/\\\`}
  echo -n "$arg"
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# lib_set_config FILE KEY VALUE
#
# Set config valiable KEY in FILE to VALUE.
#
# Note that KEY must already exist.
#
lib_set_config ()
{
  err=0

  if [ -w "$1" ] ; then
    sed -i -E -e "s/^($2=)\S+/\1\"$3\"/" "$1"
    err=$?
  else
    echo "$1: not writable" >&2
    err=1
  fi

  return "$err"
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# lib_get_cmdline_grub
#
# Set $cmdline to default grub cmdline.
#
lib_get_cmdline_grub ()
{
  if [ -e /etc/default/grub ] ; then
    . /etc/default/grub
    if [ -z "$GRUB_CMDLINE_LINUX_DEFAULT" ] ; then
      echo "Warning: GRUB_CMDLINE_LINUX_DEFAULT is empty"
    fi
  else
    echo "/etc/default/grub missing"
    exit 1
  fi

  cmdline="$GRUB_CMDLINE_LINUX_DEFAULT"
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# lib_set_cmdline_grub
#
# Set default grub cmdline to $cmdline.
#
lib_set_cmdline_grub ()
{
  tmp_cmdline=${cmdline//\\/\\\\\\\\}
  tmp_cmdline=${tmp_cmdline//\"/\\\\\"}

  sed -i -e "/^GRUB_CMDLINE_LINUX_DEFAULT=/c GRUB_CMDLINE_LINUX_DEFAULT=\"$tmp_cmdline\"" /etc/default/grub || {
    echo "/etc/default/grub: failed to update config file"
    exit 1
  }
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# lib_get_cmdline_bls
#
# Set $cmdline to default bls cmdline.
#
lib_get_cmdline_bls ()
{
  if [ -n "$KERNEL_INSTALL_CONF_ROOT" ] ; then
    cmdline_file="$KERNEL_INSTALL_CONF_ROOT/cmdline"
  elif [ -f /etc/kernel/cmdline ] ; then
    cmdline_file=/etc/kernel/cmdline
  elif [ -f /usr/lib/kernel/cmdline ] ; then
    cmdline_file=/usr/lib/kernel/cmdline
  else
    cmdline_file=/proc/cmdline
  fi

  if [ -f "$cmdline_file" ] ; then
    cmdline=$(cat $cmdline_file)

    lib_del_option BOOT_IMAGE
    lib_del_option initrd
    lib_del_option systemd.machine_id
  else
    cmdline=
  fi
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# lib_set_cmdline_bls
#
# Set default bls cmdline to $cmdline.
#
lib_set_cmdline_bls ()
{
  echo "$cmdline" > /etc/kernel/cmdline || {
    echo "/etc/kernel/cmdline: failed to update default kernel command"
    exit 1
  }
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# lib_split_cmdline
#
# Parse $cmdline and put '\n' between options. This takes quoted (with '"')
# options into account.
#
# The result is stored in $n.
#
# Set $IFS to '\n' and store original $IFS in $ifs_orig.
#
lib_split_cmdline ()
{
  ifs_orig="$IFS" IFS=

  nl=$'\n'
  q=0
  n=
  while read -r -n 1 foo ; do
    if [ "$q" = 0 ] ; then
      [ "$foo" = '"' ] && q=1
    else
      [ "$foo" = '"' ] && q=0
    fi
    [ "$foo" = " " -a "$q" = 0 ] && foo="$nl"
    n="$n$foo"
  done <<XXX
  $cmdline
XXX

  IFS="$nl"
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# lib_add_option
#
# Add option OPTION to $cmdline.
#
# OPTION is either of the form 'key=value' or 'key="value"' or just 'key'.
#
lib_add_option ()
{
  tmp_opt="$1"

  opt_key=${tmp_opt%%=*}

  lib_split_cmdline

  changed=
  new_cmdline=
  for i in $n ; do
    match="${i#"$opt_key"}"
    if [ "$match" != "$i" -a \( -z "$match" -o "${match:0:1}" = "=" \) ] ; then
      if [ -z "$changed" ] ; then
        new_cmdline="$new_cmdline $tmp_opt"
        changed=1
      fi
    else
      new_cmdline="$new_cmdline $i"
    fi
  done

  if [ -z "$changed" ] ; then
    new_cmdline="$new_cmdline $tmp_opt"
  fi

  IFS="$ifs_orig"

  cmdline=${new_cmdline# }
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# lib_del_option OPTION
#
# Delete option OPTION from $cmdline.
#
# Set $changed to 1 if $cmdline was updated.
#
# OPTION is either of the form 'key=value' or 'key="value"' or just 'key'.
#
lib_del_option ()
{
  tmp_opt="$1"

  lib_split_cmdline

  changed=
  new_cmdline=
  for i in $n ; do
    match="${i#"$tmp_opt"}"
    if [ "$match" != "$i" -a \( -z "$match" -o "${match:0:1}" = "=" \) ] ; then
      changed=1
    else
      new_cmdline="$new_cmdline $i"
    fi
  done

  IFS="$ifs_orig"

  cmdline=${new_cmdline# }
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# lib_get_option OPTION
#
# Get option OPTION from $cmdline.
#
# Set $matched to contain option from cmdline in key=value form.
#
# OPTION is either of the form 'key=value' or 'key="value"' or just 'key'.
#
lib_get_option ()
{
  tmp_opt="$1"

  lib_split_cmdline

  matched=
  for i in $n ; do
    match="${i#"$tmp_opt"}"
    if [ "$match" != "$i" -a \( -z "$match" -o "${match:0:1}" = "=" \) ] ; then
      matched="$i"
    fi
  done

  IFS="$ifs_orig"
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# lib_get_efi_mount_dir
#
# Set $efi_mount_dir to mount point of EFI System Partition.
#
lib_get_efi_mount_dir ()
{
  efi_mount_dir="/boot/efi"
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# lib_get_efi_distro_dir
#
# Set $efi_distro_dir to distro-specific EFI directory component.
#
# EFI boot files are expected to be installed into /EFI/$lib_efi_distro_dir
# on the EFI System Partition.
#
# Set to empty string if it can't be determined.
#
lib_get_efi_distro_dir ()
{
  if [ "$SYS__BOOTLOADER__LOADER_TYPE" = systemd-boot ] ; then
    efi_distro_dir=systemd
  else
    efi_distro_dir=$(. /etc/os-release 2>/dev/null && echo "$NAME $VERSION" | tr "[:upper:]" "[:lower:]" | cut -d ' ' -f 1)
  fi
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# lib_get_efi_boot_order
#
# Set $efi_boot_order to the current BootOrder as reported by efibootmgr.
#
lib_get_efi_boot_order ()
{
  efi_boot_order=$(/usr/sbin/efibootmgr 2>/dev/null | grep -m 1 "^BootOrder:" | cut -d ' ' -f 2)
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# lib_get_efi_distro_entry
#
# Set $efi_distro_entry (combined) and $efi_distro_entry_num,
# $efi_distro_entry_file, $efi_distro_entry_name (split into parts) to the
# relevant EFI boot manager entry.
#
# Set $efi_distro_entry to emtpy string if no entry matched.
#
# $efi_distro_entry_* are the parsed sub-components of $efi_distro_entry for convenience.
#
# Prefers 'shim' entry over non-shim entries.
#
lib_get_efi_distro_entry ()
{
  lib_get_efi_distro_dir

  if [ -n "$efi_distro_dir" ] ; then
    efi_distro_entry=$(efibootmgr --verbose | tr '\\' '/' | grep -Ei "^boot.*/file\(/efi/$efi_distro_dir" | sed -E -e 's/^Boot(....)\*? (.*)\t\S+\/File\(\/([^)]*)\)$/\1 \3 \2/')
    while read -r tmp_efi_num tmp_efi_file tmp_efi_name ; do
      if [ \( "$tmp_efi_file" != "${tmp_efi_file/shim/}" \) -o \( "$tmp_efi_file" != "${tmp_efi_file/SHIM/}" \) ] ; then
        tmp_secure_efi_entry="$tmp_efi_num $tmp_efi_file $tmp_efi_name"
      else
        tmp_efi_entry="$tmp_efi_num $tmp_efi_file $tmp_efi_name"
      fi
    # note: can't use '<<<' - not supported in ash
    done < <(echo "$efi_distro_entry")
  fi

  if [ -n "$tmp_secure_efi_entry" ] ; then
    efi_distro_entry="$tmp_secure_efi_entry"
  else
    efi_distro_entry="$tmp_efi_entry"
  fi

  # note: can't use '<<<' - not supported in ash
  read -r efi_distro_entry_num efi_distro_entry_file efi_distro_entry_name < <(echo "$efi_distro_entry")
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# lib_efi_log_boot_entry
#
# Log currently managed EFI boot manager entry.
#
lib_efi_log_boot_entry ()
{
  lib_get_efi_distro_dir
  lib_get_efi_boot_order
  lib_get_efi_distro_entry

  echo "efi_distro_dir=$efi_distro_dir"
  echo "efi_order=$efi_boot_order"
  echo "efi_entry.num=$efi_distro_entry_num"
  echo "efi_entry.file=$efi_distro_entry_file"
  echo "efi_entry.name=$efi_distro_entry_name"
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# lib_get_default_kernel_initrd
#
# Set $default_kernel and $default_initrd to default kernel image and initrd, resp.
#
lib_get_default_kernel_initrd ()
{
  default_kernel=
  default_initrd=

  tmp_target=$(uname -m)

  case "$tmp_target" in
    x86_64|i?86) tmp_image=vmlinuz ;;
    ppc*) tmp_image=vmlinux ;;
    s390*) tmp_image=image ;;
    armv*) tmp_image=zImage ;;
    aarch64|riscv64) tmp_image=Image ;;
    *) echo "architecture $tmp_target not supported" ; exit 1
  esac

  # the default values, also in case nothing is found
  default_kernel=$(readlink -f "/boot/$tmp_image")
  default_initrd=$(readlink -f "/boot/initrd")

  # there is a "/boot/<KERNEL>" symlink
  if [ -e "$default_kernel" ] ; then
    return
  fi

  tmp_kernel_version=$(uname -r)

  # try "/boot/<KERNEL-CURRENT_KERNEL_VERSION>"
  if [ -e "/boot/$tmp_image-$tmp_kernel_version" ] ; then
    default_kernel=$(readlink -f "/boot/$tmp_image-$tmp_kernel_version")
    default_initrd=$(readlink -f "/boot/initrd-$tmp_kernel_version")
    return
  fi

  # try "/usr/lib/modules/<CURRENT_KERNEL_VERSION>/KERNEL"
  if [ -e "/usr/lib/modules/$tmp_kernel_version/$tmp_image" ] ; then
    default_kernel=$(readlink -f "/usr/lib/modules/$tmp_kernel_version/$tmp_image")
    default_initrd=$(readlink -f "/boot/initrd-$tmp_kernel_version")
    return
  fi

  # try newest (according to file date) "/boot/<KERNEL-VERSION>" entry
  for tmp_kernel in $(ls -t /boot/$tmp_image-* 2>/dev/null) ; do
    tmp_kernel_version=${tmp_kernel#*-}
    default_kernel=$(readlink -f "/boot/$tmp_image-$tmp_kernel_version")
    default_initrd=$(readlink -f "/boot/initrd-$tmp_kernel_version")
    return
  done
}
