edgebsd

#!/bin/sh
#Copyright (c) 2021-2023 Pierre Pronchery <khorben@EdgeBSD.org>
#This file is part of EdgeBSD NVMM
#This code is derived from software contributed to the EdgeBSD Project
#by Pierre Pronchery <khorben@EdgeBSD.org>
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
#DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
#FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
#DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
#SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
#CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
#OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
#OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#variables
#essential
VENDOR="@VENDOR@"
PACKAGE="@PACKAGE@"
#executables
CAT="cat"
DEBUG="_debug"
EDGEBSD_QEMU="@PREFIX@/libexec/$VENDOR/$PACKAGE/edgebsd-qemu"
MKDIR="mkdir"
NC="nc"
NVMMCTL="/usr/sbin/nvmmctl"
SCREEN="@BINDIR@/screen"
TR="tr"
#settings
CHROOTDIR=
DEVNULL="/dev/null"
DRYRUN=0
PROGNAME="nvmm-xl"
RUNAS=
RUNDIR="/var/run/nvmm"
SCREENDIR="/tmp/screens"
SYSCONFDIR="@SYSCONFDIR@"
USER="$(id -un)"
VERBOSE=1
#load local settings
[ -f "$SYSCONFDIR/$VENDOR/$PACKAGE/$PROGNAME.conf" ] &&
. "$SYSCONFDIR/$VENDOR/$PACKAGE/$PROGNAME.conf"
[ -n "$HOME" -a -f "$HOME/.config/$VENDOR/$PACKAGE/$PROGNAME.conf" ] &&
. "$HOME/.config/$VENDOR/$PACKAGE/$PROGNAME.conf"
#functions
#debug
_debug()
{
[ $VERBOSE -ge 3 ] && echo "$@" 1>&2
[ $DRYRUN -ne 0 ] || "$@"
}
#error
_error()
{
echo "$PROGNAME: $@" 1>&2
return 2
}
#nvmm_xl
_nvmm_xl()
{
shift $((OPTIND - 1))
if [ $# -eq 0 ]; then
_usage
return $?
fi
command="$1"
shift
case "$command" in
"cd-eject" \
|"console" \
|"create" \
|"destroy" \
|"info" \
|"list" \
|"monitor" \
|"network" \
|"pause" \
|"reboot" \
|"shutdown" \
|"unpause")
command=$(echo "$command" | $TR -- - _)
"_nvmm_xl_$command" "$@"
return $?
;;
*)
_usage "$command: Unknown command"
return $?
;;
esac
}
#nvmm_xl_cd_eject
_nvmm_xl_cd_eject()
{
command_args=
while getopts "fO:qv" name; do
case "$name" in
f)
command_args="$command_args -f"
;;
O)
eval "${OPTARG%%=*}"="${OPTARG#*=}"
;;
q)
VERBOSE=0
;;
v)
VERBOSE=$((VERBOSE + 1))
;;
*)
_usage
return $?
;;
esac
done
shift $((OPTIND - 1))
if [ $# -ne 2 ]; then
_usage "cd-eject: A domain and a device are required"
return $?
fi
domain="$1"
device="$2"
_nvmm_xl_command "$domain" "eject$command_args $device"
}
#nvmm_xl_command
_nvmm_xl_command()
{(
if [ $# -lt 2 ]; then
_usage "command: A domain and a command are required"
return $?
fi
domain="$1"
socket="$RUNDIR/$domain/socket"
shift
$DEBUG echo "$@" | $DEBUG $NC -N -U "$socket" > "$DEVNULL"
)}
#nvmm_xl_command_output
_nvmm_xl_command_output()
{(
if [ $# -lt 2 ]; then
_usage "command: A domain and a command are required"
return $?
fi
domain="$1"
socket="$RUNDIR/$domain/socket"
shift
$DEBUG echo "$@" | $DEBUG $NC -N -U "$socket" | (cnt=0
prevline=
while read line; do
cnt=$((cnt + 1))
[ $cnt -gt 2 ] && echo "$line"
done)
)}
#nvmm_xl_command_sync
_nvmm_xl_command_sync()
{(
if [ $# -lt 2 ]; then
_usage "command: A domain and a command are required"
return $?
fi
domain="$1"
socket="$RUNDIR/$domain/socket"
shift
$DEBUG echo "$@" | $DEBUG $NC -U "$socket" > "$DEVNULL"
)}
#nvmm_xl_console
_nvmm_xl_console()
{
while getopts "O:qv" name; do
case "$name" in
O)
eval "${OPTARG%%=*}"="${OPTARG#*=}"
;;
q)
VERBOSE=0
;;
v)
VERBOSE=$((VERBOSE + 1))
;;
*)
_usage
return $?
;;
esac
done
shift $((OPTIND - 1))
if [ $# -ne 1 ]; then
_usage "console: A domain is required"
return $?
fi
domain="$1"
session="_nvmm-$domain"
for s in "$SCREENDIR/S-$USER/"*"._nvmm-"*; do
[ -S "$s" ] || continue
if [ "${s#$SCREENDIR/S-$USER/*.}" = "$session" ]; then
$DEBUG $SCREEN -x "$session"
return $?
fi
done
_error "$domain: Domain not available"
return $?1
}
#nvmm_xl_create
_nvmm_xl_create()
{
console=0
while getopts "cO:qv" name; do
case "$name" in
c)
console=1
;;
O)
eval "${OPTARG%%=*}"="${OPTARG#*=}"
;;
q)
VERBOSE=0
;;
v)
VERBOSE=$((VERBOSE + 1))
;;
*)
_usage
return $?
;;
esac
done
shift $((OPTIND - 1))
if [ $# -ne 1 ]; then
_usage "create: A configuration file is required"
return $?
fi
filename="$1"
if [ ! -f "$filename" ]; then
_error "$filename: Domain not configured"
return $?
fi
domain="${filename##*/}"
section=
#variables
edgebsd_qemu_args=
[ -n "$CHROOTDIR" ] && edgebsd_qemu_args="$edgebsd_qemu_args -c $CHROOTDIR"
[ -n "$RUNAS" ] && edgebsd_qemu_args="$edgebsd_qemu_args -u $RUNAS"
session="_nvmm-$domain"
screen_args=
[ $console -eq 0 ] && screen_args="-d -m"
$DEBUG $SCREEN -S "$session" $screen_args \
$EDGEBSD_QEMU $edgebsd_qemu_args "$domain" "$filename"
}
#nvmm_xl_destroy
_nvmm_xl_destroy()
{
while getopts "O:qv" name; do
case "$name" in
O)
eval "${OPTARG%%=*}"="${OPTARG#*=}"
;;
q)
VERBOSE=0
;;
v)
VERBOSE=$((VERBOSE + 1))
;;
*)
_usage
return $?
;;
esac
done
shift $((OPTIND - 1))
if [ $# -ne 1 ]; then
_usage "destroy: A domain is required"
return $?
fi
domain="$1"
_nvmm_xl_command_sync "$domain" "quit"
}
#nvmm_xl_info
_nvmm_xl_info()
{
while getopts "O:qv" name; do
case "$name" in
O)
eval "${OPTARG%%=*}"="${OPTARG#*=}"
;;
q)
VERBOSE=0
;;
v)
VERBOSE=$((VERBOSE + 1))
;;
*)
_usage
return $?
;;
esac
done
shift $((OPTIND - 1))
if [ $# -lt 1 ]; then
_usage "info: A domain must be provided"
return $?
elif [ $# -gt 2 ]; then
_usage "info: A sub-command may be provided"
return $?
fi
domain="$1"
subcommand="$2"
_nvmm_xl_command_output "$domain" "info $subcommand"
}
#nvmm_xl_list
_nvmm_xl_list()
{
while getopts "O:qv" name; do
case "$name" in
O)
eval "${OPTARG%%=*}"="${OPTARG#*=}"
;;
q)
VERBOSE=0
;;
v)
VERBOSE=$((VERBOSE + 1))
;;
*)
_usage
return $?
;;
esac
done
shift $((OPTIND - 1))
if [ $# -ne 0 ]; then
_usage "list: Parameters are not supported"
return $?
fi
#XXX output more information about each domain
echo "Name"
echo "NVMM"
for domain in "$SCREENDIR/S-$USER/"*"._nvmm-"*; do
[ -S "$domain" ] || continue
echo "${domain##*._nvmm-}"
done
}
#nvmm_xl_monitor
_nvmm_xl_monitor()
{
while getopts "O:qv" name; do
case "$name" in
O)
eval "${OPTARG%%=*}"="${OPTARG#*=}"
;;
q)
VERBOSE=0
;;
v)
VERBOSE=$((VERBOSE + 1))
;;
*)
_usage
return $?
;;
esac
done
shift $((OPTIND - 1))
if [ $# -ne 1 ]; then
_usage "monitor: A domain is required"
return $?
fi
domain="$1"
socket="$RUNDIR/$domain/socket"
$DEBUG $NC -U "$socket"
}
#nvmm_xl_network
_nvmm_xl_network()
{
while getopts "O:qv" name; do
case "$name" in
O)
eval "${OPTARG%%=*}"="${OPTARG#*=}"
;;
q)
VERBOSE=0
;;
v)
VERBOSE=$((VERBOSE + 1))
;;
*)
_usage
return $?
;;
esac
done
shift $((OPTIND - 1))
if [ $# -lt 3 ]; then
_usage "network: A domain, an interface, a sub-command are required"
return $?
fi
domain="$1"
interface="$2"
subcommand="$3"
#XXX provide this help more easily
case "$subcommand" in
"connect")
subcommand="set_link $interface on"
;;
"disconnect")
subcommand="set_link $interface off"
;;
"help"|*)
_usage "network: $subcommand: Unsupported sub-command
Sub-commands supported:
connect
disconnect
"
;;
esac
_nvmm_xl_command "$domain" "$subcommand"
}
#nvmm_xl_pause
_nvmm_xl_pause()
{
while getopts "O:qv" name; do
case "$name" in
O)
eval "${OPTARG%%=*}"="${OPTARG#*=}"
;;
q)
VERBOSE=0
;;
v)
VERBOSE=$((VERBOSE + 1))
;;
*)
_usage
return $?
;;
esac
done
shift $((OPTIND - 1))
if [ $# -ne 1 ]; then
_usage "pause: A domain is required"
return $?
fi
domain="$1"
_nvmm_xl_command "$domain" "stop"
}
#nvmm_xl_reboot
_nvmm_xl_reboot()
{
while getopts "O:qv" name; do
case "$name" in
O)
eval "${OPTARG%%=*}"="${OPTARG#*=}"
;;
q)
VERBOSE=0
;;
v)
VERBOSE=$((VERBOSE + 1))
;;
*)
_usage
return $?
;;
esac
done
shift $((OPTIND - 1))
if [ $# -ne 1 ]; then
_usage "reboot: A domain is required"
return $?
fi
domain="$1"
_nvmm_xl_command "$domain" "system_reset"
}
#nvmm_xl_shutdown
_nvmm_xl_shutdown()
{
fallback=0
while getopts "FO:qv" name; do
case "$name" in
F)
#XXX implement
fallback=1
;;
O)
eval "${OPTARG%%=*}"="${OPTARG#*=}"
;;
q)
VERBOSE=0
;;
v)
VERBOSE=$((VERBOSE + 1))
;;
*)
_usage
return $?
;;
esac
done
shift $((OPTIND - 1))
if [ $# -ne 1 ]; then
_usage "shutdown: A domain is required"
return $?
fi
domain="$1"
_nvmm_xl_command_sync "$domain" "system_powerdown"
}
#nvmm_xl_unpause
_nvmm_xl_unpause()
{
while getopts "O:qv" name; do
case "$name" in
O)
eval "${OPTARG%%=*}"="${OPTARG#*=}"
;;
q)
VERBOSE=0
;;
v)
VERBOSE=$((VERBOSE + 1))
;;
*)
_usage
return $?
;;
esac
done
shift $((OPTIND - 1))
if [ $# -ne 1 ]; then
_usage "unpause: A domain is required"
return $?
fi
domain="$1"
_nvmm_xl_command "$domain" "cont"
}
#usage
_usage()
{
[ $# -ge 1 ] && echo "$PROGNAME: $@" 1>&2
echo "Usage: $PROGNAME command [options...]" 1>&2
echo 1>&2
echo "Commands supported:" 1>&2
echo " cd-eject Eject a CD from a removable device" 1>&2
echo " console Attach the console of a Virtual Machine instance" 1>&2
echo " create [-c] Start a Virtual Machine instance" 1>&2
echo " destroy Power off a Virtual Machine instance" 1>&2
echo " info Print information about a Virtual Machine instance" 1>&2
echo " list List the Virtual Machine instances available" 1>&2
echo " monitor Attach the monitor of a Virtual Machine instance" 1>&2
echo " network Manage the network interfaces of a Virtual Machine instance" 1>&2
echo " pause Pause a Virtual Machine instance" 1>&2
echo " reboot Reboot a Virtual Machine instance" 1>&2
echo " shutdown [-F] Stop a Virtual Machine instance gracefully" 1>&2
echo " unpause Unpause a Virtual Machine instance" 1>&2
return 1
}
#main
_nvmm_xl "$@"