#!/bin/sh -e
#
#  Copyright (c) 2006 Vagrant Cascadian <vagrant@freegeek.org>
#
#  2006, Vagrant Cascadian <vagrant@freegeek.org>
#        Oliver Grawert <ogra@canonical.com>
#  2007, Vagrant Cascadian <vagrant@freegeek.org>
#        Scott Balneaves <sbalneav@ltsp.org>
#  2008, Vagrant Cascadian <vagrant@freegeek.org>
#  2012, Alkis Georgopoulos <alkisg@gmail.com>
#  2012, Vagrant Cascadian <vagrant@freegeek.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.
#
#  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, you can find it on the World Wide
#  Web at http://www.gnu.org/copyleft/gpl.html, or write to the Free
#  Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#

# this script is run either chrooted on the server, or by a client with write
# access to the NFS mount point. (much of this code was originally in
# server/ltsp-update-kernels). --vagrant 20060801

case "$0" in
    /etc/kernel/post*.d*)
        # Relaunch update-kernels with its correct basename so that
        # ltsp-client-functions includes /etc/ltsp/update-kernels.conf.
        QUIET=true exec /usr/share/ltsp/update-kernels "$@"
        ;;
esac

msg() {
    if [ "$QUIET" != "true" ]; then
       echo $@
    fi
}

# List kernel versions in a descending order, while also respecting the e.g.
#   LIST_KERNELS="generic generic-pae *"
# order that the user may have put in $CHROOT/etc/ltsp/update-kernels.conf.
# Distros are required to set e.g. KERNEL_PREFIX="vmlinuz-*', KERNEL_SUFFIX="".
# The * goes where the version is expected to go.
kernel_split() {
    local orig_flags kernels arch loop_kernels kernel
    # Save values of flags, and restore them later.
    orig_flags=$(set +o)

    # Disable glob expansion for this function
    set -f
    for kernel in $(find "/boot/" -type f -name "$KERNEL_PREFIX$KERNEL_SUFFIX" -printf "%f\n"); do
        # Validate the "arch"
        if [ "${LIST_KERNELS}" = "ALL" ]; then
            LIST_KERNELS="*"
        fi
        for arch in ${LIST_KERNELS:-*}; do
            case "$kernel" in
                $KERNEL_PREFIX$arch$KERNEL_SUFFIX)
                    echo "$kernel"
                    break 1
                    ;;
            esac
        done
    done | sed -n "$KERNEL_NAMES" | sort -k 4,4V -k 3,3rV

    eval "$orig_flags"
}

kernel_versions(){
    for arch in ${LIST_KERNELS:-"ALL"} ; do
        LIST_KERNELS="$arch" kernel_split | awk '{print $3$4}'
    done
}

kernel_variants(){
    kernel_split | awk '{print $4}' | sort -u
}

# This also sources vendor functions and .conf file settings
. /usr/share/ltsp/ltsp-client-functions
require_root

BOOT=${BOOT:-"/boot"}
CHROOT_NAME=${CHROOT_NAME:-"$(detect_arch)"}

# Ensure default values for BOOT_METHODS, CMDLINE_LINUX_DEFAULTS, CMDLINE_NFS 
# and CMDLINE_NBD. Distros *should* ship an /etc/ltsp/update-kernels.conf with
# appropriate values for their distro.
BOOT_METHODS=${BOOT_METHODS:-"NFS NBD AOE"}
CMDLINE_LINUX_DEFAULTS=${CMDLINE_LINUX_DEFAULTS:-"ro init=/sbin/init-ltsp"}
CMDLINE_NFS=${CMDLINE_NFS:-"root=/dev/nfs ip=dhcp"}
CMDLINE_NBD=${CMDLINE_NBD:-"root=/dev/nbd0"}
CMDLINE_AOE=${CMDLINE_AOE:-"root=/dev/etherd/e0.0"}

# Set a default BOOTPROMPT_OPTS using the first defined in BOOT_METHODS
boot_method_default=$(echo $BOOT_METHODS | awk '{print $1}')
cmdline_method_default=$(eval echo '$CMDLINE_'$boot_method_default)
BOOTPROMPT_OPTS="$CMDLINE_LINUX_DEFAULTS $cmdline_method_default"

# A sed expression that matches all kernels and returns $FILE $NAME $VERSION $FLAVOR
# Example: ls /boot | sed -n "$KERNEL_NAMES" | sort -k 4,4V -k 3,3rV
KERNEL_NAMES=${KERNEL_NAMES:-'s/\(vmlinu[xz]-\)\([^-]*-[^-]*-\)\(.*\)/& \1 \2 \3/p'}

if [ -f /usr/lib/yaboot/yaboot ]; then
    cp -a -v /usr/lib/yaboot/yaboot $BOOT
    cat > $BOOT/yaboot.conf <<EOF
timeout=0
default=ltsp
root=/dev/ram

image=/ltsp/$CHROOT_NAME/vmlinux
        label=ltsp
        initrd=/ltsp/$CHROOT_NAME/initrd.img
        append="$BOOTPROMPT_OPTS"
EOF

    kversions=$(kernel_versions)
    if [ -n "$kversions" ]; then
        for version in $kversions ; do
            for method in $BOOT_METHODS ; do
                cat >> $BOOT/yaboot.conf <<EOF

image=/ltsp/$CHROOT_NAME/vmlinux-${version}
        label=ltsp-$method-$version
        initrd=/ltsp/$CHROOT_NAME/initrd.img-${version}
        append="$CMDLINE_LINUX_DEFAULT $(eval echo '$CMDLINE_'$method)"

EOF
            done
        done
    fi
else
    msg "Skipping yaboot configuration. install yaboot package if you need it."
fi

syslinux_dir=/usr/lib/syslinux
pxelinux_dir=/usr/lib/PXELINUX
test -d "$pxelinux_dir" || pxelinux_dir="$syslinux_dir"
syslinux_modules_dir=/usr/lib/syslinux/modules/bios
test -d "$syslinux_modules_dir" || syslinux_modules_dir="$syslinux_dir"

if [ -f $pxelinux_dir/pxelinux.0 ]; then
    PXECFG=$BOOT/pxelinux.cfg
    cp $pxelinux_dir/pxelinux.0 $BOOT

    # copy over variant with extended support for gPXE
    if [ -f "$pxelinux_dir/gpxelinux.0" ]; then
        cp $pxelinux_dir/gpxelinux.0 $BOOT
    fi

    # Needed by recent versions of pxelinux.
    if [ -f "$syslinux_modules_dir/ldlinux.c32" ]; then
        cp $syslinux_modules_dir/ldlinux.c32 $BOOT
    fi

    # copy the PXELINUX_DEFAULT helper program if installed, such as menu or
    # vesamenu
    if [ -f "$syslinux_modules_dir/$PXELINUX_DEFAULT.c32" ]; then
        cp "$syslinux_modules_dir/$PXELINUX_DEFAULT.c32" $BOOT
        case $PXELINUX_DEFAULT in
            # set a timeout, otherwise the menu will wait indefinitely
            menu) TIMEOUT=${TIMEOUT:-"50"}
                # libutil.c32 is needed by newer versions of menu
                if [ -f "$syslinux_modules_dir/libutil.c32" ]; then
                    cp "$syslinux_modules_dir/libutil.c32" $BOOT
                fi
                ;;
            vesamenu) TIMEOUT=${TIMEOUT:-"50"}
                # libutil.c32 is needed by newer versions of menu
                if [ -f "$syslinux_modules_dir/libutil.c32" ]; then
                    cp "$syslinux_modules_dir/libutil.c32" $BOOT
                fi
                # libcom32.c32 is needed by newer versions of vesamenu
                if [ -f "$syslinux_modules_dir/libcom32.c32" ]; then
                    cp "$syslinux_modules_dir/libcom32.c32" $BOOT
                fi
                ;;
            ifcpu64) IFCPU64=true ;;
        esac
    fi

    if [ ! -d $PXECFG ]; then
        mkdir $PXECFG
    fi

    # Remove all autogenerated menus.
    rm -f $PXECFG/ltsp* $PXECFG/default

    cat > $PXECFG/ltsp <<EOF
# This file is regenerated when update-kernels runs.  Do not edit
# directly, edit the client's /etc/ltsp/update-kernels.conf instead.

default ${PXELINUX_DEFAULT:-"ltsp-$boot_method_default"}
ontimeout ${ONTIMEOUT:-"ltsp-$boot_method_default"}
${TIMEOUT:+timeout $TIMEOUT}

EOF

    pxelinux_include_files=""
    for method in $BOOT_METHODS ; do
        pxelinux_include_files="$pxelinux_include_files ltsp-$method"
        # Get the preferred default kernel, preferred 32-bit kernel, or any 
        # kernel.
        version=$(LIST_KERNELS="$LIST_KERNELS_DEFAULT $LIST_KERNELS_32 ALL" kernel_versions | head -n 1)
    	cat > $PXECFG/ltsp-$method <<EOF
# This file is regenerated when update-kernels runs.
# Do not edit, see /etc/ltsp/update-kernels.conf instead.

label ltsp-$method
menu label LTSP, using $method
kernel vmlinuz${version:+-"$version"}
append ro initrd=initrd.img${version:+-"$version"} $CMDLINE_LINUX_DEFAULT $(eval echo '$CMDLINE_'$method)
ipappend ${IPAPPEND:-2}

EOF

    done

    if boolean_is_true "$IFCPU64" ; then
        if [ -f "$syslinux_modules_dir/ifcpu64.c32" ]; then
            cp "$syslinux_modules_dir/ifcpu64.c32" $BOOT
        fi
        # libcom32.c32 is needed by newer versions of ifcpu64
        if [ -f "$syslinux_modules_dir/libcom32.c32" ]; then
            cp "$syslinux_modules_dir/libcom32.c32" $BOOT
        fi
        for method in $BOOT_METHODS ; do
            pxelinux_include_files="$pxelinux_include_files ltsp-ifcpu64-$method"
            cat > $PXECFG/ltsp-ifcpu64-$method <<EOF
# This file is regenerated when update-kernels runs.
# Do not edit, see /etc/ltsp/update-kernels.conf instead.

label ltsp-ifcpu64-$method
menu label LTSP, using $method, Autodetect 64-bit, PAE or 32-bit kernels
kernel ifcpu64.c32
append ltsp-$method-64 -- ltsp-$method-PAE -- ltsp-$method-32

EOF
            # only generate an entry for the first boot method
            for type in 64 PAE 32 ; do
                # get appropriate kernel from LIST_KERNELS_64, LIST_KERNELS_PAE, 
                # and LIST_KERNELS_32 variables.
                case $type in
                    64) list_kernels="$LIST_KERNELS_64 $LIST_KERNELS_PAE $LIST_KERNELS_32" ;;
                    PAE) list_kernels="$LIST_KERNELS_PAE $LIST_KERNELS_32" ;;
                    32) list_kernels="$LIST_KERNELS_32" ;;
                esac
                # only return the newest kernel of appropriate type.
                version=$(LIST_KERNELS="$list_kernels ALL" kernel_versions | head -n 1)
            	cat >> $PXECFG/ltsp-ifcpu64-$method <<EOF

label ltsp-$method-$type
menu hide
menu label LTSP, using $method, with Linux $version
kernel vmlinuz-$version
append ro initrd=initrd.img-$version $CMDLINE_LINUX_DEFAULT $(eval echo '$CMDLINE_'$method)
ipappend ${IPAPPEND:-2}

EOF
            done
        done
    fi

    # Add entries for each kernel and boot method,
    # Which needs a corresponding CMDLINE_ for each defined method.
    # i.e. CMDLINE_NFS or CMDLINE_NBD
    # Also requires that CMDLINE_LINUX_DEFAULT is set to a sane value.
    kversions=$(kernel_versions)
    if [ -n "$kversions" ]; then
        for method in $BOOT_METHODS ; do
            pxelinux_include_files="$pxelinux_include_files ltsp-versions-$method"
            cat > $PXECFG/ltsp-versions-$method <<EOF
# This file is regenerated when update-kernels runs.
# Do not edit, see /etc/ltsp/update-kernels.conf instead.

menu begin ltsp-versions-$method
menu label Other LTSP boot options using $method

EOF
            for version in $kversions ; do
                cat >> $PXECFG/ltsp-versions-$method <<EOF

label ltsp-$method-$version
menu label LTSP, using $method, with Linux $version
kernel vmlinuz-$version
append ro initrd=initrd.img-$version $CMDLINE_LINUX_DEFAULT $(eval echo '$CMDLINE_'$method)
ipappend ${IPAPPEND:-2}

EOF
            done
            cat >> $PXECFG/ltsp-versions-$method <<EOF
menu end
EOF
        done
    fi

    if [ -f "$BOOT/memtest86+.bin" ]; then
        pxelinux_include_files="$pxelinux_include_files memtest"
        cat > $PXECFG/memtest <<EOF
# This file is regenerated when update-kernels runs.
# Do not edit, see /etc/ltsp/update-kernels.conf instead.
label memtest86+.bin
menu label Memory test
linux memtest86+.bin
EOF
    fi

    for file in $pxelinux_include_files ; do
        if [ -f "$PXECFG/$file" ]; then
            cat "$PXECFG/$file" >> $PXECFG/ltsp
        fi
    done

else
    msg "Skipping PXE configuration.  Install the syslinux package if you need it."
fi


if [ "$(detect_arch)" = "armhf" ] || [ "$(detect_arch)" = "armel" ]; then
    if which mkimage >/dev/null; then
        # Generate a boot script for use with versions of u-boot
        # supporting bootz.
        version=$(LIST_KERNELS="$LIST_KERNELS_DEFAULT $LIST_KERNELS_ARM ALL" kernel_versions | head -n 1)
        kernel_file="/ltsp/${CHROOT_NAME}/vmlinuz-${version}"
        initrd_file="/ltsp/${CHROOT_NAME}/initrd.img-${version}"
        fdt_dir="/ltsp/${CHROOT_NAME}/dtbs-${version}/"

        mkdir -p $BOOT

        cat > $BOOT/boot.ltsp << EOF
# Boot script for u-boot on arm systems

# Configure the console
if test -n \${console}; then
    setenv bootargs \${bootargs} console=\${console}
fi

setenv bootargs \${bootargs} $CMDLINE_LINUX_DEFAULT $(eval echo '$CMDLINE_'$boot_method_default)

# Compatibility variables for versions of u-boot without standardized
# variable names.
test -z "\${fdtfile}" && setenv fdtfile \${fdt_file}
test -z "\${fdt_addr_r}" && setenv fdt_addr_r \${fdtaddr}
test -z "\${fdt_addr_r}" && setenv fdt_addr_r \${fdt_addr}
test -z "\${kernel_addr_r}" && setenv kernel_addr_r \${loadaddr}
test -z "\${ramdisk_addr_r}" && setenv ramdisk_addr_r \${ramdiskaddr}
test -z "\${ramdisk_addr_r}" && setenv ramdisk_addr_r \${rdaddr}

tftpboot \${kernel_addr_r} \${serverip}:${kernel_file} \\
&& tftpboot \${fdt_addr_r} \${serverip}:${fdt_dir}\${fdtfile} \\
&& tftpboot \${ramdisk_addr_r} \${serverip}:${initrd_file} \\
&& echo booting LTSP $CHROOT_NAME linux $version \\
&& bootz \${kernel_addr_r} \${ramdisk_addr_r}:\${filesize} \${fdt_addr_r}
EOF

        mkimage -A arm -O Linux -T script -C none -a 0x0 -e 0x0 \
            -n 'LTSP boot script' -d $BOOT/boot.ltsp $BOOT/boot.scr \
            > /dev/null
	
        # Copy .dtb files to boot dir.
        dtb_dirs="/usr/lib/linux-image-${version}/"
        for dtb_dir in $dtb_dirs ; do
            if [ -d "${dtb_dir}" ]; then
                cp -r ${dtb_dir}/. $BOOT/dtbs-${version}/
            fi
        done

    else
        msg "Skipping ARM configuration.  Install the u-boot-tools package if you need it."
    fi
fi


# allow specifying a specific kernel image to update, from kernel postinst
if [ -f "$2" ]; then
    ALL_KERNELS="$2"
else
    ALL_KERNELS="$(find $BOOT -type f -name 'vmlinu*')"
fi

# look for symlinks, too, and put them after the "real" kernels
ALL_KERNELS="$ALL_KERNELS $(find $BOOT -type l -name 'vmlinu*')"

for kernel in $ALL_KERNELS ; do
    basename=$(basename "$kernel")
    initrd=initrd.img
    nbi=nbi.img

    case $basename in
        vmlinuz|vmlinux)
            # USE DEFAULT
        ;;
        vmlinu*.old) 
            initrd=$initrd.old
            nbi=$nbi.old
        ;;
        vmlinuz*) 
            version=${basename##vmlinuz-}
            initrd=$initrd-$version
            nbi=$nbi-$version
        ;;
        vmlinux*) 
            version=${basename##vmlinux-}
            initrd=$initrd-$version
            nbi=$nbi-$version
        ;;
    esac

    if [ -L "$kernel" ]; then
        basename="$(readlink $kernel)"
        if [ -f "$BOOT/$basename" ]; then
            case $basename in
                vmlinuz*)
                    version=${basename##vmlinuz-}
                ;;
                vmlinux*)
                    version=${basename##vmlinux-}
                ;;
            esac

            realnbi="nbi.img-$version"
            if [ -f "$BOOT/$realnbi" ]; then
                ln -sf $realnbi $BOOT/$nbi
            fi
        fi
    else
        if which mkelfImage >/dev/null; then
            # x86_64/amd64 i386 ia64(?): mkelfimage
            MKELFIMAGE_INITRD_OPT=""
            if [ -z "$MKELFIMAGE_OPTS" ]; then
                MKELFIMAGE_OPTS="$BOOTPROMPT_OPTS"
            fi
            if [ -f "$BOOT/$initrd" ]; then
                MKELFIMAGE_INITRD_OPT="--ramdisk=$BOOT/$initrd"
            fi
            mkelfImage --command-line="$MKELFIMAGE_OPTS" --output=$BOOT/$nbi.tmp \
                --kernel=$kernel $MKELFIMAGE_INITRD_OPT && mv $BOOT/$nbi.tmp $BOOT/$nbi
        elif which mkelf-linux >/dev/null; then
            # i386: mknbi
            MKELF_LINUX_INITRD_OPT=""
            if [ -z "$MKELF_LINUX_APPEND" ]; then
                MKELF_LINUX_APPEND="$BOOTPROMPT_OPTS"
            fi
            if [ -f "$BOOT/$initrd" ]; then
                MKELF_LINUX_INITRD_OPT="$BOOT/$initrd"
            fi
            mkelf-linux $MKELF_LINUX_OPTS --append="$MKELF_LINUX_APPEND" \
                -o $BOOT/$nbi $kernel $MKELF_LINUX_INITRD_OPT
        else
            if [ -z "$mkelf_seen" ]; then
                mkelf_seen=true
                msg "Skipping etherboot images.  Install the mkelfimage package if you need them."
            fi
        fi
        if which mknbi-linux >/dev/null ; then
            # i386: mknbi
            # generate an legacy-nbi.img for legacy versions of etherboot that
            # didn't support ELF

            MKNBI_LINUX_INITRD_OPT=""
            if [ -z "$MKNBI_LINUX_APPEND" ]; then
                MKNBI_LINUX_APPEND="$BOOTPROMPT_OPTS"
            fi
            if [ -f "$BOOT/$initrd" ]; then
                MKNBI_LINUX_INITRD_OPT="$BOOT/$initrd"
            fi
            mknbi-linux $MKNBI_LINUX_OPTS --append="$MKNBI_LINUX_APPEND" \
                -o $BOOT/legacy-"$nbi" $kernel $MKNBI_LINUX_INITRD_OPT

        fi
    fi
done
