From: Andrey Borzenkov Subject: fix parsing GRUB2 grub.cfg References: bnc#796919 Fix several problems in parsing of grub.cfg by linux-boot-probes/mounted/40grub2 1. It looked for /boot/grub/grub.cfg only. Make it check for /boot/grub2-efi/grub.cfg, /boot/grub2/grub.cfg, /boot/grub/grub.cfg in that order 2. Parsing of menuentry was completely broken. It is (near to) impossible to parse full fledged shell quoted strings with a couple of sed expressions. Replace it with ``eval "set -- $line"'', which should handle quoting automatically. It still may fail for manually created grub.cfg though. 3. It checked for literal "(on /dev/.*)" to filter out menu entries added by another os-prober on target system. But grub.cfg now includes TRANSLATED strings, so this check will fail if grub.cfg was created in non-English locale. Relax check and make it '(.* /dev/.*)'. It should work as long as grub.cfg was created by grub-mkconfig. Index: os-prober/linux-boot-probes/mounted/common/40grub2 =================================================================== --- os-prober.orig/linux-boot-probes/mounted/common/40grub2 +++ os-prober/linux-boot-probes/mounted/common/40grub2 @@ -34,33 +34,43 @@ parse_grub_menu () { title="" ignore_item=0 - while read line; do + # grub.cfg is written in shell like language that can contain + # arbitrary quoting. We need to extract second word according to + # normal quoting rules. Unfortunately, sticking ``eval $line'' for + # every line will try to interpret it according to shell + # grammar and error out for something as simple as "xxx; then". + # So the following is using it only on known cases. + # FIXME: it will fail if ``menuentry'' is not the first word on line. + # case patterns below include SPACE and TAB. + while read -r line; do debug "parsing: $line" - set -f - set -- $line - set +f - case "$1" in - menuentry) + line="$(printf "%s" "$line" | sed -e 's/^[[:space:]]*//')" + case "$line" in + menuentry[" "]*) entry_result - shift 1 - # The double-quoted string is the title. - title="$(echo "$@" | sed -n 's/[^"]*"\(.*\)".*/\1/p' | sed 's/://g')" - if [ -z "$title" ]; then - # ... or single-quoted? The - # unescaping here is odd because the - # 'set' above has already eaten - # backslash-escapes. - title="$(echo "$@" | sed -n "s/[^']*'\(.*\)'.*/\1/p" | sed "s/'''/'/; s/://g")" - fi + set -f + eval "set -- $line" + set +f + title="$2" if [ -z "$title" ]; then ignore_item=1 - elif echo "$title" | grep -q '(on /dev/[^)]*)$'; then + # Currently GRUB2 puts translated strings + # in grub.cfg, so checking for verbatim + # (on /dev/.*) will fail if target grub.cfg + # was created in non-English locale + elif echo "$title" | grep -q '(.* /dev/[^)]*)$'; then log "Skipping entry '$title':" log "appears to be an automatic reference taken from another menu.lst" ignore_item=1 fi ;; - linux) + linux[" "]*) + # And here we do NOT want to strip off + # existing quting, which will be transferred + # verbatim in new grub.cfg + set -f + set -- $line + set +f # Hack alert: sed off any (hdn,n) but # assume the kernel is on the same # partition. @@ -73,14 +83,17 @@ parse_grub_menu () { kernel="/boot$kernel" fi ;; - initrd) + initrd[" "]*) + # And here we do NOT want to strip off + # existing quting, which will be transferred + # verbatim in new grub.cfg initrd="$(echo "$2" | sed 's/(.*)//')" # Initrd same. if [ "$partition" != "$bootpart" ]; then initrd="/boot$initrd" fi ;; - "}") + "}*") entry_result ;; esac @@ -89,11 +102,20 @@ parse_grub_menu () { entry_result } -if [ -e "$mpoint/boot/grub/grub.cfg" ] && \ +grubcfg= +if [ -e "$mpoint/boot/grub2-efi/grub.cfg" ]; then + grubcfg="$mpoint/boot/grub2-efi/grub.cfg" +elif [ -e "$mpoint/boot/grub2/grub.cfg" ]; then + grubcfg="$mpoint/boot/grub2/grub.cfg" +elif [ -e "$mpoint/boot/grub/grub.cfg" ]; then + grubcfg="$mpoint/boot/grub/grub.cfg" +fi + +if [ -n "$grubcfg" ] && \ ([ ! -e "$mpoint/boot/grub/menu.lst" ] || \ - [ "$mpoint/boot/grub/grub.cfg" -nt "$mpoint/boot/grub/menu.lst" ]); then - debug "parsing grub.cfg" - parse_grub_menu "$mpoint" "$partition" "$bootpart" < "$mpoint/boot/grub/grub.cfg" + [ "$grubcfg" -nt "$mpoint/boot/grub/menu.lst" ]); then + debug "parsing $grubcfg" + parse_grub_menu "$mpoint" "$partition" "$bootpart" < "$grubcfg" fi if [ "$found_item" = 0 ]; then