#! /bin/sh
set -e

# grub-mkconfig helper script.
# Copyright (C) 2006,2007,2008,2009  Free Software Foundation, Inc.
#
# GRUB 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.
#
# GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.

prefix="/usr"
exec_prefix="/usr"
datarootdir="/usr/share"

export TEXTDOMAIN=grub
export TEXTDOMAINDIR="${datarootdir}/locale"

. "$pkgdatadir/grub-mkconfig_lib"

if [ "x${GRUB_DISABLE_OS_PROBER}" = "xtrue" ]; then
  grub_warn "$(gettext_printf "os-prober will not be executed to detect other bootable partitions.\nSystems on them will not be added to the GRUB boot configuration.\nCheck GRUB_DISABLE_OS_PROBER documentation entry.")"
  exit 0
fi

if ! command -v os-prober > /dev/null || ! command -v linux-boot-prober > /dev/null ; then
  # missing os-prober and/or linux-boot-prober
  exit 0
fi

grub_warn "$(gettext_printf "os-prober will be executed to detect other bootable partitions.\nIts output will be used to detect bootable binaries on them and create new boot entries.")"

OSPROBED="`os-prober | tr ' ' '^' | paste -s -d ' '`"
if [ -z "${OSPROBED}" ] ; then
  # empty os-prober output, nothing doing
  exit 0
fi

add_options() {
  if [ "x$GRUB_GFXPAYLOAD_LINUX" != xtext ]; then
    echo "        load_video" | sed "s/^/$submenu_indentation/"
  fi
  echo "        set gfxpayload=$GRUB_GFXPAYLOAD_LINUX" | sed "s/^/$submenu_indentation/"
  echo "        insmod gzio" | sed "s/^/$submenu_indentation/"
}

osx_entry() {
    # TRANSLATORS: it refers on the OS residing on device %s
    onstr="$(gettext_printf "(on %s)" "${DEVICE}")"
    hints=""
    for hint in `"${grub_probe}" --device ${device} --target=efi_hints 2> /dev/null` ; do
      hints="${hints} --hint=${hint}"
    done
    cat << EOF
menuentry ${unrestricted}'$(echo "${LONGNAME} $onstr" | grub_quote)' --class osx --class darwin --class os \$menuentry_id_option 'osprober-xnu-$2-$(grub_get_device_id "${DEVICE}")'  {
EOF
	save_default_entry | grub_add_tab
	prepare_grub_to_access_device ${DEVICE} | grub_add_tab
	cat << EOF
	set gfxpayload=keep
        load_video
	insmod part_gpt
	insmod hfsplus
	search --no-floppy --fs-uuid --set=root ${hints} $(grub_get_device_id "${DEVICE}")
	chainloader (\$root)/System/Library/CoreServices/boot.efi
	boot
}
EOF
}

used_osprober_linux_ids=
declare -A common_boot_distro_names
declare -A common_boot_distro_entries

if [ "x$GRUB_TOP_LEVEL_OS_PROBER" != x ]; then
  OSPROBED=$(grub_move_to_front "$GRUB_TOP_LEVEL_OS_PROBER" ${OSPROBED})
fi

for OS in ${OSPROBED} ; do
  DEVICE="`echo ${OS} | cut -d ':' -f 1`"
  LONGNAME="`echo ${OS} | cut -d ':' -f 2 | tr '^' ' '`"
  LABEL="`echo ${OS} | cut -d ':' -f 3 | tr '^' ' '`"
  BOOT="`echo ${OS} | cut -d ':' -f 4`"
  if UUID="`${grub_probe} --target=fs_uuid --device ${DEVICE%@*}`"; then
    EXPUUID="$UUID"

    if [ x"${DEVICE#*@}" != x ] ; then
      EXPUUID="${EXPUUID}@${DEVICE#*@}"
    fi

    if [ "x${GRUB_OS_PROBER_SKIP_LIST}" != "x" ] && [ "x`echo ${GRUB_OS_PROBER_SKIP_LIST} | grep -i -e '\b'${EXPUUID}'\b'`" != "x" ] ; then
      echo "Skipped ${LONGNAME} on ${DEVICE} by user request." >&2
      continue
    fi
  fi

  BTRFS="`echo ${OS} | cut -d ':' -f 5`"
  if [ "x$BTRFS" = "xbtrfs" ]; then
	BTRFSuuid="`echo ${OS} | cut -d ':' -f 6`"
	BTRFSsubvol="`echo ${OS} | cut -d ':' -f 7`"
  fi

  if [ -z "${LONGNAME}" ] ; then
    LONGNAME="${LABEL}"
  fi

  # os-prober returns text string followed by optional counter
  CLASS="--class $(echo "${LABEL}" | LC_ALL=C sed 's,[[:digit:]]*$,,' | cut -d' ' -f1 | tr 'A-Z' 'a-z' | LC_ALL=C sed 's,[^[:alnum:]_],_,g')"

  gettext_printf "Found %s on %s\n" "${LONGNAME}" "${DEVICE}" >&2

  case ${BOOT} in
    chain)

	  onstr="$(gettext_printf "(on %s)" "${DEVICE#/dev/}")"
      cat << EOF
menuentry ${unrestricted}'$(echo "${LONGNAME} $onstr" | grub_quote)' $CLASS --class os \$menuentry_id_option 'osprober-chain-$(grub_get_device_id "${DEVICE}")' {
EOF
      save_default_entry | grub_add_tab
      prepare_grub_to_access_device ${DEVICE} | grub_add_tab

      if [ x"`${grub_probe} --device ${DEVICE} --target=partmap`" = xmsdos ]; then
	  cat << EOF
	parttool \${root} hidden-
EOF
      fi

      case ${LONGNAME} in
	Windows\ Vista*|Windows\ 7*|Windows\ Server\ 2008*)
	;;
	*)
	  cat << EOF
	drivemap -s (hd0) \${root}
EOF
	;;
      esac

      cat <<EOF
	chainloader +1
}
EOF
    ;;
    efi)

	EFIPATH=${DEVICE#*@}
	DEVICE=${DEVICE%@*}
	onstr="$(gettext_printf "(on %s)" "${DEVICE}")"
      cat << EOF
menuentry ${unrestricted}'$(echo "${LONGNAME} $onstr" | grub_quote)' $CLASS --class os \$menuentry_id_option 'osprober-efi-$(grub_get_device_id "${DEVICE}")' {
EOF
      save_default_entry | sed -e "s/^/\t/"
      prepare_grub_to_access_device ${DEVICE} | sed -e "s/^/\t/"

      cat <<EOF
	chainloader ${EFIPATH}
}
EOF
    ;;
    linux)
      if [ "x$BTRFS" = "xbtrfs" ]; then
         LINUXPROBED="`linux-boot-prober btrfs ${BTRFSuuid} ${BTRFSsubvol}  2> /dev/null | tr ' ' '^' | paste -s -d ' '`"
      else
         LINUXPROBED="`linux-boot-prober ${DEVICE} 2> /dev/null | tr ' ' '^' | paste -s -d ' '`"
      fi
      prepare_boot_cache=
      boot_device_id=
      is_top_level=true
      title_correction_code=
      onstr="$(gettext_printf "(on %s)" "${DEVICE#/dev/}")"
      # List of items with menuentry names removed (to keep track of identical items)
      linux_unique_ids="`echo \" ${LINUXPROBED}\" | sed 's/ \([^:]*:[^:]*:\)[^:]*:/ \1:/g'`"
      OS="${LONGNAME}"

      for LINUX in ${LINUXPROBED} ; do
        LROOT="`echo ${LINUX} | cut -d ':' -f 1`"
        LBOOT="`echo ${LINUX} | cut -d ':' -f 2`"
        LLABEL="`echo ${LINUX} | cut -d ':' -f 3 | tr '^' ' '`"
        LKERNEL="`echo ${LINUX} | cut -d ':' -f 4`"
        LINITRD="`echo ${LINUX} | cut -d ':' -f 5 | tr '^' ' '`"
        LPARAMS="`echo ${LINUX} | cut -d ':' -f 6- | tr '^' ' '`"

	if [ "${LBOOT::6}" = "grub2-" ]; then
	  # Special case:
	  # /dev/sda5:grub2-/dev/sda5::/boot/grub2/grub.cfg
	  # means that instead of extracting boot entries, a link to config file should be created.
	  # LKERNEL is path to the target grub.cfg
	  ENTRYLINK=true
	  LBOOT="${LBOOT#grub2-}"
	else
	  ENTRYLINK=false
	fi

        if [ -z "${LLABEL}" ] ; then
          LLABEL="${LONGNAME}"
        fi

	if [ "${LROOT}" != "${LBOOT}" ]; then
	  LKERNEL="${LKERNEL#/boot}"
	  LINITRD="${LINITRD#/boot}"
	fi

	recovery_params="$(echo "${LPARAMS}" | grep single)" || true
	counter=1
	while echo "$used_osprober_linux_ids" | grep 'osprober-gnulinux-$LKERNEL-${recovery_params}-$counter-$boot_device_id' > /dev/null; do
	    counter=$((counter+1));
	done
	if [ -z "$boot_device_id" ]; then
	    boot_device_id="$(grub_get_device_id "${DEVICE}")"
	fi
	used_osprober_linux_ids="$used_osprober_linux_ids 'osprober-gnulinux-$LKERNEL-${recovery_params}-$counter-$boot_device_id'"

	if [ -z "${prepare_boot_cache}" ]; then
	  prepare_boot_cache="$(prepare_grub_to_access_device ${LBOOT} | grub_add_tab)"
	fi

	case ${LLABEL} in
		"")
			# Entry name not found - using OS name from os-prober
			# Advanced subentries will also be suffixed by kernel version
			title="${LONGNAME}"
			title_append_vkernel=true
			;;
		linux)
			# Main Grub-legacy entry named simply "linux" - real name is substituted in runtime
			# We cannot get it, so using OS name from os-prober instead
			title="${LONGNAME}"
			;;
		linux-nonfb)
			# Another generic name from Grub-legacy (no frame buffer)
			title="${LONGNAME} - nonfb"
			;;
		failsafe)
			# And one more (failsafe mode)
			title="${LONGNAME} - failsafe"
			;;
		*)
			# Using all other names as is
			title="${LLABEL}"
			;;
	esac

	if [ "x$ENTRYLINK" = "xtrue" ]; then
	  # Short title consists of the first one or two words of the full title, the second word is used only when it contains no digits.
	  # Examples: ROSA Desktop Fresh R5 -> ROSA Desktop; Fedora 21 -> Fedora; SomeLinux X8 -> SomeLinux
	  title_short="$(echo ${title} | perl -wpe 's/^(\S+(\s+[a-z_]+\b)?).*$/$1/is')"

	  # The most part of the menuentry contents
	  menuentry_body="${CLASS} --class gnu-linux --class gnu --class os \$menuentry_id_option 'osprober-gnulinux-simple-link-$boot_device_id' {
$(save_default_entry | sed -e "s/^/\t/"; add_options; printf '%s\n' "${prepare_boot_cache}")
	configfile ${LKERNEL}
}"

	  if [ "${LROOT}" != "${LBOOT}" ]; then
	    # If separate /boot, collect all entries with the same /boot and combine them into single entry:
	    # they all would point to the same grub.cfg, so no need to duplicate them.
	    # Using short titles (to fit them all into limited width) and specify the root partition for each system.
	    # Actual output will be performed after os-prober cycle is finished.
	    if [ -z "${common_boot_distro_names[${LBOOT}]}" ]; then
	      common_boot_distro_names[${LBOOT}]="${LBOOT#/dev/}: ${title_short} (${LROOT#/dev/})"
	      common_boot_distro_entries[${LBOOT}]="${menuentry_body}"
	    else
	      common_boot_distro_names[${LBOOT}]="${common_boot_distro_names[${LBOOT}]}, ${title_short} (${LROOT#/dev/})"
	    fi
	  else
	    # If /boot is a directory within the root partition, just output the resulting entry using full title,
	    # there are no other systems.
	    echo "menuentry '$(echo "${LBOOT#/dev/}: ${title}" | grub_quote)' ${menuentry_body}"
	  fi
	  # In any case, skip the rest of the processing.
	  continue
	fi

	VKERNEL=`echo $LKERNEL | sed -e 's,^\(/boot\)\?\(/vmlinuz-\|/vmlinux-\|/kernel-\),,'`
	message_kernel="$(gettext_printf "Loading Linux %s ..." ${VKERNEL})"
	message_initrd="$(gettext_printf "Loading initial ramdisk ...")"

	# The GRUB_DISABLE_SUBMENU option used to be different than others since it was
	# mentioned in the documentation that has to be set to 'y' instead of 'true' to
	# enable it. This caused a lot of confusion to users that set the option to 'y',
	# 'yes' or 'true'. This was fixed but all of these values must be supported now.
	if [ "x${GRUB_DISABLE_SUBMENU}" = xyes ] || [ "x${GRUB_DISABLE_SUBMENU}" = xy ]; then
	    GRUB_DISABLE_SUBMENU="true"
	fi

	if [ "x$is_top_level" = xtrue ] && [ "x${GRUB_DISABLE_SUBMENU}" != xtrue ]; then
            cat << EOF
menuentry ${unrestricted}'$(echo "$OS $onstr" | grub_quote)' $CLASS --class gnu-linux --class gnu --class os \$menuentry_id_option 'osprober-gnulinux-simple-$boot_device_id' {
EOF
	    save_default_entry | grub_add_tab
	    add_options
	    printf '%s\n' "${prepare_boot_cache}"
	    cat <<  EOF
	echo '$message_kernel'
	linux ${LKERNEL} ${LPARAMS}
EOF
            if [ -n "${LINITRD}" ] ; then
          cat << EOF
	echo '$message_initrd'
	initrd ${LINITRD}
EOF
            fi
        cat << EOF
}
EOF
	    echo "submenu '$(gettext_printf "Advanced options for %s" "${title} $onstr" | grub_quote)' \$menuentry_id_option 'osprober-gnulinux-advanced-$boot_device_id' {"
	    is_top_level=false

	    # If there are other entries in the list with absolutely the same kernel, parameters and initrd
	    # we don't duplicate this main item in the Advanced submenu: it will appear there anyway only with
	    # different title, and all chances are, this other title will be better suited (since it most probably
	    # came from the Advanced submebu of the target system's Grub2 menu).
	    unique_id="`echo ${LINUX} | sed 's/^\([^:]*:[^:]*:\)[^:]*:/\1:/'`"
	    if ((`echo "${linux_unique_ids}" | grep -Fo "${unique_id}" | wc -l` > 1)); then
	      continue
	    fi
	fi
	if [ "x$title_append_vkernel" = xtrue ]; then
		title="${title} - Linux ${VKERNEL}"
	fi

        cat << EOF
	menuentry '$(echo "$title $onstr" | grub_quote)' --class gnu-linux --class gnu --class os \$menuentry_id_option 'osprober-gnulinux-$LKERNEL-${recovery_params}-$boot_device_id' {
EOF
	save_default_entry | sed -e "s/^/$grub_tab$grub_tab/"
	add_options
	printf '%s\n' "${prepare_boot_cache}" | grub_add_tab
	cat <<  EOF
		echo '$message_kernel'
		linux ${LKERNEL} ${LPARAMS}
EOF
        if [ -n "${LINITRD}" ] ; then
            cat << EOF
		echo '$message_initrd'
		initrd ${LINITRD}
EOF
        fi
        cat << EOF
	}
EOF
	if [ x"$title" = x"$GRUB_ACTUAL_DEFAULT" ] || [ x"Previous Linux versions>$title" = x"$GRUB_ACTUAL_DEFAULT" ]; then
	    replacement_title="$(echo "Advanced options for ${LONGNAME} $onstr" | sed 's,>,>>,g')>$(echo "$title" | sed 's,>,>>,g')"
	    quoted="$(echo "$GRUB_ACTUAL_DEFAULT" | grub_quote)"
	    title_correction_code="${title_correction_code}if [ \"x\$default\" = '$quoted' ]; then default='$(echo "$replacement_title" | grub_quote)'; fi;"
	    grub_warn "$(gettext_printf "Please don't use old title \`%s' for GRUB_DEFAULT, use \`%s' (for versions before 2.00) or \`%s' (for 2.00 or later)" "$GRUB_ACTUAL_DEFAULT" "$replacement_title" "gnulinux-advanced-$boot_device_id>gnulinux-$version-$type-$boot_device_id")"
	fi
      done
      if [ x"$is_top_level" != xtrue ]; then
	  echo '}'
      fi
      echo "$title_correction_code"
    ;;
    macosx)
      for subdevice in ${DEVICE%[[:digit:]]*}* ; do
	parttype="`"${grub_probe}" --device ${device} --target=gpt_parttype "${subdevice}" 2> /dev/null`"
	if [[ "$parttype" = "426f6f74-0000-11aa-aa11-00306543ecac" ]]; then
	  DEVICE="${subdevice}" osx_entry
	fi
      done
    ;;
    hurd)
      onstr="$(gettext_printf "(on %s)" "${DEVICE#/dev/}")"
      cat << EOF
menuentry ${unrestricted}'$(echo "${LONGNAME} $onstr" | grub_quote)' --class hurd --class gnu --class os \$menuentry_id_option 'osprober-gnuhurd-/boot/gnumach.gz-false-$(grub_get_device_id "${DEVICE}")' {
EOF
      save_default_entry | grub_add_tab
      prepare_grub_to_access_device ${DEVICE} | grub_add_tab
      grub_device="`${grub_probe} --device ${DEVICE} --target=drive`"
      mach_device="`echo "${grub_device}" | sed -e 's/(\(hd.*\),msdos\(.*\))/\1s\2/'`"
      grub_fs="`${grub_probe} --device ${DEVICE} --target=fs`"
      case "${grub_fs}" in
	*fs)	hurd_fs="${grub_fs}" ;;
	*)	hurd_fs="${grub_fs}fs" ;;
      esac
      cat << EOF
	multiboot /boot/gnumach.gz root=device:${mach_device}
	module /hurd/${hurd_fs}.static ${hurd_fs} --readonly \\
			--multiboot-command-line='\${kernel-command-line}' \\
			--host-priv-port='\${host-port}' \\
			--device-master-port='\${device-port}' \\
			--exec-server-task='\${exec-task}' -T typed '\${root}' \\
			'\$(task-create)' '\$(task-resume)'
	module /lib/ld.so.1 exec /hurd/exec '\$(exec-task=task-create)'
}
EOF
    ;;
    minix)
	  cat << EOF
menuentry ${unrestricted}"${LONGNAME} (on ${DEVICE}, Multiboot)" {
EOF
         save_default_entry | sed -e "s/^/\t/"
         prepare_grub_to_access_device ${DEVICE} | sed -e "s/^/\t/"
	 cat << EOF
	multiboot /boot/image_latest
}
EOF
    ;;
    *)
      case ${DEVICE} in
	*.efi)
	  cat << EOF
menuentry ${unrestricted}'$(echo "${LONGNAME}" | grub_quote)' {
EOF
	  save_default_entry | grub_add_tab
	  cat << EOF
	  chainloader /EFI/${DEVICE}
	  boot
}
EOF
	  ;;
	*)
          echo -n "  "
          # TRANSLATORS: %s is replaced by OS name.
          gettext_printf "%s is not yet supported by grub-mkconfig.\n" "${LONGNAME}" >&2
        ;;
      esac
  esac
done

# Now output entries for systems with separate /boot.
for LBOOT in "${!common_boot_distro_names[@]}"; do
  echo "menuentry '$(echo "${common_boot_distro_names[${LBOOT}]}" | grub_quote)' ${common_boot_distro_entries[${LBOOT}]}"
done
