updategrub for openSUSE Legacy Grub (not update-grub!)

updategrub is a script which aims to solve all your boot problems … lol!

As I just advised somebody to try it, I think I have to post the code now, so that users and others can have a look at it.

More seriously, it works similarly to Grub2’s update-grub under Ubuntu, using the same helper scripts : os-prober and linux-bootprober. I don’t know why os-prober wasn’t available for openSUSE. So I packaged it from a Fedora/ArchLinux port (which in turn ported the original Ubuntu/Debian script). os-prober will be installed as dependency when you install updategrub from my repo.

Like update-grub, updategrub allows to append custom boot entries written in /etc/updategrub/custom (Ubuntu uses /etc/grub.d/40_custom). This file doesn’t exist by default. It has a couple options that can be turned on/off in /etc/updategrub/defaults. This file is installed with defaults values. The options are explained in details in /usr/share/doc/packages/updategrub/README.

As a consequence of installing os-prober, Grub2 users under openSUSE will be able to update Grub2 menu entries as well (assuming they use Grub2 under openSUSE). I added such an option, although updategrub isn’t originally intended to work with Grub2.

If - as I read many times - the advantage of Grub2 over Legacy Grub is that it can update Grub menu boot entries:

  • it wasn’t entirely true since it is not the job of Grub2 but the fact of os-prober/linux-bootprober
  • it might not be true anymore since updategrub does it better (I hope so) and with more features.

updategrub will be installed in /usr/bin and is actually a symlink to /usr/bin/updateLegacyGrub (to avoid future conflicts ?).
I also added the old good findgrub and cfindgrub scripts to the updategrub package. :wink:

Installation from repo:

su -l
zypper ar [noparse]http://download.opensuse.org/repositories/home:/please_try_again/openSUSE_11.4/[/noparse]](http://download.opensuse.org/repositories/home:/please_try_again/openSUSE_11.4/) PTA
zypper refresh -r PTA
zypper in updategrub

Usage:

updategrub     # updtate /boot/grub/menu.lst
updategrub -l  # display boot entries which could be added but don't write menu.lst
updategrub -a  # activate Grub primary partition (same as findgrub -a)
updategrub -r  # restore previous menu.lst
updategrub -2  # update Grub2 menu.

Some of updategrub’s options in /etc/updategrub/defaults include:

CHAINLOADGRUB (default yes)
add other Grubs chainload entries
**
MAKEACTIVE** (default is no and should remain NO!)
make Windows partition active

SETBOOTFLAG (default is no)
(set bootflag on Grub primary partition if any, same as updategrub -a)

You may also influence the order in which other Linux boot entries will be added in linux_order and ignore failsafe like boot entries as well as entries containing specific keywords, as in the example:
linux_ignore=“Gutsy Hardy”

I don’t guarantee it is bugfree. First versions rarely are. I hope you’ll tell me if you find some.
To Windows users - and I do not intend to work for them, please! - the entries that updategrub will add are the ones found and identified by os-prober, basically the same ones you would see in Ubuntu while running update-grub. Since openSUSE already adds those entries (sometimes also wrong ones), at least one (probably the right one) will appear twice. I guess I’ll have to add a windows_ignore option in the next release to avoid duplicates.

updategrub does not modify the part of menu.lst written by openSUSE YaST or zypper. It does not add other openSUSE’s kernel boot entries, since os-prober doesn’t check the /boot partition of the current OS. However it will add other openSUSE kernel boot entries if they are installed in other partitions (other openSUSE versions or several installs of the same version).

Viel Spass. :wink:

#! /bin/bash

#: Title       : updateLegacyGrub
#: Date Created: Sat Apr  2 16:20:32 PDT 2011
#: Last Edit   : Fri Apr 15 07:15:49 PDT 2011
#: Author      : Agnelo de la Crotche (please_try_again) 
#: Version     : 1.2.2
#: Description : add other Linux & Windows boot entries to Legacy Grub boot menu (similarly to Grub2's update-grub)
#: Requires    : os-prober (available in my repo: [noparse]http://download.opensuse.org/repositories/home:/please_try_again/openSUSE_11.4/[/noparse])
#: Usage       : updategrub
#: Syntax      : There are a few options. Type updategrub -h for help. 
#              : Read /usr/share/doc/packages/updategrub/README for details.
#
#
# current version
version="1.2.2"
prg=$(basename $0)
devmap=/boot/grub/device.map

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# TESTS 
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# check if we are root
[ $UID -eq 0 ] || exec echo "You have to run this script as root (or sudo)" 

# check for os-prober and linux-boot-prober 
osprober=`which os-prober 2>/dev/null` || exec echo "os-prober not found"
linuxbootprober=`which linux-boot-prober 2>/dev/null` || exec echo "linux-boot-prober not found"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# DEFINE VARIABLES 
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

declare -l CHAINLOADGRUB NOFAILSAFE MAKEACTIVE SETBOOTFLAG WINDOWSFIRST DISPLAYONLY EXCLUDE GRUB2
declare -a GRUBS HDDEV

# Chainload other Grubs by default.
CHAINLOADGRUB=yes
# Ignore failsafe, recovery and single user mode entries.
NOFAILSAFE=yes
# Make Windows Partition active.
MAKEACTIVE=no
# Windows Chainload entries before other Linux distros kernel entries
WINDOWSFIRST=no
# Set bootflag on Grub partition
SETBOOTFLAG=no
# Display changes but do not write boot menu. 
DISPLAYONLY=no
# if NOFAILSAFE is set, reject entries containing this words keywords  
EXCLUDE='recovery|rescue|failsafe|fallback|single-user'

# import some defaults from (optional) configuration file
[ -f /etc/updategrub/defaults ] && source /etc/updategrub/defaults

for OPT in CHAINLOADGRUB NOFAILSAFE MAKEACTIVE WINDOWSFIRST SETBOOTFLAG DISPLAYONLY ; do
	[ "x${!OPT}" == "xyes" -o "x${!OPT}" ==  "xtrue" -o "x${!OPT}" == "x1" ] && eval $OPT=yes
	[ "x${!OPT}" == "xno" -o "x${!OPT}" ==  "xfalse" -o "x${!OPT}" == "x0" ] && eval unset $OPT
done 

osprobedir="/usr/lib/os-probes/mounted" 

[ "$NOFAILSAFE" ] || EXCLUDE="fkflsgjtriogserpfkawerpkfdlpw"
[ "$linux_ignore" ] && EXCLUDE="${EXCLUDE}$(echo $linux_ignore | tr " " "
" | sed 's/^/|/' | tr -d "
")"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# DISTROS SPECIFIC 
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# YaST comment for openSUSE
[ -f /sbin/yast ] && CAP="###Don't change this comment - YaST2 identifier: Original name:"

# on Fedora and Centos, Grub menu file is /boot/grub/grub.conf
grubmenu=/boot/grub/menu.lst
[ -e /etc/redhat-release -a -f /boot/grub/grub.conf ] && grubmenu=/boot/grub/grub.conf

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# some Bootsector ID at offset 0x80,81
# from bootinfo script by Ulrich Meierfrankenfeld
# [Boot Info Script | Download Boot Info Script software for free at SourceForge.net](http://sourceforge.net/projects/bootinfoscript/)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

BSaa75="Legacy Grub"
BS5272="Legacy Grub"
BS48b4="Grub 1,96"
BS488="Grub2 s core.img"
BS7c3c="Grub2"

BS8053="Lilo"

BSbd0="MSWIN4.1 Fat 32"
BS7cc6="MSWIN4.1 Fat 32"
BS7cc6="Windows 98"
BS8cd="Windows XP"
BS8ec0="Windows XP"
BSfa33="Windows XP"
BSb6d1="Windows XP Fat32"
BS745="Vista Fat 32"
BS55aa="Windows Vista/7"
BSe9d8="Windows Vista/7"

BSb60="Dell Utility Fat16" 
BSe00="Dell Utility Fat16"
BS4445="DEll Restore Fat32" 
BS8ed0="DEll Recovery: Fat32"

BS3a5e="Recovery:Fat 32"
BS6974="BootIt: Fat16"
BS6f65="BootIt: Fat16"
BSe2f7="Fat32, Non Bootable"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# DEVICE/BIOS MAPPING 
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
HDDEV=($(cat /proc/partitions | awk '!/major/{ gsub(/[0-9]/,"",$4) ; print $4 }' | sort -u ))

i=0
while [ $i -lt ${#HDDEV[li]} ] ; do eval ${HDDEV[$i]}=hd$i ; let i++ ; done
[/li]
function invalidHD {
	str="e[37;1m/grub/device.map contains an orphan link: e[31;1m"$1"e[37;1m. Maybe you removed a hard disk since you installed Grub. Either delete /boot/grup/device.map or edit the devices  in this file to achieve a correct drive mapping. updategrub can not work with a corrupted device.map.e[37;0m" 
	printf "
%s

" "$str" | fmt
	exit
}

# map hard disk devices to BIOS drives according to /boot/grub/device.map (if this file exists)  
if [ -f $devmap ] ; then
	devlinks=($(sed -n '/(hd/s|(\(.*\))[ 	][ 	]*\(.*\)|\2=\1|p' $devmap))
	if [ ${#devlinks[li]} -ge 1 ] ; then
[/li]		for dev in ${devlinks[li]} ; do
[/li]			sdx=${dev%=*} ; hdn=${dev#*=}
			if [ -b $sdx ] ; then
				[ -h $sdx ] && sdx=$(readlink $sdx)
			else
				invalidHD $sdx
			fi
			eval $(basename $sdx)=$hdn
		done
	fi
fi
DEVMAP=$(for d in ${HDDEV[li]} ; do printf "s|\\$\\$%s|%s|;" $d ${!d} ; done)
[/li]


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# FUNCTIONS 
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

function grubname {
dev=$(basename $1 | tr -d "0-9")
part=$(($(basename $1 | tr -d "a-z") - 1))
if [ "${!dev}" ] ; then
	drv=${!dev}
else
	drv=$(echo $dev | sed 's|..|HD|' | tr "abcdefgh" "01234567" | sed 's|HD|hd|')
fi
if [ $part -ge 0 ] ; then
	echo "($drv,$part)"
else
	echo "($drv)"
fi
}

function writeEntry {
M=$1 ; shift
for lin in $* ; do
	D=${lin##*:} ; LONG=${D//@/ }
	D=${lin%:*}  ; SHORT=${D%:*} ; DEV=${D#*:} 
	COM=${CAP:+$CAP $SHORT###}
	COM=${COM:-# $LONG on $DEV}
	HDP=$(grubname $DEV)
	OPT=${SHORT//[0-9]/}_options ; OPT="${!OPT}"
	O=$(printf "$OPT" | sed 's| *$|\
|' | tr -d "
")
	case $M in 
	K) 
	$linuxbootprober $DEV | grep -E -i -v $EXCLUDE | awk -F ":" 'BEGIN { C="'"$COM"'" ; O="'"$O"'" } ; { P=$2 ; sub(/\/dev\//,"",P) ; MAJ=P ; MIN=P ; gsub(/[0-9]/,"",MAJ) ; gsub(/[a-zA-Z]/,"",MIN) ; MIN-- ; printf "
%s
title %s
%s    root ($$%s,%s)
    kernel %s %s
", C, $3, O, MAJ, MIN, $4, $6 ; if ( $5 != "" ) printf "    initrd %s
", $5 }' | sed "$DEVMAP" 
	file -s $DEV | grep -i -q "Grand Unified Bootloader" && GRUBS=(${GRUBS[li]} "$DEV:$HDP:$SHORT")
[/li]	;;
	C)
	printf "
%s
title %s
" "$COM" "$LONG" 
	[ "$OPT" ] && printf "%s
" "$OPT"
	HD=$(echo ${HDP%%,*} | tr -d "(")
	[ "$HD" == "hd0" ] || printf "    map (%s) (hd0)
    map (hd0) (%s)
" $HD $HD
	printf "    rootnoverify %s
" "$HDP"
	[ "$MAKEACTIVE" ] && printf "    makeactive
"
	printf "    chainloader +1
" 
	;;
	esac
done
}

function writeGrubChainloadEntries {

	# add Grubs found in the MBR of all HDs.
	for dev in ${HDDEV[li]} ; do
[/li]		dd if=/dev/$dev bs=512 count=1 2>/dev/null | grep -q GRUB || continue
		BS="BS$(hexdump -v -s 128 -n  2 -e '/1 "%x"' /dev/$dev)"
		BS=${!BS} ; title=${BS:-Grub}
		COM=${CAP:+$CAP GrubOn${dev}###}
		COM=${COM:-# Grub on $dev MBR}
		printf "
%s
title %s in %s MBR
    rootnoverify (%s)
    chainloader +1
" "$COM" "$title" $dev ${!dev} 
	done

	# add other Grub found in linux (0x83) or extended (0x0f and 0x05) partitions to the GRUBS array.
	# Grub present in Linux root partition should already have been added.     
	SKIPROOT=$(df -hl / /boot | awk '/\/dev/ { printf "s| *%s||;",  $1 }')

	devs=`/sbin/fdisk -l 2>/dev/null | awk '/^\/dev/ { sub(/*/,"",$0) ; if ($5=="83" || $5=="f" || $5=="5") print $1 }' | sed "$(echo ${GRUBS[li]} | tr " " "[/li]" | sed 's|:.*||;s|/|\\\/|g;s|^|/|;s|$|/d|' | tr "
" ";")" | sed "$SKIPROOT"` 

	for dev in $devs ; do
		dd if=$dev bs=512 count=1 2>/dev/null | grep -q GRUB &&	GRUBS=(${GRUBS[li]} "$dev:$(grubname $dev):Grub")
[/li]	done

	for entry in ${GRUBS[li]} ; do
[/li]		eval $(echo $entry | awk -F ":" '{printf "DEV=%s ; HDP=%s ; LBL=%s ;", $1, $2, $3}')
		BS="BS$(hexdump -v -s 128 -n  2 -e '/1 "%x"' $DEV)" ; BS=${!BS} 
		[ "$LBL" == "Grub" ] || LBL="${LBL}-Grub"
		[ "$LBL" == "Grub" ] && LBL="$BS"
		COM=${CAP:+$CAP $LBL###}
		COM=${COM:-# $LBL on $DEV}
		printf "
%s
title %s on %s 
    root (%s)" "$COM" "$(echo $LBL | tr "-" " ")" $DEV "$HDP"
		case $BS in
		"Legacy Grub")		 
			printf "
    chainloader +1
"
		;; 
		Grub2)
			COREPOS=$(hexdump -v -s 92 -n 4 -e '"%u"' $DEV)
			dd if=${DEV:0:8} bs=512 count=1 skip=$(($COREPOS+1)) 2>/dev/null | hexdump -v  -n 64 -e '"%_u"'| sed 's|.*(,\(.*\)|\1|;s|)||;s|nul||g' | grep -q '/boot/grub' && printf "
    kernel /boot/grub/core.img
    boot
" || printf "
    chainloader +1
"
		;;
 		esac
	done
}

function setBootFlag {
	devs=`LC_ALL=C /sbin/fdisk -l 2>/dev/null | awk '/^Disk/&&/dev/ { sub(/:/,"",$2) ; print $2 }'`
	declare -a grubPt
	for DEV in $devs ; do
		if [ "$(grubname $DEV)" == "(hd0)" ] ; then
			eval $(dd if=$DEV bs=1 skip=446 count=64 2>/dev/null | od -x | head -4 | cat -n | awk 'BEGIN { PART="'"$DEV"'"; I=-1 } ; { sub(/../,"", $3) ; sub(/../,"",$5) ; if ( $5 == "0f" || $5 == "83" ) { I++ ; if ( $3 == "00" ) P="i" ; else P="a" ;  printf "%spart[%s]=%s ;", P, I, $1 }}')
			if [ ${#apart[li]} -eq 0 -a ${#ipart[*]} -ge 1 ] ; then
[/li]				for n in ${ipart[li]} ; do
[/li]                    dd if=${DEV}$n bs=512 count=1 2>/dev/null | grep -q -i GRUB && grubPt=(${grubPt[li]} $n)
[/li]				done
				[ ${#grubPt[li]} -eq 1 -a -x /sbin/sfdisk ] && /sbin/sfdisk $DEV -A${grubPt[0]}
[/li]			fi
		fi 
	done
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# UPDATE GRUB2 !!! 
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function updategrub2 {
	grubmkconfig=$(find /usr/sbin/ -name "grub*-mkconfig")
	[ "$grubmkconfig" ] || exec echo "grub2 not found" 

	if [ "$DISPLAYONLY" ] ; then
		echo "Scanning..."
		$grubmkconfig
	else
		[ -f /etc/default/grub ] || exec echo "/etc/default/grub not found" 
		grubmenu=$(sed -n 's|.*\(/boot/grub.*/grub.cfg\).*|\1|p' /etc/default/grub | sort -u)
		[ "$grubmenu" ] || exec echo "could not find grub2 menu"
		[ -f $grubmenu ] && cp $grubmenu{,.updg}
		echo "Scanning..."
		$grubmkconfig -o $grubmenu
	fi	
	exit
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# HELP 
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function syntax {
cat << EOFSYNTAX

$prg $version
______________
usage:
   $prg [options]

options:
   -l --list     : list found boot entries but do not add them to Grub menu.
   -a --activate : activate Grub primary partition (if any) on 1st BIOS drive.
   -r --restore  : restore previous Grub menu
   -h --help     : display this help.
   -2 --grub2    : update Grub2 menu.

EOFSYNTAX
[ -d $osprobedir ] && find $osprobedir -name "*linux-distro*" -exec awk -F "=" 'BEGIN { printf "linux distros short names:

" } ; /short=/ { s=tolower($2) ; gsub(/"/,"",s) ; gsub(/ /,"",s) ;  print s }' "{}" ";"
exit
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# BACK UP AND RESTORE GRUB MENU 
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

function backupMenu {
# save original grub menu once 
[ -f ${grubmenu}.dist ] || cp $grubmenu{,.dist}
# save current grub menu
[ -f ${grubmenu} ] && cp $grubmenu{,.updg}
}
 
function restoreMenu {
if [ -f ${grubmenu}.updg ] ; then
	declare -l yesno
	read -p "Do you want to restore previously saved grub menu? [y|n]" -n1 yesno
	printf "
"
	[ "x$yesno" == "xy" ] && cp $grubmenu{.updg,}
fi
exit
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# parsing options 
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

ARGS=`getopt -q -o lahr2 --long list,activate,help,restore,grub2 -- "$@"`

[ "$?" == "0" ] || syntax

eval set -- "$ARGS"

while true ; do
	case "$1" in
		-l|--list) DISPLAYONLY=yes ; shift ;;
		-a|--activate) SETBOOTFLAG=yes ; shift ;;
		-h|--help) syntax ; shift ;;
		-r|--restore) restoreMenu ; shift ;;
		-2|--grub2) GRUB2=yes ; shift ;;
		--) shift ; break ;;
    esac
done

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### ~~~~~~~~~~~~~~~~ MAIN - BEGIN ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# update Grub2 and exit
[ "$GRUB2" ] && updategrub2

echo "Scanning..."

# If used WITHOUT the option "-n", save Grub menu first
if [ "$DISPLAYONLY" ] ; then
	echo "Following boot entries could be added to Grub menu:"
else
	backupMenu
	sed -i '/Added by updategrub/,$d' $grubmenu
	printf "# *** Don't change this comment - Added by updategrub %s
" "$(date)" >> $grubmenu
fi

eval $($osprober 2>/dev/null | awk -F ":" 'BEGIN { OFS=":" ; i=-1 ; j=-1 }; { if ( $4 == "linux" ) { i++ ; I=i } else { j++ ; I=j } ; S=tolower($3) ; s=tolower($2) ; sub(/ .*/,"",s) ; if ( S == "unknownlsb" ) S=s ;  gsub( / /, "@" , $2 ) ;  printf "%s[%s]=\"%s:%s:%s\" ; ", $4, I, S, $1, $2 }')

if [ "$linux_order" ] ; then
	eval $(echo $linux_order | awk 'BEGIN { RS=" " ; printf "REPLACE=\"" } ; { printf "s|%s|%s%s|;", $1, NR, $1 } ; END { printf "\"" }')
	linux=($(echo ${linux[li]} | tr " " "[/li]" | sed "$REPLACE;s|\([a-zA-Z]\):|\10:|" | sort -n | sed 's|^[0-9]*||;s|0:/|:/|'))
fi

[ $((${#linux[li]} + ${#chain[*]})) -ge 1 ] || exec "No other operating systems found"
[/li]
if [ "$WINDOWSFIRST" ] ; then
	if [ "$DISPLAYONLY" ] ; then
		writeEntry C ${chain[li]}
[/li]		writeEntry K ${linux[li]}
[/li]	else
		writeEntry C ${chain[li]} >> $grubmenu
[/li]		writeEntry K ${linux[li]} >> $grubmenu
[/li]	fi
else
	if [ "$DISPLAYONLY" ] ; then
		writeEntry K ${linux[li]}
[/li]		writeEntry C ${chain[li]}
[/li]	else
		writeEntry K ${linux[li]} >> $grubmenu
[/li]		writeEntry C ${chain[li]} >> $grubmenu
[/li]	fi
fi 

if [ "$CHAINLOADGRUB" ] ; then
	if [ "$DISPLAYONLY" ] ; then
		writeGrubChainloadEntries
	else
		writeGrubChainloadEntries >> $grubmenu
	fi
fi

# append custom boot entries (like other OS chainload entries) from /etc/updategrub/custom
if [ -f /etc/updategrub/custom ] ; then
	if [ "$DISPLAYONLY" ] ; then
		printf "
"
		cat /etc/updategrub/custom 
	else
		printf "
" >> $grubmenu
		cat /etc/updategrub/custom >> $grubmenu
 	fi 
fi

# Set bootflag on  Grub partition 
if [ "$SETBOOTFLAG" ] ; then
	setBootFlag
fi

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### ~~~~~~~~~~~~~~~~ MAIN - END ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

v. 1.4.1


#! /bin/bash

#: Title       : updateLegacyGrub
#: Date Created: Sat Apr 2 16:20:32 PDT 2011
#: Last Edit   : Fri Apr 22 23:55:09 PDT 2011
#: Author      : Agnelo de la Crotche (please_try_again)
#: Version     : 1.4.1
#: Description : add other Linux & Windows boot entries to Legacy Grub boot menu
#: Requires    : os-prober, dialog
#: Usage       : updategrub
#: Syntax      : There are a few options. Type updategrub -h for help.
#              : Read /usr/share/doc/packages/updategrub/README for details
#
# current version
version="1.4.1"
prg=$(basename $0)
devmap=/boot/grub/device.map
defaults=/etc/updategrub/defaults
custom=/etc/updategrub/custom

# ~~~~~
# TESTS
# check if we are root
[ $UID -eq 0 ] || exec echo "You have to run this script as root or sudo"

# check for os-prober & linux-boot-prober
osprober=`which os-prober 2&gt;/dev/null` || exec echo "os-prober not found"
linuxbootprober=`which linux-boot-prober 2&gt;/dev/null` || exec echo "linux-boot-prober not found"

# ~~~~~~~~~~~~~~~~
# DEFINE VARIABLES
declare -l CHAINLOADGRUB NOFAILSAFE MAKEACTIVE SETBOOTFLAG WINDOWSFIRST INTERACTIVE DISPLAYONLY EXCLUDE GRUB2
declare -a GRUBS HDDEV

# Chainload other Grubs by default
CHAINLOADGRUB=yes
# Ignore failsafe entries
NOFAILSAFE=yes
# Make Windows Partition active
MAKEACTIVE=no
# Windows Chainload entries before other Linux distros kernel entries
WINDOWSFIRST=no
# Set bootflag on Grub partition
SETBOOTFLAG=no
# Display changes but do not write boot menu
DISPLAYONLY=no
# Interactive menu
INTERACTIVE=no
# if NOFAILSAFE is set, reject entries containing this words keywords
EXCLUDE='recovery|rescue|failsafe|fallback|single-user'

# import some defaults from (optional) configuration file
[ -f $defaults ] && source $defaults

for OPT in CHAINLOADGRUB NOFAILSAFE MAKEACTIVE WINDOWSFIRST INTERACTIVE SETBOOTFLAG DISPLAYONLY ; do
	[ "x${!OPT}" == "xyes" -o "x${!OPT}" == "xtrue" -o "x${!OPT}" == "x1" ] && eval $OPT=yes
	[ "x${!OPT}" == "xno" -o "x${!OPT}" == "xfalse" -o "x${!OPT}" == "x0" ] && eval unset $OPT
done

osprobedir="/usr/lib/os-probes/mounted"

[ "$NOFAILSAFE" ] || EXCLUDE="fkflsgjtriogserpfkawerpkfdlpw"
[ "$linux_ignore" ] && EXCLUDE="${EXCLUDE}$(echo $linux_ignore | tr " " "
" | sed 's/^/|/' | tr -d "
")"

# ~~~~~~~~~~~~~~~~
# DISTROS SPECIFIC

# YaST comment for openSUSE
[ -f /sbin/yast ] && CAP="###Don't change this comment - YaST2 identifier: Original name:"

# on Fedora and Centos, Grub menu file is /boot/grub/grub.conf
grubmenu=/boot/grub/menu.lst
[ -e /etc/redhat-release -a -f /boot/grub/grub.conf ] && grubmenu=/boot/grub/grub.conf

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# some Bootsector ID at offset 0x80,81

BSaa75="Legacy Grub"
BS5272="Legacy Grub"
BS48b4="Grub 1,96"
BS488="Grub2 s core.img"
BS7c3c="Grub2"
BS8053="Lilo"
BSbd0="MSWIN4.1 Fat 32"
BS7cc6="MSWIN4.1 Fat 32"
BS7cc6="Windows 98"
BS8cd="Windows XP"
BS8ec0="Windows XP"
BSfa33="Windows XP"
BSb6d1="Windows XP Fat32"
BS745="Vista Fat 32"
BS55aa="Windows Vista/7"
BSe9d8="Windows Vista/7"
BSb60="Dell Utility Fat16"
BSe00="Dell Utility Fat16"
BS4445="DEll Restore Fat32"
BS8ed0="DEll Recovery: Fat32"
BS3a5e="Recovery:Fat 32"
BS6974="BootIt: Fat16"
BS6f65="BootIt: Fat16"
BSe2f7="Fat32, Non Bootable"

# ~~~~~~~~~~~~~~~~~~~
# DEVICE/BIOS MAPPING
HDDEV=($(cat /proc/partitions | awk '!/major/{ gsub(/[0-9]/,"",$4) ; print $4 }' | sort -u))

i=0
while [ $i -lt ${#HDDEV[li]} ] ; do eval ${HDDEV[$i]}=hd$i ; let i++ ; done
[/li]
function invalidHD {
	str="/grub/device.map contains an orphan link: "$1". Maybe you removed a hard disk since you installed Grub. Either delete /boot/grup/device.map or edit the devices in this file to achieve a correct drive mapping. updategrub can not work with a corrupted device.map."
	printf "
%s

" "$str" | fmt
	exit
}

# map hard disk devices to BIOS drives according to /boot/grub/device.map (if this file exists)
if [ -f $devmap ] ; then
	devlinks=($(sed -n '/(hd/s|(\(.*\))[ 	][ 	]*\(.*\)|\2=\1|p' $devmap))
	if [ ${#devlinks[li]} -ge 1 ] ; then
[/li]		for dev in ${devlinks[li]} ; do
[/li]			sdx=${dev%=*} ; hdn=${dev#*=}
			if [ -b $sdx ] ; then
				[ -h $sdx ] && sdx=$(readlink $sdx)
			else
				invalidHD $sdx
			fi
			eval $(basename $sdx)=$hdn
		done
	fi
fi
DEVMAP=$(for d in ${HDDEV[li]} ; do printf "s|\\$\\$%s|%s|;" $d ${!d} ; done)
[/li]
# ~~~~~~~~~
# FUNCTIONS

function grubname {
dev=$(basename $1 | tr -d "0-9")
part=$(($(basename $1 | tr -d "a-z") - 1))
if [ "${!dev}" ] ; then
	drv=${!dev}
else
	drv=$(echo $dev | sed 's|..|HD|' | tr "abcdefgh" "01234567" | sed 's|HD|hd|')
fi
if [ $part -ge 0 ] ; then
	echo "($drv,$part)"
else
	echo "($drv)"
fi
}

function writeEntry {
M=$1 ; shift
for lin in $* ; do
	D=${lin##*:} ; LONG=${D//@/ }
	D=${lin%:*} ; SHORT=${D%:*} ; DEV=${D#*:}
	COM=${CAP:+$CAP $SHORT###}
	COM=${COM:-# $LONG on $DEV}
	HDP=$(grubname $DEV)
	OPT=${SHORT//[0-9]/}_options ; OPT="${!OPT}"
	O=$(printf "$OPT" | sed 's| *$|\
|' | tr -d "
")
	case $M in
	K)
	$linuxbootprober $DEV | grep -E -i -v $EXCLUDE | awk -F ":" 'BEGIN { C="'"$COM"'" ; O="'"$O"'" } ; { P=$2 ; sub(/\/dev\//,"",P) ; MAJ=P ; MIN=P ; gsub(/[0-9]/,"",MAJ) ; gsub(/[a-zA-Z]/,"",MIN) ; MIN-- ; printf "
%s
title %s
%s    root ($$%s,%s)
    kernel %s %s
", C, $3, O, MAJ, MIN, $4, $6 ; if ($5 != "") printf "    initrd %s
", $5 }' | sed "$DEVMAP"
	file -s $DEV | grep -i -q "Grand Unified Bootloader" && GRUBS=(${GRUBS[li]} "$DEV:$HDP:$SHORT") ;;
[/li]	C)
	printf "
%s
title %s - added by updategrub
" "$COM" "$LONG"
	[ "$OPT" ] && printf "%s
" "$OPT"
	HD=$(echo ${HDP%%,*} | tr -d "(")
	[ "$HD" == "hd0" ] || printf "    map (%s) (hd0)
    map (hd0) (%s)
" $HD $HD
	printf "    rootnoverify %s
" "$HDP"
	[ "$MAKEACTIVE" ] && printf "    makeactive
"
	printf "    chainloader +1
" ;;
	esac
done
}

function writeGrubChainloadEntries {
# add Grubs found in the MBR of all HDs
for dev in ${HDDEV[li]} ; do
[/li]	dd if=/dev/$dev bs=512 count=1 2>/dev/null | grep -q GRUB || continue
	BS="BS$(hexdump -v -s 128 -n 2 -e '/1 "%x"' /dev/$dev)"
	BS=${!BS} ; title=${BS:-Grub}
	COM=${CAP:+$CAP GrubOn${dev}###}
	COM=${COM:-# Grub on $dev MBR}
	printf "
%s
title %s in %s MBR
    rootnoverify (%s)
    chainloader +1
" "$COM" "$title" $dev ${!dev}
done

# add other Grub found in linux (0x83) or extended (0x0f and 0x05) partitions to the GRUBS array.
# Grub present in Linux root partition should already have been added.
SKIPROOT=$(df -hl / /boot | awk '/\/dev/ { printf "s| *%s||;", $1 }')

devs=`/sbin/fdisk -l 2&gt;/dev/null | awk '/^\/dev/ { sub(/*/,"",$0) ; if ($5=="83" || $5=="f" || $5=="5") print $1 }' | sed "$(echo ${GRUBS[li]} | tr " " "[/li]" | sed 's|:.*||;s|/|\\\/|g;s|^|/|;s|$|/d|' | tr "
" ";")" | sed "$SKIPROOT"`

for dev in $devs ; do
	dd if=$dev bs=512 count=1 2>/dev/null | grep -q GRUB &&	GRUBS=(${GRUBS[li]} "$dev:$(grubname $dev):Grub")
[/li]done

for entry in ${GRUBS[li]} ; do
[/li]	eval $(echo $entry | awk -F ":" '{printf "DEV=%s ; HDP=%s ; LBL=%s ;", $1, $2, $3}')
	BS="BS$(hexdump -v -s 128 -n 2 -e '/1 "%x"' $DEV)" ; BS=${!BS}
	[ "$LBL" == "Grub" ] || LBL="${LBL}-Grub"
	[ "$LBL" == "Grub" ] && LBL="$BS"
	COM=${CAP:+$CAP $LBL###}
	COM=${COM:-# $LBL on $DEV}
	printf "
%s
title %s on %s 
    root (%s)" "$COM" "$(echo $LBL | tr "-" " ")" $DEV "$HDP"
	case $BS in
	"Legacy Grub")	printf "
    chainloader +1
" ;;
	Grub2)
		COREPOS=$(hexdump -v -s 92 -n 4 -e '"%u"' $DEV)
		dd if=${DEV:0:8} bs=512 count=1 skip=$(($COREPOS+1)) 2>/dev/null | hexdump -v -n 64 -e '"%_u"'| sed 's|.*(,\(.*\)|\1|;s|)||;s|nul||g' | grep -q '/boot/grub' && printf "
    kernel /boot/grub/core.img
    boot
" || printf "
    chainloader +1
"
	;;
	esac
done
}

function setBootFlag {
devs=`LC_ALL=C /sbin/fdisk -l 2&gt;/dev/null | awk '/^Disk/&&/dev/ { sub(/:/,"",$2) ; print $2 }'`
declare -a grubPt
for DEV in $devs ; do
	if [ "$(grubname $DEV)" == "(hd0)" ] ; then
		eval $(dd if=$DEV bs=1 skip=446 count=64 2>/dev/null | od -x | head -4 | cat -n | awk 'BEGIN { PART="'"$DEV"'"; I=-1 } ; { sub(/../,"", $3) ; sub(/../,"",$5) ; if ($5 == "0f" || $5 == "83") { I++ ; if ($3 == "00") P="i" ; else P="a" ; printf "%spart[%s]=%s ;", P, I, $1 }}')
		if [ ${#apart[li]} -eq 0 -a ${#ipart[*]} -ge 1 ] ; then
[/li]			for n in ${ipart[li]} ; do
[/li]				dd if=${DEV}$n bs=512 count=1 2>/dev/null | grep -q -i GRUB && grubPt=(${grubPt[li]} $n)
[/li]			done
			[ ${#grubPt[li]} -eq 1 -a -x /sbin/sfdisk ] && /sbin/sfdisk $DEV -A${grubPt[0]}
[/li]		fi
	fi
done
exit
}

# ~~~~~~~~~~~~
# UPDATE GRUB2
function updategrub2 {
grubmkconfig=$(find /usr/sbin/ -name "grub*-mkconfig")
[ "$grubmkconfig" ] || exec echo "grub2 not found"

if [ "$DISPLAYONLY" ] ; then
	echo "Scanning..."
	$grubmkconfig
else
	[ -f /etc/default/grub ] || exec echo "/etc/default/grub not found"
	grubmenu=$(sed -n 's|.*\(/boot/grub.*/grub.cfg\).*|\1|p' /etc/default/grub | sort -u)
	[ "$grubmenu" ] || exec echo "could not find grub2 menu"
	[ -f $grubmenu ] && cp $grubmenu{,.updg}
	echo "Scanning..."
	$grubmkconfig -o $grubmenu
fi	
exit
}

# ~~~~
# HELP
function syntax {
cat << EOFSYNTAX

$prg $version
______________
usage:
   $prg [options]

options:
   -l --list        : list found boot entries but do not add them to Grub menu
   -i --interactive : launch interactive menu to enable/disable boot entries
   -m --menu        : enable/disable boot entries in grubmenu (without scanning)
   -a --activate    : activate Grub primary partition (if any) on 1st BIOS drive
   -r --restore     : restore previous Grub menu
   -h --help        : display this help
   -2 --grub2       : update Grub2 menu

EOFSYNTAX
[ -d $osprobedir ] && find $osprobedir -name "*linux-distro*" -exec awk -F "=" 'BEGIN { printf "linux distros short names:

" } ; /short=/ { s=tolower($2) ; gsub(/"/,"",s) ; gsub(/ /,"",s) ; print s }' "{}" ";"
exit
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~
# BACK UP & RESTORE GRUB MENU
function backupMenu {
# save original grub menu once
[ -f ${grubmenu}.dist ] || cp $grubmenu{,.dist}
# save current grub menu
[ -f ${grubmenu} ] && cp $grubmenu{,.updg}
}

function restoreMenu {
if [ -f ${grubmenu}.updg ] ; then
	declare -l yesno
	read -p "Do you want to restore previously saved grub menu? [y|n]" -n1 yesno
	printf "
"
	[ "x$yesno" == "xy" ] && cp $grubmenu{.updg,}
fi
exit
}

# ~~~~~~~~~~~~~~~~
# interactive menu
function grubmenu {

[ $(tail -1 $grubmenu | wc -L) -gt 0 ] && echo >> $grubmenu

# get checklist width and height
W=$(($(sed -n '/^#* *title/s/title *//p' $grubmenu | wc -L)+10))
h=$(sed -n '/^#* *title/s/title *//p' $grubmenu | wc -l)
H=$(($h + 7))
[ $W -eq 10 ] && W=80
[ $H -eq 6 ] && H=15

eval $(grep -E -n -e '^#* *title' -e '^ *$' $grubmenu | sed 's|:|@|;s|:|;|g;s|@|:|;s|\([0-9][0-9]*\):$|=:\1|' | sed -e :a -e '$!N;s/
=/ /;ta' -e 'P;D' | sed 's/\([0-9][0-9]*\):\(#*\)title *\([^:]*\) :\([0-9][0-9]*\).*/\1:\4:on\2:\3/;s/on#/off/;/^=/d' | awk -F ":" '{ $2-- ; printf "startpos[%s]=%s;endpos[%s]=%s;enable[%s]=%s;entry[%s]=\"%s\"
" , NR, $1, NR, $2, NR, $3, NR, $4 }')
glist=$(sed -n '/^#* *title/s/title *//p' $grubmenu | sed 's/^/on /;s/on ##*/off /;s/^on \(.*\) *$/\"\1\" on/;s/^off \(.*\) *$/\"\1\" off/;s/  *"/"/' | awk '{ printf "%s %s ", NR, $0 }')

cat << EOFGLIST | sh -
dialog --clear --stdout --title "Grub Menu" --nocancel --output-separator "-" --checklist "Press spacebar to select/unselect boot menu entries" $H $W $h $glist | sed 's/"//g;s/$/-/' > /tmp/grubmenu.tmp
EOFGLIST

[ -f /tmp/grubmenu.tmp ] && enable=$(cat /tmp/grubmenu.tmp) || exec echo "can not get list of Menu entries"

# exit if user pressed escape
[ "x$enable" == "x" ] && return

i=1
while [ $i -le $h ] ; do
	[ "${enable%%-$i-*}" == "${enable}" ] && enable[$i]=off || enable[$i]=on
	let i++
done

i=1
cat << EOFSED | sed -f - -i $grubmenu
`while [ $i -le $h ] ; do
	[ "${enable[$i]}" == "on" ] && echo "${startpos[$i]},${endpos[$i]}s/^##*//" || echo "${startpos[$i]},${endpos[$i]}s/^\\\([^#]\\\)/#\\\1/"
	let i++
done`
EOFSED
}

# ~~~~~~~~~~~~~~~
# parsing options

ARGS=`getopt -q -o lamihr2 --long list,activate,menu,interactive,help,restore,grub2 -- "$@"`

[ "$?" == "0" ] || syntax

eval set -- "$ARGS"

while true ; do
case "$1" in
	-l|--list) DISPLAYONLY=yes ; shift ;;
	-a|--activate) setBootFlag ; shift ;;
	-i|--interactive) INTERACTIVE=yes ; shift ;;
	-m|--menu) GRUBMENU=yes ; shift ;;
	-h|--help) syntax ; shift ;;
	-r|--restore) restoreMenu ; shift ;;
	-2|--grub2) GRUB2=yes ; shift ;;
	--) shift ; break ;;
esac
done

[ "$prg" == "grubmenu" ] && GRUBMENU=yes

### ~~~ MAIN - BEGIN ~~~
# run grubmenu without scanning and exit
if [ "$GRUBMENU" ] ; then
	grubmenu
	exit
fi

# update Grub2 and exit
[ "$GRUB2" ] && updategrub2

echo "Scanning..."

# If used WITHOUT the option "-n", save Grub menu first
if [ "$DISPLAYONLY" ] ; then
	echo "Following boot entries could be added to Grub menu:"
else
	backupMenu
	sed -i '/Added by updategrub/,$d' $grubmenu
	printf "# *** Don't change this comment - Added by updategrub %s
" "$(date)" >> $grubmenu
fi

eval $($osprober 2>/dev/null | awk -F ":" 'BEGIN { OFS=":" ; i=-1 ; j=-1 }; { if ($4 == "linux") { i++ ; I=i } else { j++ ; I=j } ; S=tolower($3) ; s=tolower($2) ; sub(/ .*/,"",s) ; if (S == "unknownlsb") S=s ; gsub(/ /, "@" , $2) ; printf "%s[%s]=\"%s:%s:%s\" ; ", $4, I, S, $1, $2 }')

if [ "$linux_order" ] ; then
	eval $(echo $linux_order | awk 'BEGIN { RS=" " ; printf "REPLACE=\"" } ; { printf "s|%s|%s%s|;", $1, NR, $1 } ; END { printf "\"" }')
	linux=($(echo ${linux[li]} | tr " " "[/li]" | sed "$REPLACE;s|\([a-zA-Z]\):|\10:|" | sort -n | sed 's|^[0-9]*||;s|0:/|:/|'))
fi

[ $((${#linux[li]} + ${#chain[*]})) -ge 1 ] || exec "No other operating systems found"
[/li]
if [ "$WINDOWSFIRST" ] ; then
	if [ "$DISPLAYONLY" ] ; then
		writeEntry C ${chain[li]}
[/li]		writeEntry K ${linux[li]}
[/li]	else
		writeEntry C ${chain[li]} >> $grubmenu
[/li]		writeEntry K ${linux[li]} >> $grubmenu
[/li]	fi
else
	if [ "$DISPLAYONLY" ] ; then
		writeEntry K ${linux[li]}
[/li]		writeEntry C ${chain[li]}
[/li]	else
		writeEntry K ${linux[li]} >> $grubmenu
[/li]		writeEntry C ${chain[li]} >> $grubmenu
[/li]	fi
fi

if [ "$CHAINLOADGRUB" ] ; then
	if [ "$DISPLAYONLY" ] ; then
		writeGrubChainloadEntries
	else
		writeGrubChainloadEntries >> $grubmenu
	fi
fi

# append custom boot entries (like other OS chainload entries) from /etc/updategrub/custom
if [ -f $custom ] ; then
	if [ "$DISPLAYONLY" ] ; then
		printf "
"
		cat $custom
	else
		printf "
" >> $grubmenu
		cat $custom >> $grubmenu
 	fi
fi

[ "$DISPLAYONLY" ] && exit

# enabling/disabling boot entries.
[ "$INTERACTIVE" ] && grubmenu

# Set bootflag on Grub partition and exit
[ "$SETBOOTFLAG" ] && setBootFlag

### ~~~ MAIN - END ~~~

Very useful guide please_try_again. Thank you!!!:slight_smile:

This is version 1.4.1 of updategrub (post above). The full documentation can be seen here: updategrub for openSUSE Legacy Grub (not update-grub!). To install this script, don’t copy/paste this code! It depends on 2 other packages: os-prober and dialog. Install the script from repo as described in the HowTo to install the dependencies. The package also includes a config file, a man page and the scripts fingrub and cfindgrub.

# rpm -ql updategrub
/etc/updategrub
/etc/updategrub/defaults
/usr/bin/cfindgrub
/usr/bin/findgrub
/usr/bin/grubmenu
/usr/bin/updateLegacyGrub
/usr/bin/updategrub
/usr/share/doc/packages/updategrub
/usr/share/doc/packages/updategrub/COPYING
/usr/share/doc/packages/updategrub/README
/usr/share/doc/packages/updategrub/defaults
/usr/share/man/man1/updategrub.1.gz

After a kernel update, an entry is added to the Grub menu by /usr/lib/bootloader/bootloader_entry, a bash script which in turn calls the perl script /sbin/update-bootloader.

While updating to the latest kernel on 11.4, the post.sh](https://build.opensuse.org/package/view_file?file=post.sh&package=kernel-default&project=Kernel%3AopenSUSE-11.4&srcmd5=8dc0ef0131f7bc48c058e74996b7a4e9) script of the kernel package to be installed runs the following command:

/usr/lib/bootloader/bootloader_entry add default 2.6.37.6-0.5-default vmlinuz-2.6.37.6-0.5-default initrd-2.6.37.6-0.5-default

and so when you reboot, you see that the Grub entry for the kernel has changed.

To delete the previous kernel entry, the postun.sh](https://build.opensuse.org/package/view_file?file=postun.sh&package=kernel-default&project=Kernel%3AopenSUSE-11.4&srcmd5=8dc0ef0131f7bc48c058e74996b7a4e9) script of the kernel package to be removed does that:

/usr/lib/bootloader/bootloader_entry remove default 2.6.37.1-1.2-default vmlinuz-2.6.37.1-1.2-default initrd-2.6.37.1-1.2-default

which means in fact:


/sbin/update-bootloader --image /boot/vmlinuz-2.6.37.1-1.2-default --initrd /boot/initrd-2.6.37.1-1.2-default --remove --force

But it doesn’t only remove the 2.6.37.1-1.2 kernel entries. It removes all other kernel entries (not the chainloading ones) from Grub menu. What has been reported in numerous posts as a YaST bug is IMHO a bug in /sbin/update-bootloader. You can reproduce it easily by adding an entry for the older kernel as well as some others (openSUSE or others) and executing the command described above in a terminal.

This command was expected to remove only the text in red in the exemple below (simplified). In fact it removed everything in red, blue and green, including the previous openSUSE kernel (red), the updategrub marker (blue) and all kernel menu entries added by updategrub, but didn’t touch any of the chainloading entries (there were a lot more, I didn’t paste them all).

###Don't change this comment - YaST2 identifier: Original name: linux###
title openSUSE 11.4 (Celadon) - kernel 2.6.37.6-0.5 (default)
    root (hd0,10)
    kernel /boot/vmlinuz-2.6.37.6-0.5-default root=/dev/disk/by-uuid/e15f9b1d-d2e4-404f-aac6-79e46b016eb9 resume=/dev/disk/by-uuid/50089717-8b5e-482a-8686-7d47d88b7402 splash=silent quiet showopts vga=0x31a
    initrd /boot/initrd-2.6.37.6-0.5-default

###Don't change this comment - YaST2 identifier: Original name: failsafe###
title openSUSE 11.4 (Celadon) - kernel 2.6.37.6-0.5 (failsafe)
    root (hd0,10)
    kernel /boot/vmlinuz-2.6.37.6-0.5-default root=/dev/disk/by-uuid/e15f9b1d-d2e4-404f-aac6-79e46b016eb9 showopts apm=off noresume nosmp maxcpus=0 edd=off powersaved=off nohz=off highres=off processor.max_cstate=1 nomodeset x11failsafe vga=0x31a
    initrd /boot/initrd-2.6.37.6-0.5-default

###Don't change this comment - YaST2 identifier: Original name: linux###
title openSUSE 11.4 (celadon) - kernel 2.6.37.1-1.2
    root (hd0,10)
    kernel /boot/vmlinuz-2.6.37.1-1.2-default root=/dev/disk/by-uuid/e15f9b1d-d2e4-404f-aac6-79e46b016eb9 resume=/dev/disk/by-uuid/50089717-8b5e-482a-8686-7d47d88b7402 splash=silent quiet showopts vga=0x31a
    initrd /boot/initrd-2.6.37.1-1.2-default

###Don't change this comment - YaST2 identifier: Original name: failsafe###
title openSUSE 11.4 (celadon) - kernel 2.6.37.1-1.2 (Failsafe)
    root (hd0,10)
    kernel /boot/vmlinuz-2.6.37.1-1.2-default root=/dev/disk/by-uuid/e15f9b1d-d2e4-404f-aac6-79e46b016eb9 showopts apm=off noresume nosmp maxcpus=0 edd=off powersaved=off nohz=off highres=off processor.max_cstate=1 nomodeset x11failsafe vga=0x31a
    initrd /boot/initrd-2.6.37.1-1.2-default

# *** Don't change this comment - Added by updategrub Mon May  2 06:48:20 PDT 2011

###Don't change this comment - YaST2 identifier: Original name: ubuntu###
title Ubuntu 10.10 (maverick) - kernel 2.6.35-28-generic
    root (hd0,4)
    kernel /boot/vmlinuz-2.6.35-28-generic root=UUID=ccee43d6-331d-47dc-aac9-8174471d378a ro vga=791
    initrd /boot/initrd.img-2.6.35-28-generic

###Don't change this comment - YaST2 identifier: Original name: ubuntu###
title Ubuntu 10.10 (maverick) - kernel 2.6.35-27-generic
    root (hd0,4)
    kernel /boot/vmlinuz-2.6.35-27-generic root=UUID=ccee43d6-331d-47dc-aac9-8174471d378a ro vga=791
    initrd /boot/initrd.img-2.6.35-27-generic

###Don't change this comment - YaST2 identifier: Original name: mandrivalinux###
title linux Mandriva (Farman) - kernel 2.6.33.7-2
    root (hd1,4)
    kernel /boot/vmlinuz-2.6.33.7-desktop-2mnb BOOT_IMAGE=desktop_2.6.33.7-2 root=UUID=4a9d7d4f-dce0-43c5-b354-fae98fec5f76 resume=UUID=50089717-8b5e-482a-8686-7d47d88b7402 splash=silent vga=788
    initrd /boot/initrd-2.6.33.7-desktop-2mnb.img

###Don't change this comment - YaST2 identifier: Original name: mandrivalinux###
title linux Mandriva (Farman) - kernel 2.6.33.7-2 (desktop586)
    root (hd1,4)
    kernel /boot/vmlinuz-2.6.33.7-desktop586-2mnb BOOT_IMAGE=desktop586_2.6.33.7-2 root=UUID=4a9d7d4f-dce0-43c5-b354-fae98fec5f76 resume=UUID=50089717-8b5e-482a-8686-7d47d88b7402 splash=silent vga=788
    initrd /boot/initrd-2.6.33.7-desktop586-2mnb.img

###Don't change this comment - YaST2 identifier: Original name: mandrivalinux###
title linux Mandriva (Farman) - kernel 2.6.33.7-1
    root (hd1,4)
    kernel /boot/vmlinuz-2.6.33.7-desktop-1mnb BOOT_IMAGE=linux_Mandriva_(Farman)_-_kerne root=UUID=4a9d7d4f-dce0-43c5-b354-fae98fec5f76 resume=UUID=50089717-8b5e-482a-8686-7d47d88b7402 splash=silent vga=788
    initrd /boot/initrd-2.6.33.7-desktop-1mnb.img

###Don't change this comment - YaST2 identifier: Original name: windows###
title Microsoft Windows XP Professional - added by updategrub
    rootnoverify (hd0,0)
    chainloader +1

###Don't change this comment - YaST2 identifier: Original name: windows1###
title Microsoft Windows XP Professional - added by updategrub
    map (hd1) (hd0)
    map (hd0) (hd1)
    rootnoverify (hd1,0)
    chainloader +1

###Don't change this comment - YaST2 identifier: Original name: GrubOnsda###
title Grub2 in sda MBR
    rootnoverify (hd0)
    chainloader +1

....

So, what is the consequence for updategrub? Since the marker has been deleted, it will write a new one and add all kernel and chainloading entries it finds below that point. All chainloading entries will then be duplicated in Grub menu. We could certainly edit the /boot/grub/menu.lst and delete the chainloading entries above the marker line. Other workarounds would be:

  • set/uncomment CHAINLOADGRUB=no
    in /etc/updategrub/defaults, so that it won’t add chainloading entries anymore, in fact a very elegant solution.
  • apply the following patch to /usr/lib/bootloader/bootloader_entry, so that it won’t delete any entry (including the older openSUSE kernels that will have to be deleted manually):
--- /usr/lib/bootloader/bootloader_entry.orig   2011-05-05 06:59:52.937004863 -0700
+++ /usr/lib/bootloader/bootloader_entry        2011-05-05 04:12:30.892338883 -0700
@@ -260,7 +260,6 @@
                        update_bootloader --image /boot/$image \
                                          --initrd /boot/$initrd \
                                          --remove \
-                                         --force \
                                           || exit 1
                fi

This is actually the option I prefer. I don’t care having to delete one or two boot entries if it prevents upate-bootloader from removing 20 others.

  • Get an average Perl programmer to have a look at /sbin/update-bootloader
  • Wait until they fix this file and write a bug report for me … cause I’m not good at that.

updategrub 1.5

Version 1.5 of updategrub adds an extra check to work around a bug I have observed in linux-boot-prober since the first os-prober versions installed on Ubuntu. Since updategrub uses os-prober as well, this bug affects both update-grub under Ubuntu and updategrub under openSUSE and other Legacy Grub based distros.

More precisely it affects all but openSUSE distros because it concerns openSUSE’s menu.lst. openSUSE’s menu.lst is not parsed while running updategrub under openSUSE, because the host system root partition is ignored. Its purpose is to add boot entries for other systems, not write or modify native boot entries. Thus, when you run updategrub under openSUSE, os-prober will skip the openSUSE root partition, and linux-boot-prober will read the boot entries in the menu.lst files on all other Linux root partitions detected by os-prober. It will read them “as is”, meaning that if these partitions contain errors, such as wrong device number, wrong partition UUID or anything else, the boot entries added by update-grub and updategrub will contain the same errors.

What happens if the different menu.lst files include many boot entries for other Linux distro kernels? This is common in complex multiboot setups where you want each Linux distro to use its own Grub and be able to boot any other (whether you added the entries manually or with updategrub). In most cases, it works pretty well. While scanning Fedora’s menu.lst on a given partition, linux-boot-prober retrieves only Fedora boot entries; while scanning Mandriva’s menu.lst, it retrieves only Mandriva boot entries; same for ArchLinux, Gentoo, Ubuntu and other Grub2-based systems (where it doesn’t seem to parse the boot menu). However, linux-boot-prober has problems with openSUSE’s menu.lst - which again doesn’t matter under openSUSE itself. If this menu.lst contains Mandriva boot entries, linux-boot-prober will retrieve these entries and pass them to update-grub/updategrub which in turn will add a completely wrong boot entry in the menu. I have lived with this bug and removed these entries manually from both Legacy and Grub2 boot menus until I decided that enough was enough and finally added an extra check in updategrub. The new checkroot function makes sure that the root partition currently read by linux-boot-prober matches the one specified in the kernel root option (by device name, UUID, /dev/disk/by-uuid symlinks or label).

The feature is enabled by default. If it doesn’t work as expected for you (i.e if it skips valid boot entries), you can disable this extra check by adding the following option in /etc/updategrub/defaults:

CHECKROOT=no 

Illustration of the bug

  • snip of Ubuntu’s /boot/grub/grub.cfg
### BEGIN /etc/grub.d/30_os-prober ###
menuentry "openSUSE 11.4 (Celadon) - kernel 2.6.37.6-0.5 (Desktop) (on /dev/sda11)" {
	insmod part_msdos
	insmod ext2
	**set root='(hd0,msdos11)'**
	search --no-floppy --fs-uuid --set 86949758-5b38-4f29-9f03-da0b2c055b8e
	linux /boot/vmlinuz-2.6.37.6-0.5-desktop root=/dev/disk/by-uuid/86949758-5b38-4f29-9f03-da0b2c055b8e resume=/dev/disk/by-uuid/ba9db764-a47e-48d9-b6f9-b627b57c0287 splash=silent quiet showopts vga=794
	initrd /boot/initrd-2.6.37.6-0.5-desktop
}
menuentry "Mandriva 2010.2 (Farman) - kernel 2.6.38.8 (desktop) (on /dev/sdb6)" {
	insmod part_msdos
	insmod ext2
	set root='(hd1,msdos6)'
	search --no-floppy --fs-uuid --set 4ecf3517-c7e9-47a1-8930-13a4e188e730
	linux /boot/vmlinuz-2.6.38.8-desktop-69mib BOOT_IMAGE=2.6.38.8-desktop-69mib root=UUID=4ecf3517-c7e9-47a1-8930-13a4e188e730 resume=UUID=ba9db764-a47e-48d9-b6f9-b627b57c0287 splash=silent vga=794
	initrd /boot/initrd-2.6.38.8-desktop-69mib.img
}
menuentry "Mandriva 2010.2 (Farman) - kernel 2.6.38.8 (on /dev/sda11)" {
	insmod part_msdos
	insmod ext2
	**set root='(hd0,msdos11)'**
	search --no-floppy --fs-uuid --set 86949758-5b38-4f29-9f03-da0b2c055b8e
	linux /boot/vmlinuz BOOT_IMAGE=linux root=UUID=4ecf3517-c7e9-47a1-8930-13a4e188e730 resume=UUID=ba9db764-a47e-48d9-b6f9-b627b57c0287 splash=silent vga=794
	initrd /boot/initrd.img
}

The second Mandriva boot entry added by update-grub is obviously wrong: the root partition reported is the same as for openSUSE (hd0,msdos11) but the kernel root options refer to different partitions - which are by the way correct in both cases (meaning that linux-boot-prober parses Grub2 entries in a different way). Notice that neither openSUSE nor Mandriva use separate /boot partitions!

  • snip of /boot/grub/menu.lst created by updategrub (previous to version 2.4x) under Mandriva, Fedora, Archinux (but NOT openSUSE):
title Mandriva 2010.2 (Farman) - kernel 2.6.38.8
    root (hd1,5)
    kernel /boot/vmlinuz BOOT_IMAGE=linux root=UUID=4ecf3517-c7e9-47a1-8930-13a4e188e730 resume=UUID=ba9db764-a47e-48d9-b6f9-b627b57c0287 splash=silent vga=794
    initrd /boot/initrd.img

# openSUSE 11.4 (x86_64) on /dev/sda11
title openSUSE 11.4 (Celadon) - kernel 2.6.37.6-0.5 (Desktop)
  **  root (hd0,10)**
    kernel /boot/vmlinuz-2.6.37.6-0.5-desktop root=/dev/disk/by-uuid/86949758-5b38-4f29-9f03-da0b2c055b8e resume=/dev/disk/by-uuid/ba9db764-a47e-48d9-b6f9-b627b57c0287 splash=silent quiet showopts vga=0x31a
    initrd /boot/initrd-2.6.37.6-0.5-desktop

# **openSUSE** 11.4 (x86_64) on /dev/sda11
title **Mandriva** 2010.2 (Farman) - kernel 2.6.38.8
    **root (hd0,10)**
    kernel /boot/vmlinuz BOOT_IMAGE=linux root=UUID=4ecf3517-c7e9-47a1-8930-13a4e188e730 resume=UUID=ba9db764-a47e-48d9-b6f9-b627b57c0287 splash=silent vga=794
    initrd /boot/initrd.img

In the example above it becomes even clearer that the third boot entry is wrong: linux-boot-prober retrieved the latest Mandriva boot entry from openSUSE’s menu.lst. The device (hd0,10) is wrong (since Mandriva is on (hd1,5) and the partitition’s UUID provided in the kernel root option is wrong too (it is openSUSE’s root partition and not Mandriva’s). The function checkroot in version 1.5 will notice that and reject this boot entry.

  • linux-boot-prober output:
/dev/sda11:/dev/sda11:openSUSE 11.4 (Celadon) - kernel 2.6.37.6-0.5 (Desktop):/boot/vmlinuz-2.6.37.6-0.5-desktop:/boot/initrd-2.6.37.6-0.5-desktop:root=/dev/disk/by-uuid/86949758-5b38-4f29-9f03-da0b2c055b8e resume=/dev/disk/by-uuid/ba9db764-a47e-48d9-b6f9-b627b57c0287 splash=silent quiet showopts vga=0x31a
/dev/sda11:/dev/sda11:openSUSE 11.4 (Celadon) - kernel 2.6.37.6-0.5 (Failsafe):/boot/vmlinuz-2.6.37.6-0.5-desktop:/boot/initrd-2.6.37.6-0.5-desktop:root=/dev/disk/by-uuid/86949758-5b38-4f29-9f03-da0b2c055b8e showopts apm=off noresume edd=off powersaved=off nohz=off highres=off processor.max_cstate=1 nomodeset x11failsafe vga=0x31a
/dev/sda11:/dev/sda11:Mandriva 2010.2 (Farman) - kernel 2.6.38.8:/boot/vmlinuz:/boot/initrd.img:BOOT_IMAGE=linux root=UUID=4ecf3517-c7e9-47a1-8930-13a4e188e730 resume=UUID=ba9db764-a47e-48d9-b6f9-b627b57c0287 splash=silent vga=794

It should NOT show the Mandriva boot entry on partition sda11.

I added support for chainloading Grub 1.99 in updategrub - half support actually, as I don’t have too much time to look in the compressed core now. It concerns only other Grub chainload entries that can be optionally added by updategrub. When updategrub finds Grub2 1.97/1.98 signature, it doesn’t chainload the bootsector - as it would do for Legacy Grub - but boots the core image instead with such an entry in menu.lst:

kernel /boot/grub/core.img

Before writing this entry, it has to make sure that the core.img is present on the root partition - the one specified in root (hdX,N) - by jumping to the sector (+1) , which offset is written in the Grub bootsector and looking for the partition containing /boot/grub at this location. The following example looks for “stage2” (although the term is not used for Grub2) in sda6.

corepos=$(hexdump -v -s 92 -n 4 -e '"%u"' /dev/sda6)
dd if=/dev/sda count=1 bs=512 skip=$(($corepos + 1)) 2>/dev/null | strings

and - since it finds it - displays:

(,msdos6)/boot/grub

And so we know that core.img should be in sda6 and we can use root=(hd0,5) and the following entry in Legacy Grub syntax to chainload Grub2:

# Grub 2 on /dev/sda6
title Grub2 on /dev/sda6
root (hd0,5)
kernel /boot/grub/core.img
boot

Although booting the core.img is different from chainloading the bootsector, what you get to see is pretty much the same thing: the Grub2 boot menu.

As it was too simple, they had to change that. lol!

Since Grub 1.99 - used in Ubuntu Oneiric and I guess Natty too - the core is compressed and you can not find out the location (meaning the partition) to boot without doing some calculation and uncompressing the core first. I did it in findgrub since version 3.5 by adding the function lzmacore - widely inspired from the bootinfo script. I might implement this function in updategrub at some point, but in the meantime, I decided to chainload the bootsector - not ideal but better than before.

The latest updategrub version 1.5.5 is available in my repo. The package includes findgrub 3.5.1.
The code can be viewed here: http://www.unixversal.com/linux/openSUSE/updategrub

Ooops! I forgot to remove this line from the code:

5c5
< #: Last Edit   : Sun Nov 13 16:46:28 PST 2011
---
> #: Last Edit   : Mon Nov 14 10:32:41 PST 2011
222d221
< GRUBS=("/dev/sda11:(hd0,10):suse" "/dev/sdb6:(hd1,5):arch")

I fixed it. The package has been scheduled for rebuilding…

I noticed - while running updategrub on openSUSE 12.1 and Fedora 15 (using Legacy Grub) - that if failed to add Ubuntu when Ubuntu’s partition is not mounted. The problem comes from os-prober, not updategrub, not detecting ubuntu Oneiric and probably Natty, but not Maverick - I suspect more generally all Oses using Grub2 1.99 with compressed core. Mounting Ubuntu’s partition once solves the issue - even if you unmount it afterwards an run updategrub again.

It seems to be fixed in os-prober 1.49. I just updated it in my repo.

I added support for bootchart in updategrub.
You can add a bootchart entry for the current kernel with

sudo updategrub -i -b

And remove it with

sudo updategrub -i

bootchart will be installed if it’s missing.

  • The option -i (or --interactive) is actually not required … but is prettier.

You can also hide or unhide the bootchart entry (or other boot entries added by updategrub) by running:

sudo grubmenu

Don’t forget to rename /var/log/bootchart.png before booting with bootchart the second time if you want to compare the boot processes of systemd and system V (for example).

http://img59.imageshack.us/img59/2116/updategrub.png