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>/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 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>/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
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 ~~~