VBoxHeadlessXOrg.sh revision c7450c9040258e7d6fee49769efbf82ba7ecee6e
#!/bin/sh
# $Id$
#
# VirtualBox X Server auto-start service.
#
# Copyright (C) 2012 Oracle Corporation
#
# This file is part of VirtualBox Open Source Edition (OSE), as
# available from http://www.virtualbox.org. This file is free software;
# you can redistribute it and/or modify it under the terms of the GNU
# General Public License (GPL) as published by the Free Software
# Foundation, in version 2 as it comes in the "COPYING" file of the
# VirtualBox OSE distribution. VirtualBox OSE is distributed in the
# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
#
PATH=$PATH:/bin:/sbin:/usr/sbin
## Start one or several X servers in the background for use with headless
# rendering. For details, options and configuration see the usage() function
# further down.
#
# I have tried to follow the best practices I could find for writing a Linux
# service (and doing it in shell script) which should work well with
# traditional and modern service systems using minimal init or service files.
# In our case this boils down to:
# * Start with a single command line, stop using one of ${EXIT_SIGNALS} below.
# * Stopping with a signal can be done safely using the pid stored in the
# pid-file and our (presumably unique) command name. For this reason we
# only support running one instance of the service though.
# * Start in the foreground. Systems without proper service control can take
# care of the backgrounding in the init script.
# * Clean up all sub-processes (X servers) ourselves when we are stopped
# cleanly and don't provide any other way to clean them up automatically (in
# case we are stopped uncleanly) as we don't know of a generic safe way to
# do so, though some service management systems (i.e. systemd) can do so.
# (A more thorough automatic clean-up would be possible if Xorg didn't
# potentially have to be run as root, so that we could run all processes
# using a service-specific user account and just terminate all processes
# run by that user to clean up.)
## Default configuration file name.
# @note This is not very nice - /etc/default is actually Debian-specific.
CONFIGURATION_FILE=/etc/default/virtualbox
## The name of this script.
SCRIPT_NAME="$0"
## The service name.
SERVICE_NAME="vboxheadlessxorg"
## The service description.
SERVICE_DESCRIPTION="Headless rendering service"
## Signals and conditions which may be used to terminate the service.
EXIT_SIGNALS="EXIT HUP INT QUIT ABRT TERM"
## The default run-time data folder.
DEFAULT_RUN_FOLDER="/var/run/${SERVICE_NAME}/"
## The default X server configuration directory.
DEFAULT_CONFIGURATION_FOLDER="${DEFAULT_RUN_FOLDER}/xorg.conf.d/"
## The extra data key used to provide the list of available X server displays.
EXTRA_DATA_KEY_DISPLAYS="HeadlessXServer/Displays"
## The extra data key used to specify the X server authority file.
EXTRA_DATA_KEY_AUTH="HeadlessXServer/AuthFile"
## Print usage information for the service script.
## @todo Perhaps we should support some of the configuration file options from
# the command line. Opinions welcome.
## @todo Possibly extract this information for the user manual.
usage() {
cat << EOF
Usage:
$(basename "${SCRIPT_NAME}") [<options>]
Start one or several X servers in the background for use with headless
rendering. We only support X.Org Server at the moment. On service start-up available graphics devices are detected and an X server configuration file is
generated for each. We attempt to start an X server process for each
configuration file. The process is configurable by setting values in a file as
described below.
Options:
-c|--conf-file) Specify an alternative locations for the configuration
file. The default location is:
"${CONFIGURATION_FILE}"
--help|--usage Print this text.
The optional configuration file should contain a series of lines of the form
"KEY=value". It will be read in as a command shell sub-script. Here is the
current list of possible key settings with a short explanation. Usually it
should be sufficient to change the value of \${HEADLESS_X_ORG_USERS} and to
leave all other settings unchanged.
HEADLESS_X_ORG_CONFIGURATION_FOLDER
The folder where the X server configuration files are to be created.
HEADLESS_X_ORG_LOG_FOLDER
The folder where log files will be saved.
HEADLESS_X_ORG_LOG_FILE
The main log file name.
HEADLESS_X_ORG_RUN_FOLDER
The folder to store run-time data in.
HEADLESS_X_ORG_WAIT_FOR_PREREQUISITES
Command to execute to wait until all dependencies for the X servers are
available. The default command waits until the udev event queue has
settled. The command may return failure to signal that it has given up.
HEADLESS_X_ORG_USERS
List of users who will have access to the X servers started and for whom we
will provide the configuration details via VirtualBox extra data. This
variable is only used by the commands in the default configuration
(\${HEADLESS_X_ORG_SERVER_PRE_COMMAND} and
\${HEADLESS_X_ORG_SERVER_POST_COMMAND}), and not by the service itself.
HEADLESS_X_ORG_FIRST_DISPLAY
The first display number which will be used for a started X server. The
others will use the following numbers.
HEADLESS_X_ORG_SERVER_PRE_COMMAND
Command to execute once to perform any set-up needed before starting the
X servers, such as setting up the X server authentication. The default
command creates an authority file for each of the users in the list
\${HEADLESS_X_ORG_USERS} and generates server configuration files for all
detected graphics cards.
HEADLESS_X_ORG_SERVER_COMMAND
The default X server start-up command. It will be passed three parameters
- in order, the screen number to use, the path of the X.Org configuration
file to use and the path of the X server log file to create.
HEADLESS_X_ORG_SERVER_POST_COMMAND
Command to execute once the X servers have been successfully started. It
will be passed a single parameter which is a space-separated list of the
X server screen numbers. By default this stores the service configuration
information to VirtualBox extra data for each of the users in the list
from the variable HEADLESS_X_ORG_USERS: the list of displays is set to the
key "${EXTRA_DATA_KEY_DISPLAYS}" and the path of the authority file to
"${EXTRA_DATA_KEY_AUTH}".
EOF
}
# Default configuration.
HEADLESS_X_ORG_CONFIGURATION_FOLDER="${DEFAULT_CONFIGURATION_FOLDER}"
HEADLESS_X_ORG_LOG_FOLDER="/var/log/${SERVICE_NAME}"
HEADLESS_X_ORG_LOG_FILE="${SERVICE_NAME}.log"
HEADLESS_X_ORG_RUN_FOLDER="/var/run/${SERVICE_NAME}"
HEADLESS_X_ORG_WAIT_FOR_PREREQUISITES="udevadm settle" # Fails if no udevadm.
HEADLESS_X_ORG_USERS=""
HEADLESS_X_ORG_FIRST_DISPLAY=10
X_AUTH_FILE="${HEADLESS_X_ORG_RUN_FOLDER}/xauth"
default_pre_command()
{
# Create and duplicate the authority file.
echo > "${X_AUTH_FILE}"
key="$(dd if=/dev/urandom count=1 bs=16 2>/dev/null | od -An -x)"
xauth -f "${X_AUTH_FILE}" add :0 . "${key}"
for i in ${HEADLESS_X_ORG_USERS}; do
cp "${X_AUTH_FILE}" "${X_AUTH_FILE}.${i}"
chown "${i}" "${X_AUTH_FILE}.${i}"
done
# Create the xorg.conf files.
mkdir -p "${HEADLESS_X_ORG_CONFIGURATION_FOLDER}" || return 1
display="${HEADLESS_X_ORG_FIRST_DISPLAY}"
for i in /sys/bus/pci/devices/*; do
read class < "${i}/class"
case ${class} in *03????)
address="${i##*/}"
address="${address%%:*}${address#*:}"
address="PCI:${address%%.*}:${address#*.}"
cat > "${HEADLESS_X_ORG_CONFIGURATION_FOLDER}/xorg.conf.${display}" << EOF
Section "Screen"
Identifier "Screen${display}"
EndSection
Section "ServerLayout"
Identifier "Layout${display}"
Screen "Screen${display}"
Option "AllowMouseOpenFail" "true"
Option "AutoAddDevices" "false"
Option "AutoAddGPU" "false"
Option "AutoEnableDevices" "false"
Option "IsolateDevice" "${address}"
EndSection
EOF
display=`expr ${display} + 1`
esac
done
}
HEADLESS_X_ORG_SERVER_PRE_COMMAND="default_pre_command"
default_command()
{
auth="${HEADLESS_X_ORG_RUN_FOLDER}/xauth"
# screen=$1
# conf_file=$2
# log_file=$3
Xorg :"${1}" -auth "${auth}" -config "${2}" -logverbose 0 -logfile /dev/null -verbose 7 > "${3}" 2>&1
}
HEADLESS_X_ORG_SERVER_COMMAND="default_command"
default_post_command()
{
# screens=$1
for i in ${HEADLESS_X_ORG_USERS}; do
su ${i} -c "VBoxManage setextradata global ${EXTRA_DATA_KEY_DISPLAYS} \"${1}\""
su ${i} -c "VBoxManage setextradata global ${EXTRA_DATA_KEY_AUTH} \"${HEADLESS_X_ORG_RUN_FOLDER}/xauth\""
done
}
HEADLESS_X_ORG_SERVER_POST_COMMAND="default_post_command"
## The function definition at the start of every non-trivial shell script!
abort() {
## $@, ... Error text to output to standard error in printf format.
printf "$@" >&2
exit 1
}
## Milder version of abort, when we can't continue because of a valid condition.
abandon() {
## $@, ... Text to output to standard error in printf format.
printf "$@" >&2
exit 0
}
abort_usage() {
usage >&2
abort "$@"
}
# Print a banner message
banner() {
cat << EOF
${VBOX_PRODUCT} VBoxHeadless X Server start-up service Version ${VBOX_VERSION_STRING}
(C) 2005-${VBOX_C_YEAR} ${VBOX_VENDOR}
All rights reserved.
EOF
}
# Get the directory where the script is located.
SCRIPT_FOLDER=$(dirname "${SCRIPT_NAME}")"/"
[ -r "${SCRIPT_FOLDER}generated.sh" ] ||
abort "${LOG_FILE}" "Failed to find installation information.\n"
. "${SCRIPT_FOLDER}generated.sh"
# Parse our arguments.
do_install=""
while [ "$#" -gt 0 ]; do
case $1 in
-c|--conf-file)
[ "$#" -gt 1 ] ||
{
banner
abort "%s requires at least one argument.\n" "$1"
}
CONFIGURATION_FILE="$2"
shift
;;
--help|--usage)
banner
usage
exit 0
;;
*)
banner
abort_usage "Unknown argument $1.\n"
;;
esac
shift
done
[ -r "${CONFIGURATION_FILE}" ] && . "${CONFIGURATION_FILE}"
# Change to the root directory so we don't hold any other open.
cd /
# If something fails here we will catch it when we create the directory.
[ -e "${HEADLESS_X_ORG_LOG_FOLDER}" ] &&
[ -d "${HEADLESS_X_ORG_LOG_FOLDER}" ] &&
rm -rf "${HEADLESS_X_ORG_LOG_FOLDER}.old" 2> /dev/null &&
mv "${HEADLESS_X_ORG_LOG_FOLDER}" "${HEADLESS_X_ORG_LOG_FOLDER}.old" 2> /dev/null
mkdir -p "${HEADLESS_X_ORG_LOG_FOLDER}" 2>/dev/null ||
{
banner
abort "Failed to create log folder \"${HEADLESS_X_ORG_LOG_FOLDER}\".\n"
}
mkdir -p "${HEADLESS_X_ORG_RUN_FOLDER}" 2>/dev/null ||
{
banner
abort "Failed to create run folder \"${HEADLESS_X_ORG_RUN_FOLDER}\".\n"
}
exec > "${HEADLESS_X_ORG_LOG_FOLDER}/${HEADLESS_X_ORG_LOG_FILE}" 2>&1
banner
# Wait for our dependencies to become available.
if [ -n "${HEADLESS_X_ORG_WAIT_FOR_PREREQUISITES}" ]; then
"${HEADLESS_X_ORG_WAIT_FOR_PREREQUISITES}" ||
abort "Service prerequisites not available.\n"
fi
# Do any pre-start setup.
if [ -n "${HEADLESS_X_ORG_SERVER_PRE_COMMAND}" ]; then
"${HEADLESS_X_ORG_SERVER_PRE_COMMAND}" ||
abort "Pre-requisite failed.\n"
fi
X_SERVER_PIDS=""
X_SERVER_SCREENS=""
trap "kill \${X_SERVER_PIDS} 2>/dev/null" ${EXIT_SIGNALS}
space="" # Hack to put spaces between the pids but not before or after.
for conf_file in "${HEADLESS_X_ORG_CONFIGURATION_FOLDER}"/*; do
[ x"${conf_file}" = x"${HEADLESS_X_ORG_CONFIGURATION_FOLDER}/*" ] &&
! [ -e "${conf_file}" ] &&
abort "No configuration files found.\n"
filename="$(basename "${conf_file}")"
screen="$(expr "${filename}" : "xorg\.conf\.\(.*\)")"
[ 0 -le "${screen}" ] 2>/dev/null ||
abort "Badly formed file name \"${conf_file}\".\n"
log_file="${HEADLESS_X_ORG_LOG_FOLDER}/Xorg.${screen}.log"
"${HEADLESS_X_ORG_SERVER_COMMAND}" "${screen}" "${conf_file}" "${log_file}" &
X_SERVER_PIDS="${X_SERVER_PIDS}${space}$!"
X_SERVER_SCREENS="${X_SERVER_SCREENS}${space}${screen}"
space=" "
done
# Do any post-start work.
if [ -n "${HEADLESS_X_ORG_SERVER_POST_COMMAND}" ]; then
"${HEADLESS_X_ORG_SERVER_POST_COMMAND}" "${X_SERVER_SCREENS}" ||
abort "Post-command failed.\n"
fi
wait