#! /bin/sh
# $Id: vboxadd.sh 99550 2023-04-27 14:26:22Z vboxsync $
## @file
# Linux Additions kernel module init script ($Revision: 99550 $)
#

#
# Copyright (C) 2006-2023 Oracle and/or its affiliates.
#
# This file is part of VirtualBox base platform packages, as
# available from https://www.virtualbox.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, in version 3 of the
# License.
#
# 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, see <https://www.gnu.org/licenses>.
#
# SPDX-License-Identifier: GPL-3.0-only
#

# X-Start-Before is a Debian Addition which we use when converting to
# a systemd unit.  X-Service-Type is our own invention, also for systemd.

# chkconfig: 345 10 90
# description: VirtualBox Linux Additions kernel modules
#
### BEGIN INIT INFO
# Provides:       vboxadd
# Required-Start:
# Required-Stop:
# Default-Start:  2 3 4 5
# Default-Stop:   0 1 6
# X-Start-Before: display-manager
# X-Service-Type: oneshot
# Description:    VirtualBox Linux Additions kernel modules
### END INIT INFO

## @todo This file duplicates a lot of script with vboxdrv.sh.  When making
# changes please try to reduce differences between the two wherever possible.

# Testing:
# * Should fail if the configuration file is missing or missing INSTALL_DIR or
#   INSTALL_VER entries.
# * vboxadd, vboxsf and vboxdrmipc user groups should be created if they do not exist - test
#   by removing them before installing.
# * Shared folders can be mounted and auto-mounts accessible to vboxsf group,
#   including on recent Fedoras with SELinux.
# * Setting INSTALL_NO_MODULE_BUILDS inhibits modules and module automatic
#   rebuild script creation; otherwise modules, user, group, rebuild script,
#   udev rule and shared folder mount helper should be created/set up.
# * Setting INSTALL_NO_MODULE_BUILDS inhibits module load and unload on start
#   and stop.
# * Uninstalling the Additions and re-installing them does not trigger warnings.

export LC_ALL=C
PATH=$PATH:/bin:/sbin:/usr/sbin
PACKAGE=VBoxGuestAdditions
MODPROBE=/sbin/modprobe
OLDMODULES="vboxguest vboxadd vboxsf vboxvfs vboxvideo"
SERVICE="VirtualBox Guest Additions"
VBOXSERVICE_PIDFILE="/var/run/vboxadd-service.sh"
## systemd logs information about service status, otherwise do that ourselves.
QUIET=
test -z "${TARGET_VER}" && TARGET_VER=`uname -r`

export VBOX_KBUILD_TYPE
export USERNAME

setup_log()
{
    test -z "${LOG}" || return 0
    # Rotate log files
    LOG="/var/log/vboxadd-setup.log"
    mv -f "${LOG}.3" "${LOG}.4" 2>/dev/null
    mv -f "${LOG}.2" "${LOG}.3" 2>/dev/null
    mv -f "${LOG}.1" "${LOG}.2" 2>/dev/null
    mv -f "${LOG}" "${LOG}.1" 2>/dev/null
}

if $MODPROBE -c 2>/dev/null | grep -q '^allow_unsupported_modules  *0'; then
  MODPROBE="$MODPROBE --allow-unsupported-modules"
fi

# Preamble for Gentoo
if [ "`which $0`" = "/sbin/rc" ]; then
    shift
fi

begin()
{
    test -n "${QUIET}" || echo "${SERVICE}: ${1}"
}

info()
{
    if test -z "${QUIET}"; then
        echo "${SERVICE}: $1" | fold -s
    else
        echo "$1" | fold -s
    fi
}

# When script is running as non-root, it does not have access to log
# files in /var. In this case, lets print error message to stdout and
# exit with bad status.
early_fail()
{
    echo "$1" >&2
    exit 1
}

fail()
{
    log "${1}"
    echo "${SERVICE}: $1" >&2
    echo "The log file $LOG may contain further information." >&2
    exit 1
}

log()
{
    setup_log
    echo "${1}" >> "${LOG}"
}

module_build_log()
{
    log "Error building the module.  Build output follows."
    echo ""
    echo "${1}" >> "${LOG}"
}

dev=/dev/vboxguest
userdev=/dev/vboxuser
config=/var/lib/VBoxGuestAdditions/config
user_config=/etc/virtualbox-guest-additions.conf
owner=vboxadd
group=1

# Include custom user configuration file.
[ -r "$user_config" ] && . "$user_config"

if test -r $config; then
  . $config
else
  fail "Configuration file $config not found"
fi
test -n "$INSTALL_DIR" -a -n "$INSTALL_VER" ||
  fail "Configuration file $config not complete"
MODULE_SRC="$INSTALL_DIR/src/vboxguest-$INSTALL_VER"
BUILDINTMP="$MODULE_SRC/build_in_tmp"

# Path to VBoxService control script.
VBOX_SERVICE_SCRIPT="/sbin/rcvboxadd-service"

# Attempt to detect VirtualBox Guest Additions version and revision information.
VBOXCONTROL="${INSTALL_DIR}/bin/VBoxControl"
VBOX_VERSION="`"$VBOXCONTROL" --version | cut -d r -f1`"
[ -n "$VBOX_VERSION" ] || VBOX_VERSION='unknown'
VBOX_REVISION="r`"$VBOXCONTROL" --version | cut -d r -f2`"
[ "$VBOX_REVISION" != "r" ] || VBOX_REVISION='unknown'

# Returns if the vboxguest module is running or not.
#
# Returns true if vboxguest module is running, false if not.
running_vboxguest()
{
    lsmod | grep -q "vboxguest[^_-]"
}

# Returns if the vboxadd module is running or not.
#
# Returns true if vboxadd module is running, false if not.
running_vboxadd()
{
    lsmod | grep -q "vboxadd[^_-]"
}

# Returns if the vboxsf module is running or not.
#
# Returns true if vboxsf module is running, false if not.
running_vboxsf()
{
    lsmod | grep -q "vboxsf[^_-]"
}

# Returns if the vboxvideo module is running or not.
#
# Returns true if vboxvideo module is running, false if not.
running_vboxvideo()
{
    lsmod | grep -q "vboxvideo[^_-]"
}

# Returns if a specific module is running or not.
#
# Input $1: Module name to check running status for.
#
# Returns true if the module is running, false if not.
running_module()
{
    lsmod | grep -q "$1"
}

# Returns the version string of a currently running kernel module.
#
# Input $1: Module name to check.
#
# Returns the module version string if found, or none if not found.
running_module_version()
{
    mod="$1"
    version_string_path="/sys/module/"$mod"/version"

    [ -n "$mod" ] || return
    if [ -r "$version_string_path" ]; then
        cat "$version_string_path"
    else
        echo "unknown"
    fi
}

# Checks if a loaded kernel module version matches to the currently installed Guest Additions version and revision.
#
# Input $1: Module name to check.
#
# Returns "1" if the module matches the installed Guest Additions, or none if not.
check_running_module_version()
{
    mod=$1
    expected="$VBOX_VERSION $VBOX_REVISION"

    [ -n "$mod" ] || return
    [ -n "$expected" ] || return

    [ "$expected" = "$(running_module_version "$mod")" ] || return
}

do_vboxguest_non_udev()
{
    if [ ! -c $dev ]; then
        maj=`sed -n 's;\([0-9]\+\) vboxguest;\1;p' /proc/devices`
        if [ ! -z "$maj" ]; then
            min=0
        else
            min=`sed -n 's;\([0-9]\+\) vboxguest;\1;p' /proc/misc`
            if [ ! -z "$min" ]; then
                maj=10
            fi
        fi
        test -n "$maj" || {
            rmmod vboxguest 2>/dev/null
            fail "Cannot locate the VirtualBox device"
        }

        mknod -m 0664 $dev c $maj $min || {
            rmmod vboxguest 2>/dev/null
            fail "Cannot create device $dev with major $maj and minor $min"
        }
    fi
    chown $owner:$group $dev 2>/dev/null || {
        rm -f $dev 2>/dev/null
        rm -f $userdev 2>/dev/null
        rmmod vboxguest 2>/dev/null
        fail "Cannot change owner $owner:$group for device $dev"
    }

    if [ ! -c $userdev ]; then
        maj=10
        min=`sed -n 's;\([0-9]\+\) vboxuser;\1;p' /proc/misc`
        if [ ! -z "$min" ]; then
            mknod -m 0666 $userdev c $maj $min || {
                rm -f $dev 2>/dev/null
                rmmod vboxguest 2>/dev/null
                fail "Cannot create device $userdev with major $maj and minor $min"
            }
            chown $owner:$group $userdev 2>/dev/null || {
                rm -f $dev 2>/dev/null
                rm -f $userdev 2>/dev/null
                rmmod vboxguest 2>/dev/null
                fail "Cannot change owner $owner:$group for device $userdev"
            }
        fi
    fi
}

restart()
{
    stop && start
    return 0
}

## Updates the initramfs.  Debian and Ubuntu put the graphics driver in, and
# need the touch(1) command below.  Everyone else that I checked just need
# the right module alias file from depmod(1) and only use the initramfs to
# load the root filesystem, not the boot splash.  update-initramfs works
# for the first two and dracut for every one else I checked.  We are only
# interested in distributions recent enough to use the KMS vboxvideo driver.
update_initramfs()
{
    ## kernel version to update for.
    version="${1}"
    depmod "${version}"
    rm -f "/lib/modules/${version}/initrd/vboxvideo"
    test ! -d "/lib/modules/${version}/initrd" ||
        test ! -f "/lib/modules/${version}/misc/vboxvideo.ko" ||
        touch "/lib/modules/${version}/initrd/vboxvideo"

    # Systems without systemd-inhibit probably don't need their initramfs
    # rebuild here anyway.
    type systemd-inhibit >/dev/null 2>&1 || return
    if type dracut >/dev/null 2>&1; then
        systemd-inhibit --why="Installing VirtualBox Guest Additions" \
            dracut -f --kver "${version}"
    elif type update-initramfs >/dev/null 2>&1; then
        systemd-inhibit --why="Installing VirtualBox Guest Additions" \
            update-initramfs -u -k "${version}"
    fi
}

# Removes any existing VirtualBox guest kernel modules from the disk, but not
# from the kernel as they may still be in use
cleanup_modules()
{
    # Needed for Ubuntu and Debian, see update_initramfs
    rm -f /lib/modules/*/initrd/vboxvideo
    for i in /lib/modules/*/misc; do
        KERN_VER="${i%/misc}"
        KERN_VER="${KERN_VER#/lib/modules/}"
        unset do_update
        for j in ${OLDMODULES}; do
            for mod_ext in ko ko.gz ko.xz ko.zst; do
                test -f "${i}/${j}.${mod_ext}" && do_update=1 && rm -f "${i}/${j}.${mod_ext}"
            done
        done
        test -z "$do_update" || update_initramfs "$KERN_VER"
        # Remove empty /lib/modules folders which may have been kept around
        rmdir -p "${i}" 2>/dev/null || true
        unset keep
        for j in /lib/modules/"${KERN_VER}"/*; do
            name="${j##*/}"
            test -d "${name}" || test "${name%%.*}" != modules && keep=1
        done
        if test -z "${keep}"; then
            rm -rf /lib/modules/"${KERN_VER}"
            rm -f /boot/initrd.img-"${KERN_VER}"
        fi
    done
    for i in ${OLDMODULES}; do
        # We no longer support DKMS, remove any leftovers.
        rm -rf "/var/lib/dkms/${i}"*
    done
    rm -f /etc/depmod.d/vboxvideo-upstream.conf
}

# Secure boot state.
case "`mokutil --sb-state 2>/dev/null`" in
    *"disabled in shim"*) unset HAVE_SEC_BOOT;;
    *"SecureBoot enabled"*) HAVE_SEC_BOOT=true;;
    *) unset HAVE_SEC_BOOT;;
esac
# So far we can only sign modules on Ubuntu and on Debian 10 and later.
DEB_PUB_KEY=/var/lib/shim-signed/mok/MOK.der
DEB_PRIV_KEY=/var/lib/shim-signed/mok/MOK.priv
# Check if key already enrolled.
unset HAVE_DEB_KEY
case "`mokutil --test-key "$DEB_PUB_KEY" 2>/dev/null`" in
    *"is already"*) DEB_KEY_ENROLLED=true;;
    *) unset DEB_KEY_ENROLLED;;
esac

# Checks if update-secureboot-policy tool supports required commandline options.
update_secureboot_policy_supports()
{
    opt_name="$1"
    [ -n "$opt_name" ] || return

    [ -z "$(update-secureboot-policy --help 2>&1 | grep "$opt_name")" ] && return
    echo "1"
}

HAVE_UPDATE_SECUREBOOT_POLICY_TOOL=
if type update-secureboot-policy >/dev/null 2>&1; then
    [ "$(update_secureboot_policy_supports new-key)" = "1" -a "$(update_secureboot_policy_supports enroll-key)" = "1" ] && \
        HAVE_UPDATE_SECUREBOOT_POLICY_TOOL=true
fi

# Reads kernel configuration option.
kernel_get_config_opt()
{
    kern_ver="$1"
    opt_name="$2"

    [ -n "$kern_ver" ] || return
    [ -n "$opt_name" ] || return

    # Check if there is a kernel tool which can extract config option.
    if test -x /lib/modules/"$kern_ver"/build/scripts/config; then
        /lib/modules/"$kern_ver"/build/scripts/config \
            --file /lib/modules/"$kern_ver"/build/.config \
            --state "$opt_name" 2>/dev/null
    elif test -f /lib/modules/"$kern_ver"/build/.config; then
        # Extract config option manually.
        grep "$opt_name=" /lib/modules/"$kern_ver"/build/.config | sed -e "s/^$opt_name=//" -e "s/\"//g"
    fi
}

# Reads CONFIG_MODULE_SIG_HASH from kernel config.
kernel_module_sig_hash()
{
    kern_ver="$1"
    [ -n "$kern_ver" ] || return

    kernel_get_config_opt "$kern_ver" "CONFIG_MODULE_SIG_HASH"
}

# Returns "1" if kernel module signature hash algorithm
# is supported by us. Or empty string otherwise.
module_sig_hash_supported()
{
    sig_hashalgo="$1"
    [ -n "$sig_hashalgo" ] || return

    # Go through supported list.
    [    "$sig_hashalgo" = "sha1"   \
      -o "$sig_hashalgo" = "sha224" \
      -o "$sig_hashalgo" = "sha256" \
      -o "$sig_hashalgo" = "sha384" \
      -o "$sig_hashalgo" = "sha512" ] || return

    echo "1"
}

# Check if kernel configuration requires modules signature.
kernel_requires_module_signature()
{
    kern_ver="$1"
    vbox_sys_lockdown_path="/sys/kernel/security/lockdown"

    [ -n "$kern_ver" ] || return

    requires=""
    # We consider that if kernel is running in the following configurations,
    # it will require modules to be signed.
    if [ "$(kernel_get_config_opt "$kern_ver" "CONFIG_MODULE_SIG")" = "y" ]; then

        # Modules signature verification is hardcoded in kernel config.
        [ "$(kernel_get_config_opt "$kern_ver" "CONFIG_MODULE_SIG_FORCE")" = "y" ] && requires="1"

        # Unsigned modules loading is restricted by "lockdown" feature in runtime.
        if [   "$(kernel_get_config_opt "$kern_ver" "CONFIG_LOCK_DOWN_KERNEL")" = "y" \
            -o "$(kernel_get_config_opt "$kern_ver" "CONFIG_SECURITY_LOCKDOWN_LSM")" = "y" \
            -o "$(kernel_get_config_opt "$kern_ver" "CONFIG_SECURITY_LOCKDOWN_LSM_EARLY")" = "y" ]; then

            # Once lockdown level is set to something different from "none" (e.g., "integrity"
            # or "confidentiality"), kernel will reject unsigned modules loading.
            if [ -r "$vbox_sys_lockdown_path" ]; then
                [ -n "$(cat "$vbox_sys_lockdown_path" | grep "\[integrity\]")" ] && requires="1"
                [ -n "$(cat "$vbox_sys_lockdown_path" | grep "\[confidentiality\]")" ] && requires="1"
            fi

            # This configuration is used by a number of modern Linux distributions and restricts
            # unsigned modules loading when Secure Boot mode is enabled.
            [ "$(kernel_get_config_opt "$kern_ver" "CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT")" = "y" -a -n "$HAVE_SEC_BOOT" ] && requires="1"
        fi
    fi

    [ -n "$requires" ] && echo "1"
}

sign_modules()
{
    KERN_VER="$1"
    test -n "$KERN_VER" || return 1

    # Make list of mudules to sign.
    MODULE_LIST="vboxguest vboxsf"
    # vboxvideo might not present on for older kernels.
    [ -f "/lib/modules/"$KERN_VER"/misc/vboxvideo.ko" ] && MODULE_LIST="$MODULE_LIST vboxvideo"

    # Sign kernel modules if kernel configuration requires it.
    if test "$(kernel_requires_module_signature $KERN_VER)" = "1"; then
        begin "Signing VirtualBox Guest Additions kernel modules"

        # Generate new signing key if needed.
        [ -n "$HAVE_UPDATE_SECUREBOOT_POLICY_TOOL" ] && SHIM_NOTRIGGER=y update-secureboot-policy --new-key

        # Check if signing keys are in place.
        if test ! -f "$DEB_PUB_KEY" || ! test -f "$DEB_PRIV_KEY"; then
            # update-secureboot-policy tool present in the system, but keys were not generated.
            [ -n "$HAVE_UPDATE_SECUREBOOT_POLICY_TOOL" ] && info "

update-secureboot-policy tool does not generate signing keys
in your distribution, see below on how to generate them manually."
            # update-secureboot-policy not present in the system, recommend generate keys manually.
            fail "

System is running in Secure Boot mode, however your distribution
does not provide tools for automatic generation of keys needed for
modules signing. Please consider to generate and enroll them manually:

    sudo mkdir -p /var/lib/shim-signed/mok
    sudo openssl req -nodes -new -x509 -newkey rsa:2048 -outform DER -addext \"extendedKeyUsage=codeSigning\" -keyout $DEB_PRIV_KEY -out $DEB_PUB_KEY
    sudo mokutil --import $DEB_PUB_KEY
    sudo reboot

Restart \"rcvboxadd setup\" after system is rebooted.
"
        fi

        # Get kernel signature hash algorithm from kernel config and validate it.
        sig_hashalgo=$(kernel_module_sig_hash "$KERN_VER")
        [ "$(module_sig_hash_supported $sig_hashalgo)" = "1" ] \
            || fail "Unsupported kernel signature hash algorithm $sig_hashalgo"

        # Sign modules.
        for i in $MODULE_LIST; do

            # Try to find a tool for modules signing.
            SIGN_TOOL=$(which kmodsign 2>/dev/null)
            # Attempt to use in-kernel signing tool if kmodsign not found.
            if test -z "$SIGN_TOOL"; then
                if test -x "/lib/modules/$KERN_VER/build/scripts/sign-file"; then
                    SIGN_TOOL="/lib/modules/$KERN_VER/build/scripts/sign-file"
                fi
            fi

            # Check if signing tool is available.
            [ -n "$SIGN_TOOL" ] || fail "Unable to find signing tool"

            "$SIGN_TOOL" "$sig_hashalgo" "$DEB_PRIV_KEY" "$DEB_PUB_KEY" \
                /lib/modules/"$KERN_VER"/misc/"$i".ko || fail "Unable to sign $i.ko"
        done
        # Enroll signing key if needed.
        if test -n "$HAVE_UPDATE_SECUREBOOT_POLICY_TOOL"; then
            # update-secureboot-policy "expects" DKMS modules.
            # Work around this and talk to the authors as soon
            # as possible to fix it.
            mkdir -p /var/lib/dkms/vbox-temp
            update-secureboot-policy --enroll-key 2>/dev/null ||
                fail "Failed to enroll secure boot key."
            rmdir -p /var/lib/dkms/vbox-temp 2>/dev/null

            # Indicate that key has been enrolled and reboot is needed.
            HAVE_DEB_KEY=true
        fi
    fi
}

# Build and install the VirtualBox guest kernel modules
setup_modules()
{
    KERN_VER="$1"
    test -n "$KERN_VER" || return 1
    # Match (at least): vboxguest.o; vboxguest.ko; vboxguest.ko.xz
    set /lib/modules/"$KERN_VER"/misc/vboxguest.*o*
    #test ! -f "$1" || return 0
    test -d /lib/modules/"$KERN_VER"/build || return 0
    export KERN_VER
    info "Building the modules for kernel $KERN_VER."

    # Prepend PATH for building UEK7 on OL8 distribution.
    case "$KERN_VER" in
        5.15.0-*.el8uek*) PATH="/opt/rh/gcc-toolset-11/root/usr/bin:$PATH";;
    esac

    # Detect if kernel was built with clang.
    unset LLVM
    vbox_cc_is_clang=$(kernel_get_config_opt "$KERN_VER" "CONFIG_CC_IS_CLANG")
    if test "${vbox_cc_is_clang}" = "y"; then
        info "Using clang compiler."
        export LLVM=1
    fi

    log "Building the main Guest Additions $INSTALL_VER module for kernel $KERN_VER."
    if ! myerr=`$BUILDINTMP \
        --save-module-symvers /tmp/vboxguest-Module.symvers \
        --module-source $MODULE_SRC/vboxguest \
        --no-print-directory install 2>&1`; then
        # If check_module_dependencies.sh fails it prints a message itself.
        module_build_log "$myerr"
        "${INSTALL_DIR}"/other/check_module_dependencies.sh 2>&1 &&
            info "Look at $LOG to find out what went wrong"
        return 0
    fi
    log "Building the shared folder support module."
    if ! myerr=`$BUILDINTMP \
        --use-module-symvers /tmp/vboxguest-Module.symvers \
        --module-source $MODULE_SRC/vboxsf \
        --no-print-directory install 2>&1`; then
        module_build_log "$myerr"
        info  "Look at $LOG to find out what went wrong"
        return 0
    fi
    log "Building the graphics driver module."
    if ! myerr=`$BUILDINTMP \
        --use-module-symvers /tmp/vboxguest-Module.symvers \
        --module-source $MODULE_SRC/vboxvideo \
        --no-print-directory install 2>&1`; then
        module_build_log "$myerr"
        info "Look at $LOG to find out what went wrong"
    fi
    [ -d /etc/depmod.d ] || mkdir /etc/depmod.d
    echo "override vboxguest * misc" > /etc/depmod.d/vboxvideo-upstream.conf
    echo "override vboxsf * misc" >> /etc/depmod.d/vboxvideo-upstream.conf
    echo "override vboxvideo * misc" >> /etc/depmod.d/vboxvideo-upstream.conf

    sign_modules "${KERN_VER}"

    update_initramfs "${KERN_VER}"

    return 0
}

create_vbox_user()
{
    # This is the LSB version of useradd and should work on recent
    # distributions
    useradd -d /var/run/vboxadd -g 1 -r -s /bin/false vboxadd >/dev/null 2>&1 || true
    # And for the others, we choose a UID ourselves
    useradd -d /var/run/vboxadd -g 1 -u 501 -o -s /bin/false vboxadd >/dev/null 2>&1 || true

}

create_udev_rule()
{
    # Create udev description file
    if [ -d /etc/udev/rules.d ]; then
        udev_call=""
        udev_app=`which udevadm 2> /dev/null`
        if [ $? -eq 0 ]; then
            udev_call="${udev_app} version 2> /dev/null"
        else
            udev_app=`which udevinfo 2> /dev/null`
            if [ $? -eq 0 ]; then
                udev_call="${udev_app} -V 2> /dev/null"
            fi
        fi
        udev_fix="="
        if [ "${udev_call}" != "" ]; then
            udev_out=`${udev_call}`
            udev_ver=`expr "$udev_out" : '[^0-9]*\([0-9]*\)'`
            if [ "$udev_ver" = "" -o "$udev_ver" -lt 55 ]; then
               udev_fix=""
            fi
        fi
        ## @todo 60-vboxadd.rules -> 60-vboxguest.rules ?
        echo "KERNEL=${udev_fix}\"vboxguest\", NAME=\"vboxguest\", OWNER=\"vboxadd\", MODE=\"0660\"" > /etc/udev/rules.d/60-vboxadd.rules
        echo "KERNEL=${udev_fix}\"vboxuser\", NAME=\"vboxuser\", OWNER=\"vboxadd\", MODE=\"0666\"" >> /etc/udev/rules.d/60-vboxadd.rules
        # Make sure the new rule is noticed.
        udevadm control --reload >/dev/null 2>&1 || true
        udevcontrol reload_rules >/dev/null 2>&1 || true
    fi
}

create_module_rebuild_script()
{
    # And a post-installation script for rebuilding modules when a new kernel
    # is installed.
    mkdir -p /etc/kernel/postinst.d /etc/kernel/prerm.d
    cat << EOF > /etc/kernel/postinst.d/vboxadd
#!/bin/sh
# This only works correctly on Debian derivatives - Red Hat calls it before
# installing the right header files.
/sbin/rcvboxadd quicksetup "\${1}"
exit 0
EOF
    cat << EOF > /etc/kernel/prerm.d/vboxadd
#!/bin/sh
for i in ${OLDMODULES}; do rm -f /lib/modules/"\${1}"/misc/"\${i}".ko; done
rmdir -p /lib/modules/"\$1"/misc 2>/dev/null || true
exit 0
EOF
    chmod 0755 /etc/kernel/postinst.d/vboxadd /etc/kernel/prerm.d/vboxadd
}

shared_folder_setup()
{
    # Add a group "vboxsf" for Shared Folders access
    # All users which want to access the auto-mounted Shared Folders have to
    # be added to this group.
    groupadd -r -f vboxsf >/dev/null 2>&1

    # Put the mount.vboxsf mount helper in the right place.
    ## @todo It would be nicer if the kernel module just parsed parameters
    # itself instead of needing a separate binary to do that.
    ln -sf "${INSTALL_DIR}/other/mount.vboxsf" /sbin
    # SELinux security context for the mount helper.
    if test -e /etc/selinux/config; then
        # This is correct.  semanage maps this to the real path, and it aborts
        # with an error, telling you what you should have typed, if you specify
        # the real path.  The "chcon" is there as a back-up for old guests.
        command -v semanage > /dev/null &&
            semanage fcontext -a -t mount_exec_t "${INSTALL_DIR}/other/mount.vboxsf"
        chcon -t mount_exec_t "${INSTALL_DIR}/other/mount.vboxsf" 2>/dev/null
    fi
}

# Returns path to a module file as seen by modinfo(8), or none if not found.
#
# Input $1: Module name to get path for.
#
# Returns the module path as a string.
module_path()
{
    mod="$1"
    [ -n "$mod" ] || return

    modinfo "$mod" 2>/dev/null | grep -e "^filename:" | tr -s ' ' | cut -d " " -f2
}

# Returns module version if module is available, or none if not found.
#
# Input $1: Module name to get version for.
#
# Returns the module version as a string.
module_version()
{
    mod="$1"
    [ -n "$mod" ] || return

    modinfo "$mod" 2>/dev/null | grep -e "^version:" | tr -s ' ' | cut -d " " -f2
}

# Returns the module revision if module is available in the system, or none if not found.
#
# Input $1: Module name to get revision for.
#
# Returns the module revision as a string.
module_revision()
{
    mod="$1"
    [ -n "$mod" ] || return

    modinfo "$mod" 2>/dev/null | grep -e "^version:" | tr -s ' ' | cut -d " " -f3
}

# Checks if a given kernel module is properly signed or not.
#
# Input $1: Module name to check.
#
# Returns "1" if module is signed and signature can be verified
# with public key provided in DEB_PUB_KEY, or none otherwise.
module_signed()
{
    mod="$1"
    [ -n "$mod" ] || return

    # Be nice with distributions which do not provide tools which we
    # use in order to verify module signature. This variable needs to
    # be explicitly set by administrator. This script will look for it
    # in /etc/virtualbox-guest-additions.conf. Make sure that you know
    # what you do!
    if [ "$VBOX_BYPASS_MODULES_SIGNATURE_CHECK" = "1" ]; then
        echo "1"
        return
    fi

    extraction_tool=/lib/modules/"$(uname -r)"/build/scripts/extract-module-sig.pl
    mod_path=$(module_path "$mod" 2>/dev/null)
    openssl_tool=$(which openssl 2>/dev/null)
    # Do not use built-in printf!
    printf_tool=$(which printf 2>/dev/null)

    # Make sure all the tools required for signature validation are available.
    [ -x "$extraction_tool" ] || return
    [ -n "$mod_path"        ] || return
    [ -n "$openssl_tool"    ] || return
    [ -n "$printf_tool"     ] || return

    # Make sure openssl can handle hash algorithm.
    sig_hashalgo=$(modinfo -F sig_hashalgo "$mod" 2>/dev/null)
    [ "$(module_sig_hash_supported $sig_hashalgo)" = "1" ] || return

    # Generate file names for temporary stuff.
    mod_pub_key=$(mktemp -u)
    mod_signature=$(mktemp -u)
    mod_unsigned=$(mktemp -u)

    # Convert public key in DER format into X509 certificate form.
    "$openssl_tool" x509 -pubkey -inform DER -in "$DEB_PUB_KEY" -out "$mod_pub_key" 2>/dev/null
    # Extract raw module signature and convert it into binary format.
    "$printf_tool" \\x$(modinfo -F signature "$mod" | sed -z 's/[ \t\n]//g' | sed -e "s/:/\\\x/g") 2>/dev/null > "$mod_signature"
    # Extract unsigned module for further digest calculation.
    "$extraction_tool" -0 "$mod_path" 2>/dev/null > "$mod_unsigned"

    # Verify signature.
    rc=""
    "$openssl_tool" dgst "-$sig_hashalgo" -binary -verify "$mod_pub_key" -signature "$mod_signature" "$mod_unsigned" 2>&1 >/dev/null && rc="1"
    # Clean up.
    rm -f $mod_pub_key $mod_signature $mod_unsigned

    # Check result.
    [ "$rc" = "1" ] || return

    echo "1"
}

# Checks if a given kernel module matches the installed VirtualBox Guest Additions version.
#
# Input $1: Module name to check.
#
# Returns "1" if externally built module is available in the system and its
# version and revision number do match to current VirtualBox installation.
# None otherwise.
module_available()
{
    mod="$1"
    [ -n "$mod" ] || return

    [ "$VBOX_VERSION" = "$(module_version "$mod")" ] || return
    [ "$VBOX_REVISION" = "$(module_revision "$mod")" ] || return

    # Check if module belongs to VirtualBox installation.
    #
    # We have a convention that only modules from /lib/modules/*/misc
    # belong to us. Modules from other locations are treated as
    # externally built.
    mod_path="$(module_path "$mod")"

    # If module path points to a symbolic link, resolve actual file location.
    [ -L "$mod_path" ] && mod_path="$(readlink -e -- "$mod_path")"

    # File exists?
    [ -f "$mod_path" ] || return

    # Extract last component of module path and check whether it is located
    # outside of /lib/modules/*/misc.
    mod_dir="$(dirname "$mod_path" | sed 's;^.*/;;')"
    [ "$mod_dir" = "misc" ] || return

    # In case if kernel configuration (for currently loaded kernel) requires
    # module signature, check if module is signed.
    if test "$(kernel_requires_module_signature $(uname -r))" = "1"; then
        [ "$(module_signed "$mod")" = "1" ] || return
    fi

    echo "1"
}

# Check if required modules are installed in the system and versions match.
#
# Returns "1" on success, none otherwise.
setup_complete()
{
    [ "$(module_available vboxguest)"   = "1" ] || return
    [ "$(module_available vboxsf)"      = "1" ] || return

    # All modules are in place.
    echo "1"
}

# setup_script
setup()
{
    info "Setting up modules"

    # chcon is needed on old Fedora/Redhat systems.  No one remembers which.
    test ! -e /etc/selinux/config ||
        chcon -t bin_t "$BUILDINTMP" 2>/dev/null

    if test -z "$INSTALL_NO_MODULE_BUILDS"; then
        # Check whether modules setup is already complete for currently running kernel.
        # Prevent unnecessary rebuilding in order to speed up booting process.
        if test "$(setup_complete)" = "1"; then
            info "VirtualBox Guest Additions kernel modules $VBOX_VERSION $VBOX_REVISION are \
already available for kernel $TARGET_VER and do not require to be rebuilt."
        else
            info "Building the VirtualBox Guest Additions kernel modules.  This may take a while."
            info "To build modules for other installed kernels, run"
            info "  /sbin/rcvboxadd quicksetup <version>"
            info "or"
            info "  /sbin/rcvboxadd quicksetup all"
            if test -d /lib/modules/"$TARGET_VER"/build; then
                setup_modules "$TARGET_VER"
                depmod
            else
                info "Kernel headers not found for target kernel $TARGET_VER. \
Please install them and execute
  /sbin/rcvboxadd setup"
            fi
        fi
    fi
    create_vbox_user
    create_udev_rule
    test -n "${INSTALL_NO_MODULE_BUILDS}" || create_module_rebuild_script
    shared_folder_setup
    # Create user group which will have permissive access to DRP IPC server socket.
    groupadd -r -f vboxdrmipc >/dev/null 2>&1

    if  running_vboxguest || running_vboxadd; then
        # Only warn user if currently loaded modules version do not match Guest Additions Installation.
        check_running_module_version "vboxguest" || info "Running kernel modules will not be replaced until the system is restarted or 'rcvboxadd reload' triggered"
    fi

    # Put the X.Org driver in place.  This is harmless if it is not needed.
    # Also set up the OpenGL library.
    myerr=`"${INSTALL_DIR}/init/vboxadd-x11" setup 2>&1`
    test -z "${myerr}" || log "${myerr}"

    return 0
}

# cleanup_script
cleanup()
{
    if test -z "${INSTALL_NO_MODULE_BUILDS}"; then
        # Delete old versions of VBox modules.
        cleanup_modules
        depmod

        # Remove old module sources
        for i in $OLDMODULES; do
          rm -rf /usr/src/$i-*
        done
    fi

    # Clean-up X11-related bits
    "${INSTALL_DIR}/init/vboxadd-x11" cleanup

    # Remove other files
    if test -z "${INSTALL_NO_MODULE_BUILDS}"; then
        rm -f /etc/kernel/postinst.d/vboxadd /etc/kernel/prerm.d/vboxadd
        rmdir -p /etc/kernel/postinst.d /etc/kernel/prerm.d 2>/dev/null || true
    fi
    rm -f /sbin/mount.vboxsf 2>/dev/null
    rm -f /etc/udev/rules.d/60-vboxadd.rules 2>/dev/null
    udevadm control --reload >/dev/null 2>&1 || true
    udevcontrol reload_rules >/dev/null 2>&1 || true
}

start()
{
    begin "Starting."

    # Check if kernel modules for currently running kernel are ready
    # and rebuild them if needed.
    test "$(setup_complete)" = "1" || setup

    # Warn if Secure Boot setup not yet complete.
    if test "$(kernel_requires_module_signature)" = "1" && test -z "$DEB_KEY_ENROLLED"; then
        if test -n "$HAVE_DEB_KEY"; then
            info "You must re-start your system to finish secure boot set-up."
        else
            info "You must sign vboxguest, vboxsf and
vboxvideo (if present) kernel modules before using
VirtualBox Guest Additions. See the documentation
for your Linux distribution."
        fi
    fi

    if test -z "${INSTALL_NO_MODULE_BUILDS}"; then
        test -d /sys &&
            ps -A -o comm | grep -q '/*udevd$' 2>/dev/null ||
            no_udev=1
        check_running_module_version "vboxguest" || {
            rm -f $dev || {
                fail "Cannot remove $dev"
            }
            rm -f $userdev || {
                fail "Cannot remove $userdev"
            }
            # Assuming modules were just (re-)built, try to reload everything.
            reload
        }
        case "$no_udev" in 1)
            do_vboxguest_non_udev;;
        esac
    fi  # INSTALL_NO_MODULE_BUILDS

    return 0
}

stop()
{
    begin "Stopping."

    if test -r /etc/ld.so.conf.d/00vboxvideo.conf; then
        rm /etc/ld.so.conf.d/00vboxvideo.conf
        ldconfig
    fi
    if ! umount -a -t vboxsf 2>/dev/null; then
        # Make sure we only fail, if there are truly no more vboxsf
        # mounts in the system.
        [ -n "$(findmnt -t vboxsf)" ] && fail "Cannot unmount vboxsf folders"
    fi
    test -n "${INSTALL_NO_MODULE_BUILDS}" ||
        info "You may need to restart your guest system to finish removing guest drivers or consider running 'rcvboxadd reload'."
    return 0
}

check_root()
{
    # Check if script is running with root privileges and exit if it does not.
    [ `id -u` -eq 0 ] || early_fail "root privileges are required"
}

# Check if process with this PID is running.
check_pid()
{
    pid="$1"

    test -n "$pid" -a -d "/proc/$pid"
}

# A wrapper for check_running_module_version.
# Go through the list of Guest Additions' modules and
# verify if they are loaded and running version matches
# to current installation version. Skip vboxvideo since
# it is not loaded for old guests.
check_status_kernel()
{
    for mod in vboxguest vboxsf; do

        for attempt in 1 2 3 4 5; do

            # Wait before the next attempt.
            [ $? -ne 0 ] && sleep 1

            running_module "$mod"
            if [ $? -eq 0 ]; then
                mod_is_running="1"
                check_running_module_version "$mod"
                [ $? -eq 0 ] && break
            else
                mod_is_running=""
                false
            fi

        done

        # In case of error, try to print out proper reason of failure.
        if [ $? -ne 0 ]; then
            # Was module loaded?
            if [ -z "$mod_is_running" ]; then
                info "module $mod is not loaded"
            else
                # If module was loaded it means that it has incorrect version.
                info "currently loaded module $mod version ($(running_module_version "$mod")) does not match to VirtualBox Guest Additions installation version ($VBOX_VERSION $VBOX_REVISION)"
            fi

            # Set "bad" rc.
            false
        fi

    done
}

# Check whether user-land processes are running.
# Currently only check for VBoxService.
check_status_user()
{
    [ -r "$VBOXSERVICE_PIDFILE" ] && check_pid "$(cat $VBOXSERVICE_PIDFILE)" >/dev/null 2>&1
}

send_signal_by_pidfile()
{
    sig="$1"
    pidfile="$2"

    if [ -f "$pidfile" ]; then
        check_pid $(cat "$pidfile")
        if [ $? -eq 0 ]; then
            kill "$sig" $(cat "$pidfile") >/dev/null 2>&1
        else
            # Do not spoil $?.
            true
        fi
    else
        # Do not spoil $?.
        true
    fi
}

# SIGUSR1 is used in order to notify VBoxClient processes that system
# update is started or kernel modules are going to be reloaded,
# so VBoxClient can release vboxguest.ko resources and then restart itself.
send_signal()
{
    sig="$1"
    # Specify whether we sending signal to VBoxClient parent (control)
    # process or a child (actual service) process.
    process_type="$2"

    pidfile_postfix=""
    [ -z "$process_type" ] || pidfile_postfix="-$process_type"

    for user_name in $(getent passwd | cut -d ':' -f 1); do

        # Filter out empty login names (paranoia).
        [ -n "$user_name" ] || continue

        user_shell=$(getent passwd "$user_name" | cut -d ':' -f 7)

        # Filter out login names with not specified shells (paranoia).
        [ -n "$user_shell" ] || continue

        # Filter out know non-login account names.
        case "$user_shell" in
        *nologin)   skip_user_home="1";;
        *sync)      skip_user_home="1";;
        *shutdown)  skip_user_home="1";;
        *halt)      skip_user_home="1";;
        *)          skip_user_home=""
        esac
        [ -z "$skip_user_home" ] || continue;

        user_home=$(getent passwd "$user_name" | cut -d ':' -f 6)

        for pid_file in "$user_home"/.vboxclient-*"$pidfile_postfix".pid; do

            [ -r "$pid_file" ] || continue

            # If process type was not specified, we assume that signal supposed
            # to be sent to legacy VBoxClient processes which have different
            # pidfile name pattern (it does not contain "control" or "service").
            # Skip those pidfiles who has.
            [ -z "$process_type" -a -n "$(echo "$pid_file" | grep "control")" ] && continue
            [ -z "$process_type" -a -n "$(echo "$pid_file" | grep "service")" ] && continue

            send_signal_by_pidfile -USR1 "$pid_file"

        done
    done
}

# Helper function which executes a command, prints error message if command fails,
# and preserves command execution status for further processing.
try_load_preserve_rc()
{
    cmd="$1"
    msg="$2"

    $cmd >/dev/null 2>&1

    rc=$?
    [ $rc -eq 0 ] || info "$msg"

    return $rc
}

reload()
{
    begin "reloading kernel modules and services"

    # Stop VBoxService if running.
    $VBOX_SERVICE_SCRIPT status >/dev/null 2>&1
    if [ $? -eq 0 ]; then
        $VBOX_SERVICE_SCRIPT stop >/dev/null 2>&1 || fail "unable to stop VBoxService"
    fi

    # Unmount Shared Folders.
    umount -a -t vboxsf >/dev/null 2>&1 || fail "unable to unmount shared folders, mount point(s) might be still in use"

    # Stop VBoxDRMClient.
    send_signal_by_pidfile "-USR1" "/var/run/VBoxDRMClient" || fail "unable to stop VBoxDRMClient"

    if [ $? -eq 0 ]; then
        # Tell legacy VBoxClient processes to release vboxguest.ko references.
        send_signal "-USR1" ""

        # Tell compatible VBoxClient processes to release vboxguest.ko references.
        send_signal "-USR1" "service"

        # Try unload.
        for attempt in 1 2 3 4 5; do

            # Give VBoxClient processes some time to close reference to vboxguest module.
            [ $? -ne 0 ] && sleep 1

            # Try unload drivers unconditionally (ignore previous command exit code).
            # If final goal of unloading vboxguest.ko won't be met, we will fail on
            # the next step anyway.
            running_vboxvideo && modprobe -r vboxvideo >/dev/null  2>&1
            running_vboxsf && modprobe -r vboxsf >/dev/null  2>&1
            running_vboxguest
            if [ $? -eq 0 ]; then
                modprobe -r vboxguest >/dev/null  2>&1
                [ $? -eq 0 ] && break
            else
                # Do not spoil $?.
                true
            fi
        done

        # Check if we succeeded with unloading vboxguest after several attempts.
        running_vboxguest
        if [ $? -eq 0 ]; then
            info "cannot reload kernel modules: one or more module(s) is still in use"
            false
        else
            # Do not spoil $?.
            true
        fi

        # Load drivers (skip vboxvideo since it is not loaded for very old guests).
        [ $? -eq 0 ] && try_load_preserve_rc "modprobe vboxguest" "unable to load vboxguest kernel module, see dmesg"
        [ $? -eq 0 ] && try_load_preserve_rc "modprobe vboxsf" "unable to load vboxsf kernel module, see dmesg"

        # Start VBoxService and VBoxDRMClient.
        [ $? -eq 0 ] && try_load_preserve_rc "$VBOX_SERVICE_SCRIPT start" "unable to start VBoxService"

        # Reload VBoxClient processes.
        [ $? -eq 0 ] && try_load_preserve_rc "send_signal -USR1 control" "unable to reload user session services"

        # Check if we just loaded modules of correct version.
        [ $? -eq 0 ] && try_load_preserve_rc "check_status_kernel" "kernel modules were not reloaded"

        # Check if user-land processes were restarted as well.
        [ $? -eq 0 ] && try_load_preserve_rc "check_status_user" "user-land services were not started"

        if [ $? -eq 0 ]; then
            # Take reported version of running Guest Additions from running vboxguest module (as a paranoia check).
            info "kernel modules and services $(running_module_version "vboxguest") reloaded"
            info "NOTE: you may still consider to re-login if some user session specific services (Shared Clipboard, Drag and Drop, Seamless or Guest Screen Resize) were not restarted automatically"
        else
            # In case of failure, sent SIGTERM to abandoned control processes to remove leftovers from failed reloading.
            send_signal "-TERM" "control"

            fail "kernel modules and services were not reloaded"
        fi
    else
        fail "cannot stop user services"
    fi
}

dmnstatus()
{
    if running_vboxguest; then
        echo "The VirtualBox Additions are currently running."
    else
        echo "The VirtualBox Additions are not currently running."
    fi
}

for i; do
    case "$i" in quiet) QUIET=yes;; esac
done
case "$1" in
# Does setup without clean-up first and marks all kernels currently found on the
# system so that we can see later if any were added.
start)
    check_root
    start
    ;;
# Tries to build kernel modules for kernels added since start.  Tries to unmount
# shared folders.  Uninstalls our Chromium 3D libraries since we can't always do
# this fast enough at start time if we discover we do not want to use them.
stop)
    check_root
    stop
    ;;
restart)
    check_root
    restart
    ;;
# Tries to reload kernel modules and restart user processes.
reload)
    check_root
    reload
    ;;
# Setup does a clean-up (see below) and re-does all Additions-specific
# configuration of the guest system, including building kernel modules for the
# current kernel.
setup)
    check_root
    cleanup && start
    ;;
# Builds kernel modules for the specified kernels if they are not already built.
quicksetup)
    check_root
    if test x"$2" = xall; then
       for topi in /lib/modules/*; do
           KERN_VER="${topi%/misc}"
           KERN_VER="${KERN_VER#/lib/modules/}"
           setup_modules "$KERN_VER"
        done
    elif test -n "$2"; then
        setup_modules "$2"
    else
        setup_modules "$TARGET_VER"
    fi
    ;;
# Clean-up removes all Additions-specific configuration of the guest system,
# including all kernel modules.
cleanup)
    check_root
    cleanup
    ;;
status)
    dmnstatus
    ;;
status-kernel)
    check_root
    check_status_kernel
    if [ $? -eq 0 ]; then
        info "kernel modules are loaded"
    else
        info "kernel modules were not loaded"
        false
    fi
    ;;
status-user)
    check_root
    check_status_user
    if [ $? -eq 0 ]; then
        info "user-land services are running"
    else
        info "user-land services are not running"
        false
    fi
    ;;
*)
    echo "Usage: $0 {start|stop|restart|reload|status|status-kernel|status-user|setup|quicksetup|cleanup} [quiet]"
    exit 1
esac

exit
