#! /bin/sh # Install GRUB on your drive. # Copyright (C) 1999,2000,2001,2002,2003,2004 Free Software Foundation, Inc. # # This file 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 2 of the License, or # (at your option) any later version. # # This program 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 this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Initialize some variables. prefix=/usr exec_prefix=/usr sbindir=/sbin datadir=/usr/share PACKAGE=grub VERSION=0.97 host_cpu=x86_64 host_os=linux-gnu host_vendor=redhat pkgdatadir=${datadir}/${PACKAGE}/${host_cpu}-${host_vendor} grub_shell=${sbindir}/grub mdadm=${sbindir}/mdadm log_file=/tmp/grub-install.log.$$ img_file=/tmp/grub-install.img.$$ rootdir= grub_prefix=/boot/grub install_drives= install_device= no_floppy= force_lba= recheck=no debug=no justcopy=no # look for secure tempfile creation wrappers on this platform if test -x /bin/tempfile; then mklog="/bin/tempfile --prefix=grub" mkimg="/bin/tempfile --prefix=grub" elif test -x /bin/mktemp; then mklog="/bin/mktemp /tmp/grub-install.log.XXXXXX" mkimg="/bin/mktemp /tmp/grub-install.img.XXXXXX" else mklog="" mkimg="" fi # Usage: usage # Print the usage. usage () { cat <. EOF } # Usage: convert os_device # Convert an OS device to the corresponding GRUB drive. # This part is OS-specific. convert () { # First, check if the device file exists. if test -e "$1"; then : else echo "$1: Not found or not a block device." 1>&2 exit 1 fi # Break the device name into the disk part and the partition part. case "$host_os" in linux*) # formats that need to be handled (disk name -> partition name): # floppies: /dev/fd0 # normal sd/hd devices: /dev/hda -> /dev/hda3 # md: /dev/md0 -> /dev/md0p0 # ide raid devs: /dev/ide/host0/bus0/target0/lun0/disc # -> /dev/ide/host0/bus0/target0/lun0/part1 # cciss: /dev/cciss/c0d0 -> /dev/cciss/c0d0p1 # mpath devs: /dev/mapper/mpath0 -> /dev/mapper/mpath0p1 # /dev/mapper/lalala -> /dev/mapper/lalalap1 # dmraid devs: /dev/mapper/via_abcdef -> /dev/mapper/via_abcdefp1 # /dev/mapper/isw_Volume0_abcdef -> /dev/mapper/isw_Volume0_abcdefp1 # the known list (to me) is (X means we should handle it): # X asr_[unfettered crap] # hpt[0-9]+x_[0-9]+-[0-9]+ # X hpt[0-9]+x_[0-9]+ # hpt[0-9]+x_SPARE # isw_[a-z]+_[a-z]+[0-9]+ # X isw_[a-z]+ # jm_[0-9]+-[0-9]+ # X jm_[0-9]+ # lsi_[0-9]+-[0-9]+ # X lsi_[0-9]+ # nvidia_[a-z]+-[0-9]+ # X nvidia_[a-z]+ # pdc_[a-z]+-[0-9]+ # X pdc_[a-z]+ # sil_[0-9]+-[0-9]+ # X sil_[0-9]+ # via_[a-z]+-[0-9]+ # X via_[a-z]+ # # more? tmp_disk=`echo "$1" | grep -v '/mapper/control$' | grep -v '/mapper/[[:alnum:]_]\+-[[:digit:]]\+$' | uniq | sed -e 's%\([shv]d[a-z]\)[0-9]*$%\1%' \ -e 's%\(/c[0-9]\+d[0-9]\+\).*$%\1%' \ -e 's%\(fd[0-9]*\)$%\1%' \ -e 's%/part[0-9]*$%/disc%' \ -e 's%\(/mapper/[[:alnum:]_-]\+\)\+p[[:digit:]]\+$%\1%'\ -e 's%\(/mapper/[[:alnum:]]\+\(_[[:alnum:]]\+\)\+\)\p[[:digit:]]\+$%\1%'` tmp_part=`echo "$1" | grep -v '/mapper/control$' | grep -v '/mapper/[[:alnum:]_]\+-[[:digit:]]\+$' | uniq | sed -e 's%.*/[shv]d[a-z]\([0-9]*\)$%\1%' \ -e 's%.*/c[0-9]\+d[0-9]\+p\([[:digit:]]\+\)%\1%' \ -e 's%.*/c[0-9]\+d[0-9]\+$%%' \ -e 's%.*/fd[0-9]\+$%%' \ -e 's%.*/floppy/[0-9]*$%%' \ -e 's%.*/\(disc\|part\([0-9]*\)\)$%\2%' \ -e 's%.*/mapper/[[:alpha:]]\+[[:digit:]]\+p\([[:digit:]]\+\)$%\1%' \ -e 's%.*/mapper/[[:alnum:]]\+\(_[[:alpha:]]\+[[:digit:]]*\)\+p\([[:digit:]]\+\)$%\2%' | grep -v '.*/mapper/.*'` ;; gnu*) tmp_disk=`echo "$1" | sed 's%\([sh]d[0-9]*\).*%\1%'` tmp_part=`echo "$1" | sed "s%$tmp_disk%%"` ;; freebsd* | kfreebsd*-gnu) tmp_disk=`echo "$1" | sed 's%r\{0,1\}\([saw]d[0-9]*\).*$%r\1%' \ | sed 's%r\{0,1\}\(da[0-9]*\).*$%r\1%'` tmp_part=`echo "$1" \ | sed "s%.*/r\{0,1\}[saw]d[0-9]\(s[0-9]*[a-h]\)%\1%" \ | sed "s%.*/r\{0,1\}da[0-9]\(s[0-9]*[a-h]\)%\1%"` ;; netbsd* | knetbsd*-gnu) tmp_disk=`echo "$1" | sed 's%r\{0,1\}\([sw]d[0-9]*\).*$%r\1d%' \ | sed 's%r\{0,1\}\(fd[0-9]*\).*$%r\1a%'` tmp_part=`echo "$1" \ | sed "s%.*/r\{0,1\}[sw]d[0-9]\([abe-p]\)%\1%"` ;; *) echo "grub-install does not support your OS yet." 1>&2 exit 1 ;; esac # Get the drive name. tmp_drive=`grep -v '^#' $device_map | grep "$tmp_disk *$" \ | sed 's%.*\(([hf]d[0-9][a-g0-9,]*)\).*%\1%'` # If not found, print an error message and exit. if test "x$tmp_drive" = x; then echo "$1 does not have any corresponding BIOS drive." 1>&2 exit 1 fi if test "x$tmp_part" != x; then # If a partition is specified, we need to translate it into the # GRUB's syntax. case "$host_os" in linux*) echo "$tmp_drive" | sed "s%)$%,`expr $tmp_part - 1`)%" ;; gnu*) if echo $tmp_part | grep "^s" >/dev/null; then tmp_pc_slice=`echo $tmp_part \ | sed "s%s\([0-9]*\)[a-g]*$%\1%"` tmp_drive=`echo "$tmp_drive" \ | sed "s%)%,\`expr "$tmp_pc_slice" - 1\`)%"` fi if echo $tmp_part | grep "[a-g]$" >/dev/null; then tmp_bsd_partition=`echo "$tmp_part" \ | sed "s%[^a-g]*\([a-g]\)$%\1%"` tmp_drive=`echo "$tmp_drive" \ | sed "s%)%,$tmp_bsd_partition)%"` fi echo "$tmp_drive" ;; freebsd* | kfreebsd*-gnu) if echo $tmp_part | grep "^s" >/dev/null; then tmp_pc_slice=`echo $tmp_part \ | sed "s%s\([0-9]*\)[a-h]*$%\1%"` tmp_drive=`echo "$tmp_drive" \ | sed "s%)%,\`expr "$tmp_pc_slice" - 1\`)%"` fi if echo $tmp_part | grep "[a-h]$" >/dev/null; then tmp_bsd_partition=`echo "$tmp_part" \ | sed "s%s\{0,1\}[0-9]*\([a-h]\)$%\1%"` tmp_drive=`echo "$tmp_drive" \ | sed "s%)%,$tmp_bsd_partition)%"` fi echo "$tmp_drive" ;; netbsd* | knetbsd*-gnu) if echo $tmp_part | grep "^[abe-p]$" >/dev/null; then tmp_bsd_partition=`echo "$tmp_part" \ | sed "s%\([a-p]\)$%\1%"` tmp_drive=`echo "$tmp_drive" \ | sed "s%)%,$tmp_bsd_partition)%"` fi echo "$tmp_drive" ;; esac else # If no partition is specified, just print the drive name. echo "$tmp_drive" fi } # Usage: resolve_symlink file # Find the real file/device that file points at resolve_symlink () { tmp_fname=$1 # Resolve symlinks while test -L $tmp_fname; do tmp_new_fname=`ls -al $tmp_fname | sed -n 's%.*-> \(.*\)%\1%p'` if test -z "$tmp_new_fname"; then echo "Unrecognized ls output" 1>&2 exit 1 fi # Convert relative symlinks case $tmp_new_fname in /*) tmp_fname="$tmp_new_fname" ;; *) tmp_fname="`echo $tmp_fname | sed 's%/[^/]*$%%'`/$tmp_new_fname" ;; esac done echo "$tmp_fname" } # Usage: is_raid1_device devicename # Returns 0 if devicename is a raid1 md device, 1 if it is not. is_raid1_device () { case "$host_os" in linux*) level=`$mdadm --query --detail $1 2>/dev/null | \ awk '/Raid Level :/ {print $4}'` if [ "$level" = "raid1" ]; then return 0 fi ;; esac return 1 } # Usage: find_real_devs device # Returns space separated list of devices for linux if device is # a raid1 device. In all other cases, the provided value is returned. find_real_devs () { source_device=$1 case "$host_os" in linux*) if is_raid1_device $source_device ; then list="" for device in `$mdadm --query --detail "${source_device}" | \ awk '/\/dev\/[^(md)]/ {print $7}'` ; do list="$list $device" done echo $list return 0 fi ;; esac echo $source_device return 0 } # Usage: stat_device file # Find major:minor of a device node. stat_device() { majmin=`stat -c "%t:%T" "$1" 2>/dev/null` if test -z "$majmin"; then echo "Could not find device for $1" 1>&2 exit 1 fi echo "$majmin" } # Usage: find_mapper_device file # Find a file in /dev/mapper with the same major:minor as the specified node. find_mapper_device() { if [ -b "$1" ]; then dev="$1" else mntpnt=`echo "$1" | sed 's,/,\\\\/,g'` dev=`awk '($2 ~ /'$mntpnt'/) { print $1 }' /etc/mtab` fi if test -z "$dev"; then echo "Could not find device for $1" 1>&2 exit 1 fi majmin=`stat_device $dev` for x in /dev/mapper/* ; do devmajmin=`stat_device "$x"` if [ "$majmin" == "$devmajmin" ]; then echo "$x" return 0 fi done return 1 } # Usage: find_device file # Find block device on which the file resides. find_device () { # For now, this uses the program `df' to get the device name, but is # this really portable? tmp_fname=`df $1/ | sed -n 's%.*\(/dev/[^ ]*\).*%\1%p'` if test -z "$tmp_fname"; then echo "Could not find device for $1" 1>&2 exit 1 fi ret_fname=`resolve_symlink $tmp_fname` || exit 1 tmp_fname=`find_mapper_device $ret_fname` if test -n "$tmp_fname"; then ret_fname="$tmp_fname" fi echo "$ret_fname" return 0 } copy_images() { # Copy the GRUB images to the GRUB directory. for file in ${grubdir}/stage1 ${grubdir}/stage2 ${grubdir}/*stage1_5; do rm -f $file || exit 1 done for file in \ ${pkgdatadir}/stage1 ${pkgdatadir}/stage2 ${pkgdatadir}/*stage1_5; do cp -f $file ${grubdir} 1>&2 || exit 1 done } dump_boot_block () { sync $grub_shell --batch $no_floppy --device-map=$device_map <$log_file dump ${root_drive}${tmp} ${img_file} quit EOF } install_boot_block () { # Before all invocations of the grub shell, call sync to make sure # the raw device is in sync with any bufferring in filesystems. sync # Now perform the installation. $grub_shell --batch $no_floppy --device-map=$device_map <>$log_file root $1 setup $force_lba --stage2=$grubdir/stage2 --prefix=$grub_prefix $2 quit EOF } # Check the arguments. for option in "$@"; do case "$option" in -h | --help) usage exit 0 ;; -v | --version) echo "grub-install (GNU GRUB ${VERSION})" exit 0 ;; --root-directory=*) rootdir=`echo "$option" | sed 's/--root-directory=//'` ;; --grub-shell=*) grub_shell=`echo "$option" | sed 's/--grub-shell=//'` ;; --no-floppy) no_floppy="--no-floppy" ;; --force-lba) force_lba="--force-lba" ;; --recheck) recheck=yes ;; --just-copy) justcopy=yes ;; # This is an undocumented feature... --debug) debug=yes ;; -*) echo "Unrecognized option \`$option'" 1>&2 usage exit 1 ;; *) if test "x$install_device" != x; then echo "More than one install_devices?" 1>&2 usage exit 1 fi install_device="${option}" ;; esac done # If the debugging feature is enabled, print commands. if test $debug = yes; then set -x fi # Initialize these directories here, since ROOTDIR was initialized. case "$host_os" in netbsd* | openbsd*) # Because /boot is used for the boot block in NetBSD and OpenBSD, use /grub # instead of /boot/grub. grub_prefix=/grub bootdir=${rootdir} ;; *) # Use /boot/grub by default. bootdir=${rootdir}/boot ;; esac grubdir=${bootdir}/grub device_map=${grubdir}/device.map if [ "$recheck" == "yes" ]; then if grep 'mapper' ${device_map} >/dev/null; then echo 'grub-install does not support reprobing of device.map when' 1>&2 echo 'using a device-mapper based boot device.' 1>&2 exit 1 fi fi # if they just want the images copied, copy the images and then exit if test $justcopy = yes; then copy_images exit 0 fi if test "x$install_device" = x; then echo "install_device not specified." 1>&2 usage exit 1 fi # Check if GRUB is installed. # This is necessary, because the user can specify "grub --read-only". set $grub_shell dummy if test -f "$1"; then : else echo "$1: Not found." 1>&2 exit 1 fi if test -f "$pkgdatadir/stage1"; then : else echo "${pkgdatadir}/stage1: Not found." 1>&2 exit 1 fi if test -f "$pkgdatadir/stage2"; then : else echo "${pkgdatadir}/stage2: Not found." 1>&2 exit 1 fi # Don't check for *stage1_5, because it is not fatal even if any # Stage 1.5 does not exist. # Create the GRUB directory if it is not present. test -d "$bootdir" || mkdir "$bootdir" || exit 1 test -d "$grubdir" || mkdir "$grubdir" || exit 1 copy_images # If --recheck is specified, remove the device map, if present. if test $recheck = yes; then mv $device_map ${device_map}.backup fi # Create the device map file if it is not present. if test -f "$device_map"; then : else # Create a safe temporary file. test -n "$mklog" && log_file=`$mklog` # Before all invocations of the grub shell, call sync to make sure # the raw device is in sync with any bufferring in filesystems. sync $grub_shell --batch $no_floppy --device-map=$device_map <$log_file quit EOF if grep "Error [0-9]*: " $log_file >/dev/null; then cat $log_file 1>&2 exit 1 fi rm -f $log_file fi # Make sure that there is no duplicated entry. tmp=`sed -n '/^([fh]d[0-9]*)/s/\(^(.*)\).*/\1/p' $device_map \ | sort | uniq -d | sed -n 1p` if test -n "$tmp"; then echo "The drive $tmp is defined multiple times in the new device map." 1>&2 if test $recheck = yes; then echo "Reverting to backed up copy." 1>&2 mv ${device_map}.backup $device_map fi exit 1 fi # Make sure device.map has at least one hd device grep -q "^(hd[0-9]\+)" $device_map if [ "x$?" != "x0" ]; then echo "No suitable drive was found in the generated device map." 1>&2 if test $recheck = yes; then echo "Reverting to backed up copy." 1>&2 mv ${device_map}.backup $device_map fi exit 1 fi # Check for INSTALL_DEVICE. case "$install_device" in /dev/*) install_device=`resolve_symlink "$install_device"` || exit 1 for install_drive in `find_real_devs $install_device` ; do install_drive=`convert $install_drive` || exit 1 if is_raid1_device $install_device; then install_drive=`echo $install_drive | sed 's/,[0-9]*)/)/'` fi if [ "x$install_drive" = "x" ]; then exit 1 fi install_drives="${install_drives} ${install_drive}" done unset install_drive if test "x$install_drives" = x ; then exit 1 fi ;; \([hf]d[0-9]*\)) install_drives="$install_device" ;; [hf]d[0-9]*) # The GRUB format with no parenthesis. install_drives="($install_device)" ;; *) echo "Format of install_device not recognized." 1>&2 usage exit 1 ;; esac unset install_device # Get the root drive. root_device=`find_device ${rootdir}` || exit 1 bootdir_device=`find_device ${bootdir}` || exit 1 # Check if the boot directory is in the same device as the root directory. if test "x$root_device" != "x$bootdir_device"; then # Perhaps the user has a separate boot partition. root_device=$bootdir_device grub_prefix="/grub" fi # Check if the root directory exists in the same device as the grub directory. grubdir_device=`find_device ${grubdir}` || exit 1 if test "x$grubdir_device" != "x$root_device"; then # For now, cannot deal with this situation. cat <&2 You must set the root directory by the option --root-directory, because $grubdir does not exist in the root device $root_device. EOF exit 1 fi # Make sure that GRUB reads the same images as the host OS. test -n "$mkimg" && img_file=`$mkimg` test -n "$mklog" && log_file=`$mklog` # There's not a real root device, so just pick the first if is_raid1_device $root_device ; then root_device=`find_real_devs $root_device | awk '{print $1}'` fi # Convert the root deviceto a GRUB drive. root_drive=`convert "$root_device"` || exit 1 if [ "x$root_drive" = x ]; then exit 1 fi for file in ${grubdir}/stage1 ${grubdir}/stage2 ${grubdir}/*stage1_5; do count=5 tmp=`echo $file | sed "s|^${grubdir}|${grub_prefix}|"` while test $count -gt 0; do dump_boot_block $root_drive $img_file if grep "Error [0-9]*: " $log_file >/dev/null; then : elif cmp $file $img_file >/dev/null; then break fi sleep 1 count=`expr $count - 1` done if test $count -eq 0; then echo "The file $file not read correctly." 1>&2 exit 1 fi done rm -f $img_file rm -f $log_file if ! test -e ${grubdir}/grub.conf ; then test -e ${grubdir}/menu.lst && ln -s ./menu.lst ${grubdir}/grub.conf fi # Create a safe temporary file. test -n "$mklog" && log_file=`$mklog` for install_drive in $install_drives; do # Convert the root deviceto a GRUB drive. root_drive=`convert "$root_device"` || exit 1 if [ "x$root_drive" = x ]; then exit 1 fi install_boot_block $root_drive $install_drive done if grep "Error [0-9]*: " $log_file >/dev/null ; then cat $log_file 1>&2 exit 1 fi if test $debug = yes; then cat $log_file 1>&2 fi rm -f $log_file # Prompt the user to check if the device map is correct. echo "Installation finished. No error reported." echo "This is the contents of the device map $device_map." echo "Check if this is correct or not. If any of the lines is incorrect," echo "fix it and re-run the script \`grub-install'." echo cat $device_map # Bye. exit 0