#!/bin/bash
# $Id: led-lights.sh 98103 2023-01-17 14:15:46Z vboxsync $
## @file
# VirtualBox guest LED demonstration test
#

#
# Copyright (C) 2021-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
#

#
# Usage:
#       led-lights.sh [-a | -r]
#

#
# Test script to twiddle the console LEDs of a VirtualBox VM.
#
# This is not an automated test, just something for humans to look
# at, to convince themselves that the VM console LEDs are working.
# By default it cycles through the LED types in a specific order.
#
# '-a' twiddles all possible LEDs at the same time
# '-r' reverses the default order
#
# For instance, run the script in 2 VMs at once, one with '-r'.
#
# LEDs are not expected to track perfectly, as other OS activities
# will light them (and buffer cache effects can delay things).  Just
# make sure that all the tested ones (hard disk, optical, USB storage,
# floppy, shared folders, net) are working.  Expected activity:
#
#     - Disk & optical devices show solid 'read'
#     - Virtual USB disk & optical devices show 'write' on the USB LED
#     - Floppy devices and shared folders alternate 'read/write'
#     - Net blinks 'write'
#
# Pre-VM setup:
#
#     Download or locate a bootable Linux ISO able to be used 'live'.
#     Make a tarball of this script + extra junk:
#
#         $ dd if=/dev/zero of=junk bs=100k count=1
#         $ tar cf floppy.img led-lights.sh junk
#
#     NOTE: floppy.img must be >= 20KiB or you will get I/O errors!
#
# VM setup:
#
#     New VM; type: Linux (subtype to match ISO); create default HD.
#     VM Settings:
#         System > raise base memory to 4GiB
#         Storage > insert 'Live bootable' Linux ISO image;
#                   turn on 'Live CD/DVD'
#         Storage > add floppy controller (i82078); insert floppy.img
#         Storage > add USB controller; insert USB HD & USB CD
#         System > move Optical before Floppy in boot order
#         Shared Folders > set up one or more Shared Folders if desired
#                   (they should be permanent, auto-mount,
#                    writable, with at least 10MB free space)
#
# Boot the VM.  Open a shell, become root, optionally install
# VirtualBox Guest Utilities to access Shared Folders, then extract
# and run this script:
#
#     $ sudo bash
#     # yum install virtualbox-guest-utils   # for Shared Folders
#     # tar xf /dev/fd0 led-lights.sh
#     # ./led-lights.sh                      [-a | -r]

if [ ! -w / ]; then
    echo "Must be run as root!" 1>&2
    exit 1
fi

all_all=false
reverse=false

if [ "x$1" = "x-a" ]; then
    all_all=true
fi

if [ "x$1" = "x-r" ]; then
    reverse=true
fi

# Copy binaries to RAM tmpfs to avoid CD I/O after cache purges
MYTMP=/tmp/led-lights.$$
mkdir $MYTMP
for bin in $(which dd sleep sync); do
    case $bin in
        /*)
            cp -p $bin $MYTMP
        ;;
    esac
done
export MYTMP PATH=$MYTMP:$PATH

set -o monitor

# Make device reads keep hitting the 'hardware'
# even if the whole medium fits in cache...
drop_cache()
{
    echo 1 >/proc/sys/vm/drop_caches
}

activate()
{
    kill -CONT -$1 2>/dev/null
}

suppress()
{
    $all_all || kill -STOP -$1 2>/dev/null
}

declare -a pids pidnames
cpids=0

twiddle()
{
    let ++cpids
    new_pid=$!
    pidname=$1
    pids[$cpids]=$new_pid
    pidnames[$cpids]=$pidname
    suppress $new_pid
}

hide_stderr()
{
    exec 3>&2 2>/dev/null
}

show_stderr()
{
    exec 2>&3 3>&-
}

bail()
{
    hide_stderr
    for pid in ${pids[*]}; do
        activate $pid
        kill -TERM -$pid
        kill -TERM $pid
    done 2>/dev/null
    rm -rf $MYTMP
    kill $$
}

trap "bail" INT

drives()
{
    echo $(
        awk '$NF ~/^('$1')$/ { print $NF }' /proc/partitions
    )
}

# Prevent job control 'stopped' msgs during twiddler startup
hide_stderr

# Hard disks
for hdd in $(drives '[sh]d.'); do
    while :; do
        dd if=/dev/$hdd of=/dev/null
        drop_cache
    done 2>/dev/null &
    twiddle disk:$hdd
done

# Optical drives
for odd in $(drives 'sr.|scd.'); do
    while :; do
        dd if=/dev/$odd of=/dev/null
        drop_cache
    done 2>/dev/null &
    twiddle optical:$odd
done

# Floppy drives
for fdd in $(drives 'fd.'); do
    while :; do
        dd if=/dev/$fdd of=$MYTMP/$fdd bs=1k count=20
        dd of=/dev/$fdd if=$MYTMP/$fdd bs=1k count=20
    done 2>/dev/null &
    twiddle floppy:$fdd
done

# Shared folders
if ! lsmod | grep -q vboxsf; then
    echo
    echo "Note: to test the Shared Folders LED, install this"
    echo "distro's VirtualBox Guest Utilities package, e.g.:"
    echo
    echo "    # yum install virtualbox-guest-utils   (Red Hat family)"
    echo "    # apt install virtualbox-guest-utils   (Debian family)"
    echo
fi >&3   # original stderr
for shf in $(mount -t vboxsf | awk '{ print $3 }'); do
    while :; do
        dd if=/dev/urandom of=$shf/tmp.led-lights.$$ bs=100k count=100
        for rep in $(seq 1 10); do
            drop_cache
            dd of=/dev/zero if=$shf/tmp.led-lights.$$ bs=100k count=100
        done
        sync
        rm -f $shf/tmp.led-lights.$$
    done >/dev/null 2>&1 &
    twiddle sharedfs:$shf
done

# Network
ping -i.2 1.2.3.4 >/dev/null &
twiddle net

# Untested LED: Graphics3D -- add some day?

sleep 0.1
show_stderr

if $reverse; then
    seq=$(seq $cpids -1 1)
else
    seq=$(seq 1 $cpids)
fi

show_intr()
{
    intr=$(stty -a | sed -n '/intr/ { s/.*intr *=* *//; s/[ ;].*//p }')
    echo
    echo "[ Hit $intr to stop ]"
    echo
}

if $all_all; then
    printf "%s ...\n" ${pidnames[*]}
    show_intr
    wait
else
    CEOL=$(tput el)
    show_intr
    while :; do
        for pidx in $seq; do
            pid=${pids[$pidx]}
            pidname=${pidnames[$pidx]}
            echo -e -n "$pidname$CEOL\r"
            shift
            activate $pid
            sleep 2
            suppress $pid
            sync
            sleep .5
        done
    done
fi
