#! /bin/bash

# Copyright (c) 2007 XenSource, Inc.
#
# This script installs or updates the XE support packages installed in a guest.

os_distro=""
os_majorver=""
install_kernel=1
interactive=1

if [ -z "${DATADIR}" ] ; then
    DATADIR=$(dirname $0)
fi

usage()
{
    echo "usage: install.sh [-d <DISTRO> -m <MAJOR>] [-k]"
    echo ""
    echo "    -d <DISTRO>       Specifies the distro name."
    echo "    -m <MAJOR>        Specifies the major version of the distro release."
    echo "    -k                Do not update the kernel."
    echo "    -n                Non interactive mode."
    echo ""
    echo "The -d and -m options must be used together. If neither is given then"
    echo "auto-detection will be attempted."
    exit 1
}
while getopts d:hkm:n OPT ; do
    case $OPT in
	d) os_distro="$OPTARG" ;;
	k) install_kernel=0 ;;
	m) os_majorver="$OPTARG" ;;
	n) interactive=0 ;;

	h|\?) usage ;;
    esac
done

if [ -z "${os_distro}" -a -n "${os_majorver}" ] || \
   [ -n "${os_distro}" -a -z "${os_majorver}" ] ; then
    echo "Distribution name (-d) and major version (-m) must be specified"
    echo "together on the command line."
    exit 1
fi

if [ -z "${os_distro}" ] ; then
    if [ -z "${XE_LINUX_DISTRIBUTION}" ] ; then
	XE_LINUX_DISTRIBUTION=${DATADIR}/xe-linux-distribution
    fi

    if [ ! -f "${XE_LINUX_DISTRIBUTION}" ] ; then
	echo "Fatal Error: xe-linux-distribution script not found in ${DATADIR}."
	exit 1
    fi

    if [ ! -f "${DATADIR}/versions.deb" -o ! -f "${DATADIR}/versions.rpm" ] ; then
	echo "Fatal Error: versions.deb or versions.rpm not found in ${DATADIR}."
	exit 1
    fi

    eval $(${XE_LINUX_DISTRIBUTION})
    if [ $? -ne 0 -o -z "${os_distro}" -o -z "${os_majorver}" ] ; then
	echo "Fatal Error: Failed to determine Linux distribution and version."
	exit 1
    fi

    echo "Detected \`${os_name}' (${os_distro} version ${os_majorver})."
    echo
else
    echo "Distribution \`${os_distro}' version \`${os_majorver}' given on command line."
    echo
fi

ARCH=$(uname -m | sed -e 's/i.86/i386/g')
case "${ARCH}" in
    i386|x86_64) ;;
    *)
	echo "Architecture ${ARCH} is not supported"
	exit 1
	;;
esac

failure()
{
	echo "Unable to install guest packages for distribution"
	if [ -n "${os_name}" ] ; then
	    echo "${os_name} (${os_distro})."
	else
	    echo "${os_distro}."
	fi
	echo
	if [ $# -gt 0 ] ; then
	    echo $@
	    echo
	fi
	echo "You should manually install a version of"
	echo "xe-guest-utilities which is suitable for your"
	echo "distribution."
	exit 1
}

select_rpm_utilities()
{
    if [ -f "${DATADIR}/versions.rpm" ] ; then
	source ${DATADIR}/versions.rpm
	XGU=
	for p in $(eval echo \${XE_GUEST_UTILITIES_PKG_FILE_${ARCH}}) ; do
	    XGU="$XGU ${DATADIR}/${p}"
	done
    else
	echo "Warning: Guest utilities not found in ${DATADIR}."
    fi
}

select_pkgs_rhel()
{
    GUEST_PKG_TYPE=rpm

    case "${os_distro}${os_majorver}" in
	rhel4|centos4)
	    if [ "${ARCH}" == "i386" -a "${install_kernel}" -gt 0 ] ; then
		if [ -f "${DATADIR}/rhel4x/versions" ] ; then
		    source ${DATADIR}/rhel4x/versions
		    KERNEL="${DATADIR}/rhel4x/${RHEL4X_KERNEL_PKG_FILE}"
		    if [ ${os_minorver} -lt 5 ] ; then
			    MKINITRD="${DATADIR}/rhel4x/mkinitrd-${RHEL4X_MKINITRD_PKG_VERSION}.i386.rpm"
		    fi
		    REQUIRE_XENBLK_KO=yes
		    REQUIRE_XENNET_KO=yes
		else
		    echo "Warning: Kernel package not found in ${DATADIR}/rhel4x"
		fi
	    fi
	    ;;
	rhel5|centos5|oracle5|scientific5)
	    # No additional kernel package
	    ;;
	rhel6|centos6|oracle6|scientific6)
	    # No additional kernel package
	    ;;
	rhel7|centos7|oracle7|scientific7)
	    # No additional kernel package
	    ;;
	rhel3|fedora*)
	    # Not officially supported therefore no additional packages required.
	    ;;
	*)
	    failure "Unknown RedHat Linux variant \`${os_distro} ${os_majorver}'."
	    ;;
    esac

    select_rpm_utilities
}

select_pkgs_sles()
{
    GUEST_PKG_TYPE=rpm

    case "${os_distro}${os_majorver}" in
	sles9|"suse linux9")
            # SLES9.4+ already contains a Xen aware kernel
	    ;;
	sles10|"suse linux10") 
            # SLES10 already contains a Xen aware kernel
	    ;;
	sles11|"suse linux11")
            # SLES11 already contains a Xen aware kernel
	    ;;
	sles12|"suse linux12")
            # SLES12 already contains a Xen aware kernel
	    ;;
	*)
	    failure "Unknown SuSE Linux variant \`${os_distro} ${os_majorver}'."
	    ;;
    esac

    select_rpm_utilities
}

select_pkgs_debian()
{
    GUEST_PKG_TYPE=deb

    case ${os_distro}:${os_majorver}:${os_minorver} in
	debian:6:*) DISTRO=squeeze ;;
	debian:7:*) DISTRO=wheezy ;;
	debian:8:*) DISTRO=jessie ;;
	debian:testing:*|debian:unstable:*) DISTRO=${os_majorver} ;;

	ubuntu:10:04) DISTRO=lucid ;;
	ubuntu:10:10) DISTRO=maverick ;;
	ubuntu:11:04) DISTRO=natty ;;
	ubuntu:11:10) DISTRO=oneiric ;;
	ubuntu:12:04) DISTRO=precise ;;
	ubuntu:12:10) DISTRO=quantal ;;
	ubuntu:13:04) DISTRO=raring ;;
	ubuntu:13:10) DISTRO=saucy ;;
	ubuntu:14:04) DISTRO=trusty ;;
	ubuntu:1[456]:*) DISTRO=future_ubuntu ;;
    esac

    case ${ARCH} in
	i386) DARCH=i386 ;;
	x86_64) DARCH=amd64 ;;
    esac

    case ${DISTRO} in
	squeeze|wheezy|jessie) # No additional packages required
	    ;;
	testing|unstable)
	    ;;
	lucid|maverick|natty|oneiric|precise|quantal|raring|saucy|trusty|future_ubuntu)
	    ;;
	*)
	    failure "Unknown Debian variant \"${os_majorver}\" \"${DISTRO}\""
	    ;;
    esac

    if [ -f "${DATADIR}/versions.deb" ] ; then
	source ${DATADIR}/versions.deb
	XGU=${DATADIR}/$(eval echo \${XE_GUEST_UTILITIES_PKG_FILE_${DARCH}})
    else
	echo "Warning: Guest utilities not found in ${DATADIR}."
    fi
}

select_pkgs_xe()
{
    GUEST_PKG_TYPE=rpm

    select_rpm_utilities
}

select_pkgs_coreos()
{
    GUEST_PKG_TYPE=coreos
    XGU="xe-linux-distribution"
}

update_lvm_configuration_required()
{
    if test "${os_distro}" != rhel
    then
        return 1
    fi
    if test ${os_majorver} != 4
    then
        return 1
    fi
    if test \! -f /etc/lvm/lvm.conf
    then
        return 1
    fi
    lvm_types="`grep -e '^[ \t]*types' /etc/lvm/lvm.conf`"
    if [[ -z "$lvm_types" || "$lvm_types" != *\"xvd\"* ]]
    then
        return 0
    fi
    return 1
}

update_lvm_configuration()
{
    if test -z "`grep -e '^[ \t]*types' /etc/lvm/lvm.conf`"
    then
        sed -i '/^[ \t]*devices[ \t]*{[ \t]*/a \    types = ["xvd", 16]' /etc/lvm/lvm.conf
    else
        sed -i 's/^[ \t]*types.*/    types = ["xvd", 16]/g' /etc/lvm/lvm.conf
    fi
}

update_inittab_configuration_required()
{
    case "${os_distro}${os_majorver}" in
    sles10|"suse linux10"|sles11|"suse linux11")
      # SLES10 starts up two gettys, and we want to disable one of them
      if grep -Eq '^cons:12345:respawn:/sbin/smart_agetty -L 42 console' /etc/inittab; then
        if grep -Eq '^x0:12345:respawn:/sbin/agetty -L 9600 xvc0 xterm' /etc/inittab; then
            return 0
        fi
      fi
      ;;
    *)
      ;;
    esac
    return 1
}

update_inittab_configuration()
{
    case "${os_distro}${os_majorver}" in
    sles10|"suse linux10"|sles11|"suse linux11")
      # Disable duplicate getty
      sed -i 's/^x0:12345:respawn:/# x0:12345:respawn:/g' /etc/inittab
      ;;
    *)
      ;;
    esac
    /sbin/telinit q
}

update_arp_notify_required()
{
    # PV ops kernels do not emit gratuitous ARPs on migrate unless this is set
    file=/proc/sys/net/ipv4/conf/all/arp_notify
    if [ -f ${file} ] ; then
	val=$(cat ${file})
	if [ "${val}" = 0 ] ; then
	    return 0;
	fi
    fi
    return 1
}

update_arp_notify_configuration()
{
    file=/etc/sysctl.conf
    if [ -e "${file}" ] ; then
        sed -i -e 's/\(^\s*net\.ipv4\.conf\.[^.]\+\.arp_notify\s*=\s*0\)/#Auto-disabled by xs-tools:install.sh\n#\1/' ${file}
    else
        folder=/etc/sysctl.d/
        if [ ! -d "${folder}" ] ; then
            echo "Cannot update arp_notify configuration as neither ${file} nor ${folder} present" >&2
            return 1
        fi
        file=${folder}/10-enable-arp-notify.conf
    fi
    echo -e  "# Auto-enabled by xs-tools:install.sh\nnet.ipv4.conf.all.arp_notify = 1" >> ${file}
    return 0
}

update_modules_configuration_required()
{
    if [ -f "/etc/modprobe.conf" ] ; then
	conf="/etc/modprobe.conf"
    elif [ -f "/etc/modules.conf" ] ; then
	conf="/etc/modules.conf"
    else
	return 1
    fi

    if [ -z "${KERNEL}" ] ; then
	return 1
    fi

    if [ X"${REQUIRE_XENNET_KO}" = "Xyes" ] ; then
	if ! grep -Eq '^alias eth0 xennet$' "${conf}" ; then
	    # Required module alias not present!
	    return 0
	fi
    else
	if grep -Eq '^[^#]*eth0' "${conf}" ; then
	    # Module alias shouldn't be present as driver is kernel resident!
	    return 0 
	fi
    fi
    if [ X"${REQUIRE_XENBLK_KO}" = "Xyes" ] ; then
	if ! grep -Eq '^alias scsi_hostadapter xenblk$' "${conf}" ; then
	    # Required module alias not present!
	    return 0
	fi
    else
	if grep -Eq '^[^#]*scsi_hostadapter' "${conf}" ; then
	    # Module alias shouldn't be present as driver is kernel resident!
	    return 0 
	fi
    fi

    # no need to update modules configuration
    return 1
}

update_modules_configuration()
{
    if [ -f "/etc/modprobe.conf" ] ; then
	conf="/etc/modprobe.conf"
    elif [ -f "/etc/modules.conf" ] ; then
	conf="/etc/modules.conf"
    else
	return 1
    fi

    echo "Updating ${conf}, original saved in ${conf}.bak"
    cp "${conf}" "${conf}.bak"

    # Flush lines concerning PV modules
    grep -Ev '\beth0\b|\bscsi_hostadapter\b' "${conf}.bak" >"${conf}" || true

    # Add lines to alias any PV modules needed
    if [ X"${REQUIRE_XENNET_KO}" = "Xyes" ] ; then
	echo 'alias eth0 xennet' >>"${conf}"
    fi
    if [ X"${REQUIRE_XENBLK_KO}" = "Xyes" ] ; then
	echo 'alias scsi_hostadapter xenblk' >>"${conf}"
    fi

    return 0
}

update_grub_configuration()
{
    if [ -f "/boot/grub/menu.lst" ] ; then
	conf=$(readlink -f "/boot/grub/menu.lst")
    elif [ -f "/boot/grub/grub.conf" ] && [ ! -L "/boot/grub/grub.conf" ] ; then
	conf=$(readlink -f "/boot/grub/grub.conf")
    else
	echo "No grub configuration found. Not updating"
	return 1
    fi

    cmdline=$(cat /proc/cmdline)
    if [ -z "${cmdline}" ] ; then
	echo "No kernel command line found. Not updating grub configuration."
	return 1
    fi
    
    echo "Updating ${conf}, original saved in ${conf}.bak"
    cp "${conf}" "${conf}.bak"

    # Correct kernel command line.
    echo "  * set kernel command line to \`${cmdline}'."
    

    # Ensure kernel initrd and module lines include /boot/ if it is
    # not a separate filesystem.
    root_stat=$(stat --format='%d' --terse "/")
    boot_stat=$(stat --format='%d' --terse "/boot")
    if [ "${root_stat}" == "${boot_stat}" ] ; then 
	echo "  * prepend /boot/ to kernel path."
	rewrite_path="s;^\(\([^\#]*\|\)\(kernel\|initrd\|module\)\) \(/boot\)\?/\?;\1 /boot/;g"
    fi

    sed -e "s;^\(\([^\#]*\|\)kernel [^ ]*\) .*;\1 ${cmdline};g ; ${rewrite_path}" \
	<"${conf}.bak" >"${conf}"

    # Make sure there is an entry for the kernel pointed to by /boot/xenkernel
    if [ -L "/boot/xenkernel" ] && [ -x "/sbin/grubby" ] ; then
	kernel=$(readlink -f "/boot/xenkernel")
	initrd=$(readlink -f "/boot/xeninitrd")
	k="$(basename ${kernel} | sed -e 's/vmlinu.-//g')"
	echo "  * add entry for ${k} (/boot/xenkernel)."
	[ -n "${initrd}" ] && INITRD="--initrd ${initrd}"
	/sbin/grubby --add-kernel=${kernel} $INITRD \
	    --copy-default --make-default --title="${k}"
    fi

    echo

    return 0
}

update_vmlinuz_symlink()
{
    local symlink=$(readlink -f "/boot/vmlinuz")

    case ${symlink} in
	/boot/xenu-linux-*)
	    ;;
	*)
	    echo "\`/boot/vmlinuz' does not point to a Xen kernel. Leaving."
	    return 1
	    ;;
    esac

    local version=${symlink/\/boot\/xenu-linux-}

    mv -v "/boot/vmlinuz" "/boot/vmlinuz-xenu-${version}"

    if [ -L "/boot/initrd.img" ] && \
	[ "$(readlink -f /boot/initrd.img)" = "/boot/initrd.img-${version}" ] ; then
	mv -v "/boot/initrd.img" "/boot/initrd.img-xenu-${version}"
    fi
}

install_rpms()
{
    if [ -n "${XGU}" ] ; then
	rpm -Uvh ${XGU}
    fi

    if [ -n "${MKINITRD}" ] ; then
	rpm -Fvh --replacefiles --replacepkgs "${MKINITRD}"
    fi

    if [ -n "${ECRYPTFS_UTILS}" ] ; then
	rpm -Fvh --replacefiles --replacepkgs "${ECRYPTFS_UTILS}"
    fi

    # Install not upgrade so that old kernel is retained.
    if [ -n "${KERNEL}" ] ; then
	rpm -ivh "${KERNEL}"
	version=$(rpm -qp --qf "%{V}-%{R}\n" "${KERNEL}")
	if [ -f "/boot/vmlinuz-${version}xenU" ] && [ -x "/sbin/grubby" ] ; then
	    echo "Making kernel ${version}xenU"
	    echo "the default in grub configuration."
	    /sbin/grubby --set-default="/boot/vmlinuz-${version}xenU"
	    echo ""
	fi
    fi
    echo ""
}

install_debs()
{
    dpkg -i ${KERNEL} ${MKINITRD} ${XGU}

    echo ""
}

install_coreos()
{
    echo "Installing the agent..."
    mkdir -p /usr/share/oem/xs/
    cp -f ${DATADIR}/xe-daemon \
          ${DATADIR}/xe-linux-distribution \
          ${DATADIR}/xe-update-guest-attrs /usr/share/oem/xs/
    cp -f ${DATADIR}/xen-vcpu-hotplug.rules /etc/udev/rules.d/
    cp -f ${DATADIR}/xe-linux-distribution.service /etc/systemd/system/
    systemctl enable /etc/systemd/system/xe-linux-distribution.service
    echo "Installation complete."
    systemctl start xe-linux-distribution.service
}

case "${os_distro}" in
    rhel|centos|oracle|fedora) select_pkgs_rhel ;;
    scientific)                select_pkgs_rhel ;;
    sles|"suse linux")         select_pkgs_sles ;;
    debian|ubuntu)             select_pkgs_debian ;;
    xe-ddk|xe-sdk)             select_pkgs_xe ;;
    CoreOS)                    select_pkgs_coreos ;;
    *)                  failure "Unknown Linux distribution \`${os_distro}'." ;;
esac

if [ -n "${KERNEL}" ] ; then
    for K in ${KERNEL} ; do
	if [ ! -f "${K}" ] ; then
	    echo "Warning: kernel ${K} not found."
	    echo ""
	    KERNEL=""
	fi
    done
fi
if [ -n "${MKINITRD}" -a ! -f "${MKINITRD}" ] ; then
    echo "Warning: mkinitrd ${MKINITRD} not found."
    echo ""
    MKINITRD=""
fi
if [ -n "${ECRYPTFS_UTILS}" ] ; then
    for E in ${ECRYPTFS_UTILS} ; do
	if [ ! -f "${E}" ] ; then
	    echo "Warning: ecryptfs-utils ${E} not found."
	    echo ""
	    ECRYPTFS_UTILS=""
	fi
    done
fi
if [ -n "${XGU}" ] ; then
    for P in ${XGU} ; do
	if [ ! -f "${P}" ] ; then
	    echo "Warning: xe-guest-utilities ${P} not found."
	    XGU=""
	fi
    done
fi
if [ -z "${XGU}" ] ; then
    echo ""
    echo "Certain guest features will not be active until a version of "
    echo "xe-guest-utilities is installed."
    echo ""
fi

if [ -z "${KERNEL}" -a -z "${MKINITRD}" -a -z "${XGU}" -a -z "${ECRYPTFS_UTILS}" ] ; then
    echo "No updates required to this Virtual Machine."
    exit 0
fi

echo "The following changes will be made to this Virtual Machine:"

if [ -n "${KERNEL}" -a -L "/boot/xenkernel" ] ||
   [ -n "${KERNEL}" -a -L "/boot/vmlinuz" ] ; then
    echo "  * grub configuration update."
    echo "  * remove legacy kernel and initrd symbolic links."
    update_grub=1
else
    update_grub=0
fi

if update_modules_configuration_required ; then
    echo "  * modules configuration update."
    update_modules=1
else
    update_modules=0
fi

if update_lvm_configuration_required ; then
    echo "  * lvm configuration update."
    update_lvm=1
else
    update_lvm=0
fi

if update_inittab_configuration_required ; then
    echo "  * update gettys in inittab."
    update_inittab=1
else
    update_inittab=0
fi

if update_arp_notify_required ; then
    echo "  * enable arp_notify in sysctl."
    update_arp_notify=1
else
    update_arp_notify=0
fi

echo "  * packages to be installed/upgraded:"
for K in ${KERNEL}; do
    echo -e                           "    - $(basename ${K})"
done
[ -n "${MKINITRD}" ] && echo -e       "    - $(basename ${MKINITRD})"
for E in ${ECRYPTFS_UTILS}; do
    echo -e                           "    - $(basename ${E})"
done
for P in ${XGU}; do
    echo -e                           "    - $(basename ${P})"
done
echo ""

if [ ${interactive} -gt 0 ] ; then
    while read -p "Continue? [y/n] " -n 1 ans ; do
	echo
	case "$ans" in
	    Y|y)
		break
		;;
	    N|n)
		echo "Aborting."
		exit 0
		;;
	    *)
		echo "Invalid response \`$ans'"
		;;
	esac
    done
    echo
fi

set -e # Any failures from here onwards should be fatal

if [ "${update_grub}" -gt 0 ] ; then
    update_grub_configuration

    # Remove /boot/xenkernel
    if [ -L "/boot/xenkernel" ] || [ -L "/boot/xeninitrd" ] || [ -L "/boot/vmlinuz" ] ; then
	echo "Removing legacy boot kernel and initrd symbolic links."
	[ -L "/boot/xenkernel" ] && mv -v "/boot/xenkernel" "/boot/xenkernel.bak"
	[ -L "/boot/xeninitrd" ] && mv -v "/boot/xeninitrd" "/boot/xeninitrd.bak"
	[ -L "/boot/vmlinuz" ] && update_vmlinuz_symlink
	echo
    fi
fi

if [ "${update_modules}" -gt 0 ] ; then
    update_modules_configuration
fi

if [ "${update_lvm}" -gt 0 ] ; then
    update_lvm_configuration
fi

if [ "${update_inittab}" -gt 0 ] ; then
    update_inittab_configuration
fi

if [ "${update_arp_notify}" -gt 0 ] ; then
    update_arp_notify_configuration
fi

case ${GUEST_PKG_TYPE} in
    rpm) install_rpms ;;
    deb) install_debs ;;
    coreos) install_coreos ;;
esac

if [ -n "${KERNEL}" -o -n "${XGU}" ] ; then
    echo "You should now reboot this Virtual Machine."
fi

exit 0
