#! /bin/sh
#----------------------------------------------------------------------------
# /var/install/bin/update-bootloader - update boot configuration
#
# Creation   :  2008-08-14 tb
# Last Update:  $Id$
#
# Copyright (c) 2001-@@YEAR@@ the eisfair team, team(at)eisfar(dot)org
#
# This program 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.
#----------------------------------------------------------------------------

. /var/install/include/eislib

do_debug="no"
system="$(lsb_release -ds)"


#-----------------------------------------------------------------------------
# show help
#-----------------------------------------------------------------------------
show_help()
{
cat <<EOF

Update boot configuration

Usage: update-bootloader [OPTIONS]

options:
    --generate-new     create a new bootloader configuration
    --use-existing     activate a existing bootloader
                       configuration for a new initrd

EOF

}

#-----------------------------------------------------------------------------
# when running in vmware, issue this warning
#-----------------------------------------------------------------------------
print_vmware_warning()
{
    if lscpu | grep -qi 'vmware'
    then
        echo
        mecho --warn 'Please stop this virtual machine and set'
        mecho --warn '     disk.EnableUUID = "TRUE"'
        mecho --warn 'in vmx file of this virtual machine.'
        mecho --warn 'Then install this package again.'
        echo
    fi
}

#-----------------------------------------------------------------------------
# read a single value for a given key from lilo.conf
#-----------------------------------------------------------------------------
lookup_value_from_lilo_conf()
{
    local key="${1}"
    local value=$(egrep "${key}[[:blank:]]+=" /etc/lilo.conf |
           grep -v '^#'      |
           head -1           |
           cut -d'=' -f2-    |
           sed 's#"##g'      |
           sed 's#[#].*$##g' |
           sed -e 's#^[[:blank:]]*##g' -e 's#[[:blank:]]*$##g')
    echo "${value}"
}

#-----------------------------------------------------------------------------
# read a single value for a given key from lilo.conf (apply filter)
#-----------------------------------------------------------------------------
lookup_value_from_lilo_conf_with_filter()
{
    local key="${1}"
    local filter="${2}"
    local value=$(egrep "${key}[[:blank:]]+=" /etc/lilo.conf |
           grep -v '^#'        |
           grep -v "${filter}" |
           head -1             |
           cut -d'=' -f2-      |
           sed 's#"##g'        |
           sed 's#[#].*$##g'   |
           sed -e 's#^[[:blank:]]*##g' -e 's#[[:blank:]]*$##g')
    echo "${value}"
}

#-----------------------------------------------------------------------------
# read a single value for a given key from lilo.conf if it is an id
#-----------------------------------------------------------------------------
lookup_id_from_lilo_conf()
{
    local key="${1}"

    if egrep "${key}[[:blank:]]+=" /etc/lilo.conf |
       grep -v '^#'                               |
       head -1                                    |
       grep -q "/dev/disk/by-id/"
    then
        # device is converted to /dev/disk/by-id/
        lookup_value_from_lilo_conf "${key}"
    fi
}

#-----------------------------------------------------------------------------
# read a single value for a given key from lilo.conf if it is an uuid
#-----------------------------------------------------------------------------
lookup_uuid_from_lilo_conf()
{
    local key="${1}"

    if egrep "${key}[[:blank:]]+=" /etc/lilo.conf |
       grep -v '^#'                                   |
       head -1                                        |
       grep -q "UUID"
    then
        echo $(egrep "${key}[[:blank:]]+=" /etc/lilo.conf |
               grep -v '^#' |
               head -1 |
               grep "UUID" |
               cut -d'"' -f2)
    fi
}

#-----------------------------------------------------------------------------
# lookup device for a given mount point or path
#-----------------------------------------------------------------------------
lookup_device_from_path()
{
    local mountpoint="${1}"

    hex_dev_number=$(LANG=C; stat ${mountpoint} |
                     grep Device: |
                     cut -f 1 -d '/' |
                     cut -f 2 -d ' ' |
                     sed 's#h##g')

    if [ "${#hex_dev_number}" -lt "4" ]
    then
        hex_dev_number="0${hex_dev_number}"
    fi

    if [ "${#hex_dev_number}" -eq "4" ]
    then
        local major=$(printf "%d\n" "0x${hex_dev_number:0:2}")
        local minor=$(printf "%d\n" "0x${hex_dev_number:2:2}")
    else
        local major=$(printf "%d\n" "0x${hex_dev_number:0:3}")
        local minor=$(printf "%d\n" "0x${hex_dev_number:3:2}")
    fi

    for file in $(find /sys/dev/block -name "${major}:${minor}")
    do
        source ${file}/uevent
        echo "/dev/${DEVNAME}"
    done
}

#-----------------------------------------------------------------------------
# read device file from link in /dev/disk/by_id/*
#-----------------------------------------------------------------------------
lookup_device_from_link()
{
    local id="${1}"
    echo $(readlink "${id}" | sed 's#^\.\.\/\.\.\/#\/dev\/#g')
}

#-----------------------------------------------------------------------------
# check if device is a virtual disk
#-----------------------------------------------------------------------------
is_disk_a_virtual_device()
{
    echo "${1}" | grep -q "^/dev/xvd"
    return "${?}"
}

#-----------------------------------------------------------------------------
# check if device is a softraid disk
#-----------------------------------------------------------------------------
is_disk_a_softraid_device()
{
    echo "${1}" | grep -q '^/dev/md'
    return "${?}"
}

#-----------------------------------------------------------------------------
# read list of disks in /dev/disk/by_id/* and lookup the id for the device
#-----------------------------------------------------------------------------
lookup_disk_id_from_list()
{
    local link=''
    local disk_device="${1}"
    if [ ! -d /dev/disk/by-id ]
    then
        return
    fi

    local disk_list=$(ls /dev/disk/by-id/* |
                     grep -v '\-part'      |
                     grep -v 'wwn'         |
                     grep -v 'nvme-nvme.')

    for link in ${disk_list}
    do
        local newdevice="$(lookup_device_from_link ${link})"
        if [ "${disk_device}" = "${newdevice}" ]
        then
            echo "${link}"
            return
        fi
    done
}

#-----------------------------------------------------------------------------
# print disks in /dev/disk/by_id/* for debug purpose
#-----------------------------------------------------------------------------
print_disk_ids_from_list()
{
    if [ ! -d /dev/disk/by-id ]
    then
        return
    fi

    local disk_list=$(ls /dev/disk/by-id/* |
                     grep -v '\-part'      |
                     grep -v 'wwn'         |
                     grep -v 'nvme-nvme.')

    mecho --info "Entries in /dev/disk/by-id:"

    for link in ${disk_list}
    do
        local newdevice="$(lookup_device_from_link ${link})"
        echo "  ${newdevice} --> ${link}"
    done
}

#-----------------------------------------------------------------------------
# read list of uuids in /dev/disk/uuid/* and lookup the uuid for the device
#-----------------------------------------------------------------------------
lookup_volume_uuid_from_list()
{
    local link=''
    local disk_device="${1}"
    if [ ! -d /dev/disk/by-uuid ]
    then
        return
    fi

    local uuid_list=$(ls /dev/disk/by-uuid/*)

    for link in ${uuid_list}
    do
        local newdevice="$(lookup_device_from_link ${link})"
        if [ "${disk_device}" = "${newdevice}" ]
        then
            link=$(echo ${link} | sed 's#/dev/disk/by-uuid/##g')
            echo "UUID=${link}"
            return
        fi
    done
}

#-----------------------------------------------------------------------------
# print uuids in /dev/disk/by_uuid/* for debug purpose
#-----------------------------------------------------------------------------
print_volume_uuids_from_list()
{
    if [ ! -d /dev/disk/by-uuid ]
    then
        return
    fi

    local uuid_list=$(ls /dev/disk/by-uuid/*)

    mecho --info "Entries in /dev/disk/by-uuid:"

    for link in ${uuid_list}
    do
        local newdevice="$(lookup_device_from_link ${link})"
        echo "  ${newdevice} --> UUID=${link}"
    done
}

#-----------------------------------------------------------------------------
# find suitable initrd name for kernel image
#-----------------------------------------------------------------------------
lookup_initrd_name()
{
    local kernel="${1}"

    if [ "${kernel}" == "kernel" ]
    then
        echo "/boot/initrd.gz"
        return
    fi

    if [ "${kernel}" == "old-kernel" ]
    then
        echo "/boot/old-initrd.gz"
        return
    fi

    local kernel_name=$(echo ${kernel} | cut -d'-' -f2-)
    echo "/boot/initrd-${kernel_name}.gz"
}

#-----------------------------------------------------------------------------
# inspect /boot for kernel images in correct order
#-----------------------------------------------------------------------------
lookup_kernel_images()
{
    local kernels="kernel"
    local kernel

    if [ -L "/boot/kernel" ]
    then
        # new configuration (prefer versioned kernels over old-kernel)
        local versioned_kernels="$(ls /boot/kernel-* 2> /dev/null |
                     sed 's#/boot/##g'  |
                     sort -r)"

        for kernel in ${versioned_kernels}
        do
            local initrd_name="$(lookup_initrd_name ${kernel})"
            if [ -f "${initrd_name}" ]
            then
                kernels="${kernels} ${kernel}"
            fi
        done

        if [ -f "/boot/old-kernel" ]
        then
            kernels="${kernels} old-kernel"
        fi
    else
        # old configuration (prefer old-kernel over versioned kernels)
        if [ -f "/boot/old-kernel" ]
        then
            kernels="${kernels} old-kernel"
        fi

        local versioned_kernels="$(ls /boot/kernel-* 2> /dev/null |
                     sed 's#/boot/##g'  |
                     sort -r)"

        # only add kernel images with valid initrd
        for kernel in ${versioned_kernels}
        do
            local initrd_name="$(lookup_initrd_name ${kernel})"
            if [ -f "${initrd_name}" ]
            then
                kernels="${kernels} ${kernel}"
            fi
        done
    fi

    echo "${kernels}"
}

#-----------------------------------------------------------------------------
# lookup name of kernel for label entry (max length = 15 characters)
#-----------------------------------------------------------------------------
lookup_kernel_name()
{
    local kernel="${1}"

    if [ "${kernel}" == "old-kernel" ]
    then
        echo "oldeis"
        return
    fi

    if [ "${kernel}" == "kernel" ]
    then
        # enable this to make kernel appear with local version name
#        kernel_name=$(strings /boot/${kernel} |  \
#                      egrep "^[0-9]+\.[0-9]+\.[0-9]+-eisfair-.*" |
#                      head -1 |                  \
#                      cut -d' ' -f1)
#        echo "$(echo ${kernel_name} | sed "s#${system}-##g")"
        echo "eis"
        return
    fi

    local kernel_name=$(echo ${kernel} | cut -d'-' -f2-)
    echo "$(echo ${kernel_name} | sed "s#${system}-##g")"
}

#-----------------------------------------------------------------------------
# add default append arguments, if not present
#-----------------------------------------------------------------------------
extend_append_args()
{
    local append="${1}"

    if [ -z "${append}" ]
    then
        # append-zeile existiert nicht, setze raid=noautodetect,
        # setze consoleblank=600 (beim 5er Kernel ist default 0)
        append="raid=noautodetect consoleblank=600"
    else
        # zeile existiert
        if ! echo "${append}" | grep -q 'raid=noautodetect'
        then
            # setze raid=noautodetect wenn nicht vorhanden,
            # setze consoleblank=600 (beim 5er Kernel ist default 0)
            append="${append} raid=noautodetect"
        fi
        # zeile existiert
        if ! echo "${append}" | grep -q 'consoleblank=600'
        then
            # setze raid=noautodetect wenn nicht vorhanden,
            # setze consoleblank=600 (beim 5er Kernel ist default 0)
            append="${append} consoleblank=600"
        fi
    fi

    if ! mount -t devtmpfs | grep -q '/dev '
    then
        # kein udev, alte base
        # Vorbereitung auf neue base mit udev
        if ! echo "${append}" | grep -q 'net.ifnames=0'
        then
            # net.ifnames=0 nicht gesetzt
            # notwendig fuer neue base mit udev
            append="${append} net.ifnames=0"
        fi
    fi

    echo "${append}"
}

#-----------------------------------------------------------------------------
# write old disk section to lilo.conf
#-----------------------------------------------------------------------------
write_disk_section()
{
    local disk_byid="${1}"
    local disk_dev="${2}"

    if [ -n "${disk_byid}" ]
    then
        echo "#disk = $disk"
        echo "disk = ${disk_byid}"
    else
        echo "disk = $disk"
    fi

    echo "       bios = 0x80"
    echo "max-partitions = $maxpartitions"
}

#-----------------------------------------------------------------------------
# write boot section to lilo.conf
#-----------------------------------------------------------------------------
write_boot_section()
{
    local boot_byid="${1}"
    local boot_dev="${2}"
    local raid_extra_boot="${3}"

    if [ -n "${boot_byid}" ]
    then
        echo "#boot = ${boot_dev}"
        echo "boot = ${boot_byid}"
    else
        echo "boot = ${boot_dev}"
    fi

    if [ -n "${raid_extra_boot}" ]
    then
        echo "raid-extra-boot = mbr"
    fi
}

#-----------------------------------------------------------------------------
# write kernel image to lilo.conf
#-----------------------------------------------------------------------------
write_kernel_image()
{
    local kernel="${1}"
    local root_uuid="${2}"
    local root_partition="${3}"
    local vga="${4}"
    local append_args="${5}"

    echo "image = /boot/${kernel}"

    if [ -n "${root_uuid}" ]
    then
        echo "  #root = ${root_partition}"
        echo "  root = \"${root_uuid}\""
    else
        echo "  root = ${root_partition}"
    fi

    echo "  label = $(lookup_kernel_name ${kernel})"
    echo "  initrd = $(lookup_initrd_name ${kernel})"

    if [ -n "${vga}" ]
    then
        echo "  vga = ${vga}"
    fi

    if [ -n "${append_args}" ]
    then
        echo "  append = \"${append_args}\""
    fi
}

#-----------------------------------------------------------------------------
# execute lilo command and check result
#-----------------------------------------------------------------------------
execute_lilo()
{
    if ! /sbin/lilo
    then
        mecho
        mecho --error "Something went wrong!"
        mecho
        if [ -f /etc/lilo.conf.OLD ]
        then
            mecho --info "Old /etc/lilo.conf was:"
            mecho ""
            mecho "$(cat /etc/lilo.conf.OLD)"
            mecho ""
            mecho --info "New generated /etc/lilo.conf was:"
            mecho ""
            mecho "$(cat /etc/lilo.conf)"
            mecho ""
        fi
        return 1
    else
        if ! "${quiet:-false}"
        then
            mecho --warn ""
            mecho --warn "Note:"
            mecho --warn "A new /etc/lilo.conf was written containing standard sections"
            mecho --warn "according to a normal updated eisfair installation."
            mecho --warn ""
            mecho --warn "You will find your old boot configuration in /etc/lilo.conf.OLD."
            mecho --warn "You have to put your own boot options into /etc/lilo.conf manually"
            mecho --warn "before you reboot."
            mecho --warn "Do not forget to run 'lilo' on the command line afterwards if you"
            mecho --warn "changed /etc/lilo.conf!"
            mecho --warn ""
            return 0
        fi
    fi
}

#-----------------------------------------------------------------------------
# create config
#-----------------------------------------------------------------------------
create_config()
{
    # read lilo configuration
    local max_partitions="$(lookup_value_from_lilo_conf 'max-partitions')"
    local disk_byid=''
    local disk_dev=''
    local boot_byid=''
    local boot_dev=''
    local root_uuid=''
    local root_dev=''
    local raid_extra_boot="$(lookup_value_from_lilo_conf 'raid-extra-boot')"
    local vga="$(lookup_value_from_lilo_conf_with_filter 'vga' 'normal')"
    local menu_scheme="$(lookup_value_from_lilo_conf 'menu-scheme')"
    local append_args="$(lookup_value_from_lilo_conf 'append')"

    if [ -z "${menu_scheme}" ]
    then
        menu_scheme='wr:bw:wr:Yr'
    fi

    if [ -n "${maxpartitions}" ]
    then
        # lookup boot disk device
        disk_dev="$(lookup_device_from_path '/boot')"
        if [ -z "${disk_dev}" ]
        then
            mecho --error "Failed to lookup boot disk device"
            exit 1
        fi

        # do translation only if physical device is present
        if is_disk_a_virtual_device "${disk_dev}" ||
           is_disk_a_softraid_device "${disk_dev}"
        then
            echo "device ${disk_dev} is not a physical disk,"
            echo "not converting it to /dev/disk/by-id/."
            disk_byid=''
        else
            disk_dev="$(echo ${disk_dev%[1-9]})"
            disk_dev="$(echo ${disk_dev%[p]})"
            disk_byid="$(lookup_disk_id_from_list ${disk_dev})"
            if [ -z "${disk_byid}" ]
            then
                mecho --error "Cannot translate disk device ${disk_dev} to /dev/disk/by-id/!"
                echo
                print_disk_ids_from_list
                print_vmware_warning
                exit 1
            fi
        fi
    fi

    # lookup boot device
    boot_dev="$(lookup_device_from_path '/boot')"
    if [ -z "${boot_dev}" ]
    then
        mecho --error "Failed to lookup block device for boot disk"
        exit 1
    fi

    # do translation only if physical device is present
    if is_disk_a_virtual_device "${boot_dev}" ||
       is_disk_a_softraid_device "${boot_dev}"
    then
        echo "boot device ${boot_dev} is not a physical disk,"
        echo "not converting it to /dev/disk/by-id/."
        boot_byid=''
    else
        boot_dev="$(echo ${boot_dev%[1-9]})" 
        boot_dev="$(echo ${boot_dev%[p]})"
        boot_byid="$(lookup_disk_id_from_list ${boot_dev})"
        if [ -z "${boot_byid}" ]
        then
            mecho --error "Cannot translate boot disk device ${boot_dev} to /dev/disk/by-id/!"
            echo
            print_disk_ids_from_list
            print_vmware_warning
            exit 1
        fi
    fi

    # lookup root partition
    root_partition="$(lookup_device_from_path '/')"
    if [ -z "${root_partition}" ]
    then
        mecho --error "Failed to lookup block device for root partition"
        exit 1
    fi

    root_uuid="$(lookup_volume_uuid_from_list ${root_partition})"
    if [ -z "${root_uuid}" ]
    then
        mecho --error "Cannot translate root disk partition ${root_partition} to /dev/disk/by-uuid/!"
        echo
        print_volume_uuids_from_list
        exit 1
    fi

    # extra processing of values
    append_args="$(extend_append_args "${append_args}")"

    kernel_images="$(lookup_kernel_images)"
    if [ -z "${kernel_images}" ]
    then
        mecho --error "No kernel images found!"
        exit 1
    fi

    # show values
    if [ "${do_debug}" = "yes" ]
    then
        if [ -n "${maxpartitions}" ]
        then
            echo -n "disk_byid       = "; mecho --info "${disk_byid}"
            echo -n "disk_dev        = "; mecho --info "${disk_dev}"
        fi
        echo -n "boot_byid       = "; mecho --info "${boot_byid}"
        echo -n "boot_dev        = "; mecho --info "${boot_dev}"
        echo -n "root_uuid       = "; mecho --info "${root_uuid}"
        echo -n "root_partition  = "; mecho --info "${root_partition}"
        echo -n "append_args     = "; mecho --info "${append_args}"
        echo -n "raid_extra_boot = "; mecho --info "${raid_extra_boot}"
        echo -n "vga             = "; mecho --info "${vga}"
        echo -n "menu_scheme     = "; mecho --info "${menu_scheme}"
        echo -n "kernel_images   = "; mecho --info "${kernel_images}"
        echo
    fi

    # backup old lilo config
    cp /etc/lilo.conf /etc/lilo.conf.OLD

    # write new config file
    {
        echo "#-----------------------------------------------------------------------------"
        echo "# /etc/lilo.conf - lilo bootloader configuration"
        echo "# created: ${EISDATE} ${EISTIME}"
        echo "#-----------------------------------------------------------------------------"
        echo
        echo "lba32"

        if [ -n "${maxpartitions}" ]
        then
            write_disk_section "${disk_byid}" "${disk_dev}"
        fi

        write_boot_section "${boot_byid}" "${boot_dev}" "${raid_extra_boot}"

        echo "read-only"
        echo "prompt"
        echo "timeout = 50"
        echo "vga = normal"
        echo "menu-scheme = ${menu_scheme}"

        for kernel in ${kernel_images}
        do
            write_kernel_image "${kernel}" \
                "${root_uuid}"             \
                "${root_partition}"        \
                "${vga}"                   \
                "${append_args}"
        done
    } > /etc/lilo.conf

    if [ ${?} -ne 0 ]
    then
        mecho --error "Failed to write to /etc/lilo.conf!"
        exit 1
    fi

    if ! execute_lilo
    then
        # restore original lilo.conf
        if [ -f /etc/lilo.conf.OLD ]
        then
           cp /etc/lilo.conf.OLD /etc/lilo.conf
           rm -f /etc/lilo.conf.OLD
        fi
        exit 1
    fi
}

#-----------------------------------------------------------------------------
# main program
#-----------------------------------------------------------------------------
main()
{
    while [ "${1:0:2}" = "--" ]
    do
        case "${1}" in
        --help)
            shift
            show_help
            exit 0
            ;;
        --generate-new)
            shift
            create_config
            exit 0
            ;;
        --use-existing)
            shift
            quiet=true
            execute_lilo
            exit 0
            ;;
        *)
            echo "error: invalid switch \"${1}\"!" >&2
            show_help
            exit 1
            ;;
         esac
    done

    # this is only for older kernel that do not use the parameter
    # and it is depracated!
    if [ -z "${@}" ]
    then
        create_config
    else
        show_help
        exit 0
    fi
}

#-----------------------------------------------------------------------------
# call function main
#-----------------------------------------------------------------------------
main "${@}"

#-----------------------------------------------------------------------------
# end
#-----------------------------------------------------------------------------