Bug#617851: xvfb-run only works on Linux systems
Package: xvfb
Version: 1.9.4.901-1
It seems that there are differences between the Xvfb command for Linux
(Debian/RedHat), AIX and Solaris.
I have patched the script so that it works on AIX and Solaris, in
addition to Linux. I have tested the script on AIX 6.1 and Solaris Sparc
10.
The following changes were made:
It checks if the script is running in a Korn(-like) or Bash(-like)
shell. If it isn't, exit code 7 is returned. The reason is that on
Solaris, /bin/sh is linked to the standard Bourne shell.
On AIX, there is no Xvfb command. The standard X command is used
instead.
The default arguments used by the xvfb-run script when running Xvfb are
different between Linux, AIX and Solaris. The script uses the correct
arguments based on the operating system being used.
It checks if the GNU version of getopt is installed. If it isn't, then,
the standard version of getopt is used and long options are disabled.
It checks if the tempfile command is installed. If it is, it used,
otherwise, mktemp is used as it was in previous versions of the script.
I also ported a fix from RedHat's version of Xvfb-run in terms of using
the mktemp script. Details of the fix are here:
https://bugzilla.redhat.com/show_bug.cgi?id=508739 and here:
https://bugzilla.redhat.com/show_bug.cgi?id=632879.
If the mcookie utility is not installed, it is simulated based on code
found at the following URL:
http://git.debian.org/?p=pkg-xorg/app/xinit.git;a=blob_plain;f=configure
.ac;hb=HEAD. By default, I am using the /dev/urandom device. If security
is an issue, it can be changed to use the /dev/random device instead.
Here is the original version of the file:
Xvfb-run.orig
============
#!/bin/sh
# $Id: xvfb-run 2027 2004-11-16 14:54:16Z branden $
# This script starts an instance of Xvfb, the "fake" X server, runs a
command
# with that server available, and kills the X server when done. The
return
# value of the command becomes the return value of this script.
#
# If anyone is using this to build a Debian package, make sure the
package
# Build-Depends on xvfb, xbase-clients, and xfonts-base.
set -e
PROGNAME=xvfb-run
SERVERNUM=99
AUTHFILE=
ERRORFILE=/dev/null
STARTWAIT=3
XVFBARGS="-screen 0 640x480x8"
LISTENTCP="-nolisten tcp"
XAUTHPROTO=.
# Query the terminal to establish a default number of columns to use for
# displaying messages to the user. This is used only as a fallback in
the event
# the COLUMNS variable is not set. ($COLUMNS can react to SIGWINCH
while the
# script is running, and this cannot, only being calculated once.)
DEFCOLUMNS=$(stty size 2>/dev/null | awk '{print $2}') || true
if ! expr "$DEFCOLUMNS" : "[[:digit:]]\+$" >/dev/null 2>&1; then
DEFCOLUMNS=80
fi
# Display a message, wrapping lines at the terminal width.
message () {
echo "$PROGNAME: $*" | fmt -t -w ${COLUMNS:-$DEFCOLUMNS}
}
# Display an error message.
error () {
message "error: $*" >&2
}
# Display a usage message.
usage () {
if [ -n "$*" ]; then
message "usage error: $*"
fi
cat <<EOF
Usage: $PROGNAME [OPTION ...] COMMAND
Run COMMAND (usually an X client) in a virtual X server environment.
Options:
-a --auto-servernum try to get a free server number,
starting at
--server-num
-e FILE --error-file=FILE file used to store xauth errors and
Xvfb
output (default: $ERRORFILE)
-f FILE --auth-file=FILE file used to store auth cookie
(default: ./.Xauthority)
-h --help display this usage message and exit
-n NUM --server-num=NUM server number to use (default:
$SERVERNUM)
-l --listen-tcp enable TCP port listening in the X
server
-p PROTO --xauth-protocol=PROTO X authority protocol name to use
(default: xauth command's default)
-s ARGS --server-args=ARGS arguments (other than server number
and
"-nolisten tcp") to pass to the Xvfb
server
(default: "$XVFBARGS")
-w DELAY --wait=DELAY delay in seconds to wait for Xvfb to
start
before running COMMAND (default:
$STARTWAIT)
EOF
}
# Find a free server number by looking at .X*-lock files in /tmp.
find_free_servernum() {
# Sadly, the "local" keyword is not POSIX. Leave the next line
commented in
# the hope Debian Policy eventually changes to allow it in /bin/sh
scripts
# anyway.
#local i
i=$SERVERNUM
while [ -f /tmp/.X$i-lock ]; do
i=$(($i + 1))
done
echo $i
}
# Clean up files
clean_up() {
if [ -e "$AUTHFILE" ]; then
XAUTHORITY=$AUTHFILE xauth remove ":$SERVERNUM" >>"$ERRORFILE"
2>&1
fi
if [ -n "$XVFB_RUN_TMPDIR" ]; then
if ! rm -r "$XVFB_RUN_TMPDIR"; then
error "problem while cleaning up temporary directory"
exit 5
fi
fi
}
# Parse the command line.
ARGS=$(getopt --options +ae:f:hn:lp:s:w: \
--long
auto-servernum,error-file:,auth-file:,help,server-num:,listen-tcp,xauth-
protocol:,server-args:,wait: \
--name "$PROGNAME" -- "$@")
GETOPT_STATUS=$?
if [ $GETOPT_STATUS -ne 0 ]; then
error "internal error; getopt exited with status $GETOPT_STATUS"
exit 6
fi
eval set -- "$ARGS"
while :; do
case "$1" in
-a|--auto-servernum) SERVERNUM=$(find_free_servernum);
AUTONUM="yes" ;;
-e|--error-file) ERRORFILE="$2"; shift ;;
-f|--auth-file) AUTHFILE="$2"; shift ;;
-h|--help) SHOWHELP="yes" ;;
-n|--server-num) SERVERNUM="$2"; shift ;;
-l|--listen-tcp) LISTENTCP="" ;;
-p|--xauth-protocol) XAUTHPROTO="$2"; shift ;;
-s|--server-args) XVFBARGS="$2"; shift ;;
-w|--wait) STARTWAIT="$2"; shift ;;
--) shift; break ;;
*) error "internal error; getopt permitted \"$1\" unexpectedly"
exit 6
;;
esac
shift
done
if [ "$SHOWHELP" ]; then
usage
exit 0
fi
if [ -z "$*" ]; then
usage "need a command to run" >&2
exit 2
fi
if ! which xauth >/dev/null; then
error "xauth command not found"
exit 3
fi
# tidy up after ourselves
trap clean_up EXIT
# If the user did not specify an X authorization file to use, set up a
temporary
# directory to house one.
if [ -z "$AUTHFILE" ]; then
XVFB_RUN_TMPDIR="$(mktemp -d -t $PROGNAME.XXXXXX)"
# Create empty file to avoid xauth warning
AUTHFILE=$(tempfile -n "$XVFB_RUN_TMPDIR/Xauthority")
fi
# Start Xvfb.
MCOOKIE=$(mcookie)
tries=10
while [ $tries -gt 0 ]; do
tries=$(( $tries - 1 ))
XAUTHORITY=$AUTHFILE xauth source - << EOF >>"$ERRORFILE" 2>&1
add :$SERVERNUM $XAUTHPROTO $MCOOKIE
EOF
XAUTHORITY=$AUTHFILE Xvfb ":$SERVERNUM" $XVFBARGS $LISTENTCP
>>"$ERRORFILE" 2>&1 &
XVFBPID=$!
sleep "$STARTWAIT"
if kill -0 $XVFBPID 2>/dev/null; then
break
elif [ -n "$AUTONUM" ]; then
# The display is in use so try another one (if '-a' was
specified).
SERVERNUM=$((SERVERNUM + 1))
SERVERNUM=$(find_free_servernum)
continue
fi
error "Xvfb failed to start" >&2
exit 1
done
# Start the command and save its exit status.
set +e
DISPLAY=:$SERVERNUM XAUTHORITY=$AUTHFILE "$@" 2>&1
RETVAL=$?
set -e
# Kill Xvfb now that the command has exited.
kill $XVFBPID
# Return the executed command's exit status.
exit $RETVAL
# vim:set ai et sts=4 sw=4 tw=80:
Here is the modified version of the file:
Xvfb-run
========
#!/bin/sh
# $Id: xvfb-run 2027 2004-11-16 14:54:16Z branden $
# This script starts an instance of Xvfb, the "fake" X server, runs a
command
# with that server available, and kills the X server when done. The
return
# value of the command becomes the return value of this script.
#
# If anyone is using this to build a Debian package, make sure the
package
# Build-Depends on xvfb, xbase-clients, and xfonts-base.
# If /bin/sh is not a Korn(-like) or BASH shell, adjust the first line
in
# the script to point to a Korn(-like) or BASH shell.
# On some platforms, you will need to install mktemp
# in order to get the script to work.
#
# mktemp can be downloaded from http://www.mktemp.org.
# There is also version of mktemp that is part of GNU CoreUtils.
PROGNAME=xvfb-run
#
# Make sure we are running in a Korn(-like) shell.
#
set +e
HOME_DIR=`cd ~ 2>/dev/null`
if test $? -ne 0
then
echo "$PROGNAME: This script must be run using a Korn or Bash shell."
exit 7
fi
set -e
SERVERNUM=99
XVFB=Xvfb
XVFBOPTIONS=
AUTHFILE=
ERRORFILE=/dev/null
STARTWAIT=3
XVFBARGS="-screen 0 640x480x8"
LISTENTCP="-nolisten tcp"
XAUTHPROTO=.
OS_TYPE=$(uname -s)
# On AIX/SunOS, adjust arguments that work differently or are not
supported.
case $OS_TYPE in
AIX) XVFB="/usr/bin/X11/X"
XVFBOPTIONS="-vfb -force -x abx -x dbe -x GLX"
XVFBARGS="-I \"${XVFBARGS}\""
LISTENTCP=
;;
SunOS) XVFBARGS="screen 0 640x480x8"
LISTENTCP=
;;
esac
# Query the terminal to establish a default number of columns to use for
# displaying messages to the user. This is used only as a fallback in
the event
# the COLUMNS variable is not set. ($COLUMNS can react to SIGWINCH
while the
# script is running, and this cannot, only being calculated once.)
DEFCOLUMNS=$(stty size 2>/dev/null | awk '{print $2}') || true
if ! expr "$DEFCOLUMNS" : "[[:digit:]]\+$" >/dev/null 2>&1; then
DEFCOLUMNS=80
fi
# Display a message, wrapping lines at the terminal width.
message () {
echo "$PROGNAME: $*" | fmt -t -w ${COLUMNS:-$DEFCOLUMNS}
}
# Display an error message.
error () {
message "error: $*" >&2
}
# Display a usage message.
usage () {
if [ -n "$*" ]; then
message "usage error: $*"
fi
cat <<EOF
Usage: $PROGNAME [OPTION ...] COMMAND
Run COMMAND (usually an X client) in a virtual X server environment.
Options:
-a --auto-servernum try to get a free server number,
starting at
--server-num
-e FILE --error-file=FILE file used to store xauth errors and
Xvfb
output (default: $ERRORFILE)
-f FILE --auth-file=FILE file used to store auth cookie
(default: ./.Xauthority)
-h --help display this usage message and exit
-n NUM --server-num=NUM server number to use (default:
$SERVERNUM)
-l --listen-tcp enable TCP port listening in the X
server
-p PROTO --xauth-protocol=PROTO X authority protocol name to use
(default: xauth command's default)
-s ARGS --server-args=ARGS arguments (other than server number
and
"-nolisten tcp") to pass to the Xvfb
server
(default: "$XVFBARGS")
-w DELAY --wait=DELAY delay in seconds to wait for Xvfb to
start
before running COMMAND (default:
$STARTWAIT)
Note: Long options (e.g. --help) may not be available.
EOF
}
# Find a free server number by looking at .X*-lock files in /tmp.
find_free_servernum() {
# Sadly, the "local" keyword is not POSIX. Leave the next line
commented in
# the hope Debian Policy eventually changes to allow it in /bin/sh
scripts
# anyway.
#local i
i=$SERVERNUM
while [ -f /tmp/.X$i-lock ]; do
i=$(($i + 1))
done
echo $i
}
# Clean up files
clean_up() {
if [ -e "$AUTHFILE" ]; then
XAUTHORITY=$AUTHFILE xauth remove ":$SERVERNUM" >>"$ERRORFILE"
2>&1
fi
if [ -n "$XVFB_RUN_TMPDIR" ]; then
if ! rm -r "$XVFB_RUN_TMPDIR"; then
error "problem while cleaning up temporary directory"
exit 5
fi
fi
}
# Parse the command line.
# Check if we are using the GNU version of GETOPT. If we
# are, then use GNU specific syntax. Otherwise, use
# standard getopt syntax.
set +e
GNU_GETOPT_INSTALLED=$(getopt --help 2>/dev/null)
set -e
if [ "$GNU_GETOPT_INSTALLED" != "-- " ]; then
GNU_GETOPT_INSTALLED=yes
else
GNU_GETOPT_INSTALLED=no
fi
if [ "$GNU_GETOPT_INSTALLED" = "yes" ]; then
ARGS=$(getopt --options +ae:f:hn:lp:s:w: \
--long
auto-servernum,error-file:,auth-file:,help,server-num:,listen-tcp,xauth-
protocol:,server-args:,wait: \
--name "$PROGNAME" -- "$@")
GETOPT_STATUS=$?
else
ARGS=$(getopt ae:f:hn:lp:s:w: "$@")
GETOPT_STATUS=$?
fi
if [ $GETOPT_STATUS -ne 0 ]; then
error "internal error; getopt exited with status $GETOPT_STATUS"
exit 6
fi
eval set -- "$ARGS"
while :; do
case "$1" in
-a|--auto-servernum) SERVERNUM=$(find_free_servernum);
AUTONUM="yes" ;;
-e|--error-file) ERRORFILE="$2"; shift ;;
-f|--auth-file) AUTHFILE="$2"; shift ;;
-h|--help) SHOWHELP="yes" ;;
-n|--server-num) SERVERNUM="$2"; shift ;;
-l|--listen-tcp) LISTENTCP="" ;;
-p|--xauth-protocol) XAUTHPROTO="$2"; shift ;;
-s|--server-args) XVFBARGS="$2"; shift ;;
-w|--wait) STARTWAIT="$2"; shift ;;
--) shift; break ;;
*) error "internal error; getopt permitted \"$1\" unexpectedly"
exit 6
;;
esac
shift
done
if [ "$SHOWHELP" ]; then
usage
exit 0
fi
if [ -z "$*" ]; then
usage "need a command to run" >&2
exit 2
fi
if ! which xauth >/dev/null; then
error "xauth command not found"
exit 3
fi
# tidy up after ourselves
trap clean_up EXIT
# If the user did not specify an X authorization file to use, set up a
temporary
# directory to house one.
if [ -z "$AUTHFILE" ]; then
XVFB_RUN_TMPDIR="$(mktemp -d -t $PROGNAME.XXXXXX)"
set +e
TEMPFILE_INSTALLED=$(tempfile --help 2>/dev/null)
if [ $? -eq 0 ]; then
TEMPFILE_INSTALLED=yes
else
TEMPFILE_INSTALLED=no
fi
set -e
# Create empty file to avoid xauth warning
if [ "$TEMPFILE_INSTALLED" = "yes" ]; then
AUTHFILE=$(tempfile -n "$XVFB_RUN_TMPDIR/Xauthority")
else
AUTHFILE=$(mktemp -p "$XVFB_RUN_TMPDIR" Xauthority.XXXXXX)
fi
fi
# Get the cookie to use.
set +e
MCOOKIE=$(mcookie 2>/dev/null)
# If the mcookie utility is not installed, simulate it.
if [ $? -ne 0 ]; then
#
# Set the random device to /dev/random if you need very secure
# random numbers. Otherwise, /dev/urandom will be fine.
#
RANDOM_DEVICE=/dev/urandom
MCOOKIE=$(od -X -A n -N 16 $RANDOM_DEVICE | tr -d '\011\040')
fi
set -e
# Start Xvfb.
tries=10
while [ $tries -gt 0 ]; do
tries=$(( $tries - 1 ))
XAUTHORITY=$AUTHFILE xauth source - << EOF >>"$ERRORFILE" 2>&1
add :$SERVERNUM $XAUTHPROTO $MCOOKIE
EOF
XAUTHORITY=$AUTHFILE $XVFB $XVFBOPTIONS ":$SERVERNUM" \
$XVFBARGS $LISTENTCP >>"$ERRORFILE" 2>&1 &
XVFBPID=$!
sleep "$STARTWAIT"
if kill -0 $XVFBPID 2>/dev/null; then
break
elif [ -n "$AUTONUM" ]; then
# The display is in use so try another one (if '-a' was
specified).
SERVERNUM=$((SERVERNUM + 1))
SERVERNUM=$(find_free_servernum)
continue
fi
error "Xvfb failed to start" >&2
exit 1
done
# Start the command and save its exit status.
set +e
DISPLAY=:$SERVERNUM XAUTHORITY=$AUTHFILE "$@" 2>&1
RETVAL=$?
set -e
# Kill Xvfb now that the command has exited.
kill $XVFBPID
# Return the executed command's exit status.
exit $RETVAL
# vim:set ai et sts=4 sw=4 tw=80:
Here is a context difference of the changes I made:
*** xvfb-run.orig Mon Jan 31 15:09:04 2011
--- xvfb-run Tue Feb 08 14:49:22 2011
***************
*** 9,18 ****
# If anyone is using this to build a Debian package, make sure the
package
# Build-Depends on xvfb, xbase-clients, and xfonts-base.
! set -e
PROGNAME=xvfb-run
SERVERNUM=99
AUTHFILE=
ERRORFILE=/dev/null
STARTWAIT=3
--- 9,44 ----
# If anyone is using this to build a Debian package, make sure the
package
# Build-Depends on xvfb, xbase-clients, and xfonts-base.
! # If /bin/sh is not a Korn(-like) or BASH shell, adjust the first line
in
! # the script to point to a Korn(-like) or BASH shell.
!
! # On some platforms, you will need to install mktemp
! # in order to get the script to work.
! #
! # mktemp can be downloaded from http://www.mktemp.org.
! # There is also version of mktemp that is part of GNU CoreUtils.
PROGNAME=xvfb-run
+
+ #
+ # Make sure we are running in a Korn(-like) shell.
+ #
+
+ set +e
+
+ HOME_DIR=`cd ~ 2>/dev/null`
+
+ if test $? -ne 0
+ then
+ echo "$PROGNAME: This script must be run using a Korn or Bash
shell."
+ exit 7
+ fi
+
+ set -e
+
SERVERNUM=99
+ XVFB=Xvfb
+ XVFBOPTIONS=
AUTHFILE=
ERRORFILE=/dev/null
STARTWAIT=3
***************
*** 20,25 ****
--- 46,65 ----
LISTENTCP="-nolisten tcp"
XAUTHPROTO=.
+ OS_TYPE=$(uname -s)
+
+ # On AIX/SunOS, adjust arguments that work differently or are not
supported.
+ case $OS_TYPE in
+ AIX) XVFB="/usr/bin/X11/X"
+ XVFBOPTIONS="-vfb -force -x abx -x dbe -x GLX"
+ XVFBARGS="-I \"${XVFBARGS}\""
+ LISTENTCP=
+ ;;
+ SunOS) XVFBARGS="screen 0 640x480x8"
+ LISTENTCP=
+ ;;
+ esac
+
# Query the terminal to establish a default number of columns to use
for
# displaying messages to the user. This is used only as a fallback in
the event
# the COLUMNS variable is not set. ($COLUMNS can react to SIGWINCH
while the
***************
*** 64,69 ****
--- 104,111 ----
(default: "$XVFBARGS")
-w DELAY --wait=DELAY delay in seconds to wait for Xvfb
to start
before running COMMAND (default:
$STARTWAIT)
+
+ Note: Long options (e.g. --help) may not be available.
EOF
}
***************
*** 95,104 ****
}
# Parse the command line.
! ARGS=$(getopt --options +ae:f:hn:lp:s:w: \
! --long
auto-servernum,error-file:,auth-file:,help,server-num:,listen-tcp,xauth-
protocol:,server-args:,wait: \
! --name "$PROGNAME" -- "$@")
! GETOPT_STATUS=$?
if [ $GETOPT_STATUS -ne 0 ]; then
error "internal error; getopt exited with status $GETOPT_STATUS"
--- 137,166 ----
}
# Parse the command line.
!
! # Check if we are using the GNU version of GETOPT. If we
! # are, then use GNU specific syntax. Otherwise, use
! # standard getopt syntax.
!
! set +e
! GNU_GETOPT_INSTALLED=$(getopt --help 2>/dev/null)
! set -e
!
! if [ "$GNU_GETOPT_INSTALLED" != "-- " ]; then
! GNU_GETOPT_INSTALLED=yes
! else
! GNU_GETOPT_INSTALLED=no
! fi
!
! if [ "$GNU_GETOPT_INSTALLED" = "yes" ]; then
! ARGS=$(getopt --options +ae:f:hn:lp:s:w: \
! --long
auto-servernum,error-file:,auth-file:,help,server-num:,listen-tcp,xauth-
protocol:,server-args:,wait: \
! --name "$PROGNAME" -- "$@")
! GETOPT_STATUS=$?
! else
! ARGS=$(getopt ae:f:hn:lp:s:w: "$@")
! GETOPT_STATUS=$?
! fi
if [ $GETOPT_STATUS -ne 0 ]; then
error "internal error; getopt exited with status $GETOPT_STATUS"
***************
*** 148,166 ****
# directory to house one.
if [ -z "$AUTHFILE" ]; then
XVFB_RUN_TMPDIR="$(mktemp -d -t $PROGNAME.XXXXXX)"
# Create empty file to avoid xauth warning
! AUTHFILE=$(tempfile -n "$XVFB_RUN_TMPDIR/Xauthority")
fi
# Start Xvfb.
- MCOOKIE=$(mcookie)
tries=10
while [ $tries -gt 0 ]; do
tries=$(( $tries - 1 ))
XAUTHORITY=$AUTHFILE xauth source - << EOF >>"$ERRORFILE" 2>&1
add :$SERVERNUM $XAUTHPROTO $MCOOKIE
EOF
! XAUTHORITY=$AUTHFILE Xvfb ":$SERVERNUM" $XVFBARGS $LISTENTCP
>>"$ERRORFILE" 2>&1 &
XVFBPID=$!
sleep "$STARTWAIT"
--- 210,262 ----
# directory to house one.
if [ -z "$AUTHFILE" ]; then
XVFB_RUN_TMPDIR="$(mktemp -d -t $PROGNAME.XXXXXX)"
+
+ set +e
+ TEMPFILE_INSTALLED=$(tempfile --help 2>/dev/null)
+
+ if [ $? -eq 0 ]; then
+ TEMPFILE_INSTALLED=yes
+ else
+ TEMPFILE_INSTALLED=no
+ fi
+ set -e
+
# Create empty file to avoid xauth warning
!
! if [ "$TEMPFILE_INSTALLED" = "yes" ]; then
! AUTHFILE=$(tempfile -n "$XVFB_RUN_TMPDIR/Xauthority")
! else
! AUTHFILE=$(mktemp -p "$XVFB_RUN_TMPDIR" Xauthority.XXXXXX)
! fi
fi
+ # Get the cookie to use.
+ set +e
+ MCOOKIE=$(mcookie 2>/dev/null)
+
+ # If the mcookie utility is not installed, simulate it.
+
+ if [ $? -ne 0 ]; then
+ #
+ # Set the random device to /dev/random if you need very secure
+ # random numbers. Otherwise, /dev/urandom will be fine.
+ #
+
+ RANDOM_DEVICE=/dev/urandom
+
+ MCOOKIE=$(od -X -A n -N 16 $RANDOM_DEVICE | tr -d '\011\040')
+ fi
+ set -e
+
# Start Xvfb.
tries=10
while [ $tries -gt 0 ]; do
tries=$(( $tries - 1 ))
XAUTHORITY=$AUTHFILE xauth source - << EOF >>"$ERRORFILE" 2>&1
add :$SERVERNUM $XAUTHPROTO $MCOOKIE
EOF
! XAUTHORITY=$AUTHFILE $XVFB $XVFBOPTIONS ":$SERVERNUM" \
! $XVFBARGS $LISTENTCP >>"$ERRORFILE" 2>&1 &
XVFBPID=$!
sleep "$STARTWAIT"
I know that the Xvfb utility also runs on HP-UX (and possibly other)
systems. If you know of a way to get a shell account on an HP-UX system,
let me know and I would be willing to try and get it running on HP-UX.
If you have any questions, need more details, do not hesitate to ask.
Thanks,
Joel
Reply to: