1#!/bin/sh 2 3# This script starts an instance of Xvfb, the "fake" X server, runs a command 4# with that server available, and kills the X server when done. The return 5# value of the command becomes the return value of this script. 6# 7# If anyone is using this to build a Debian package, make sure the package 8# Build-Depends on xvfb and xauth. 9 10set -e 11 12PROGNAME=xvfb-run 13SERVERNUM=99 14AUTHFILE= 15ERRORFILE=/dev/null 16XVFBARGS="-screen 0 1280x1024x24" 17LISTENTCP="-nolisten tcp" 18XAUTHPROTO=. 19 20# Query the terminal to establish a default number of columns to use for 21# displaying messages to the user. This is used only as a fallback in the event 22# the COLUMNS variable is not set. ($COLUMNS can react to SIGWINCH while the 23# script is running, and this cannot, only being calculated once.) 24DEFCOLUMNS=$(stty size 2>/dev/null | awk '{print $2}') || true 25case "$DEFCOLUMNS" in 26 *[!0-9]*|'') DEFCOLUMNS=80 ;; 27esac 28 29# Display a message, wrapping lines at the terminal width. 30message () { 31 echo "$PROGNAME: $*" | fmt -t -w ${COLUMNS:-$DEFCOLUMNS} 32} 33 34# Display an error message. 35error () { 36 message "error: $*" >&2 37} 38 39# Display a usage message. 40usage () { 41 if [ -n "$*" ]; then 42 message "usage error: $*" 43 fi 44 cat <<EOF 45Usage: $PROGNAME [OPTION ...] COMMAND 46Run COMMAND (usually an X client) in a virtual X server environment. 47Options: 48-a --auto-servernum try to get a free server number, starting at 49 --server-num 50-e FILE --error-file=FILE file used to store xauth errors and Xvfb 51 output (default: $ERRORFILE) 52-f FILE --auth-file=FILE file used to store auth cookie 53 (default: ./.Xauthority) 54-h --help display this usage message and exit 55-n NUM --server-num=NUM server number to use (default: $SERVERNUM) 56-l --listen-tcp enable TCP port listening in the X server 57-p PROTO --xauth-protocol=PROTO X authority protocol name to use 58 (default: xauth command's default) 59-s ARGS --server-args=ARGS arguments (other than server number and 60 "-nolisten tcp") to pass to the Xvfb server 61 (default: "$XVFBARGS") 62EOF 63} 64 65# Find a free server number by looking at .X*-lock files in /tmp. 66find_free_servernum() { 67 # Sadly, the "local" keyword is not POSIX. Leave the next line commented in 68 # the hope Debian Policy eventually changes to allow it in /bin/sh scripts 69 # anyway. 70 #local i 71 72 i=$SERVERNUM 73 while [ -f /tmp/.X$i-lock ]; do 74 i=$(($i + 1)) 75 done 76 echo $i 77} 78 79# Clean up files 80clean_up() { 81 if [ -e "$AUTHFILE" ]; then 82 XAUTHORITY=$AUTHFILE xauth remove ":$SERVERNUM" >>"$ERRORFILE" 2>&1 83 fi 84 if [ -n "$XVFB_RUN_TMPDIR" ]; then 85 if ! rm -r "$XVFB_RUN_TMPDIR"; then 86 error "problem while cleaning up temporary directory" 87 exit 5 88 fi 89 fi 90 if [ -n "$XVFBPID" ]; then 91 kill "$XVFBPID" >>"$ERRORFILE" 2>&1 92 fi 93} 94 95# Parse the command line. 96ARGS=$(getopt --options +ae:f:hn:lp:s:w: \ 97 --long auto-servernum,error-file:,auth-file:,help,server-num:,listen-tcp,xauth-protocol:,server-args:,wait: \ 98 --name "$PROGNAME" -- "$@") 99GETOPT_STATUS=$? 100 101if [ $GETOPT_STATUS -ne 0 ]; then 102 error "internal error; getopt exited with status $GETOPT_STATUS" 103 exit 6 104fi 105 106eval set -- "$ARGS" 107 108while :; do 109 case "$1" in 110 -a|--auto-servernum) SERVERNUM=$(find_free_servernum); AUTONUM="yes" ;; 111 -e|--error-file) ERRORFILE="$2"; shift ;; 112 -f|--auth-file) AUTHFILE="$2"; shift ;; 113 -h|--help) SHOWHELP="yes" ;; 114 -n|--server-num) SERVERNUM="$2"; shift ;; 115 -l|--listen-tcp) LISTENTCP="" ;; 116 -p|--xauth-protocol) XAUTHPROTO="$2"; shift ;; 117 -s|--server-args) XVFBARGS="$2"; shift ;; 118 -w|--wait) shift ;; 119 --) shift; break ;; 120 *) error "internal error; getopt permitted \"$1\" unexpectedly" 121 exit 6 122 ;; 123 esac 124 shift 125done 126 127if [ "$SHOWHELP" ]; then 128 usage 129 exit 0 130fi 131 132if [ -z "$*" ]; then 133 usage "need a command to run" >&2 134 exit 2 135fi 136 137if ! command -v xauth >/dev/null; then 138 error "xauth command not found" 139 exit 3 140fi 141 142# tidy up after ourselves 143trap clean_up EXIT 144 145# If the user did not specify an X authorization file to use, set up a temporary 146# directory to house one. 147if [ -z "$AUTHFILE" ]; then 148 XVFB_RUN_TMPDIR="$(mktemp -d -t $PROGNAME.XXXXXX)" 149 AUTHFILE="$XVFB_RUN_TMPDIR/Xauthority" 150 # Create empty file to avoid xauth warning 151 touch "$AUTHFILE" 152fi 153 154# Start Xvfb. 155MCOOKIE=$(mcookie) 156tries=10 157while [ $tries -gt 0 ]; do 158 tries=$(( $tries - 1 )) 159 XAUTHORITY=$AUTHFILE xauth source - << EOF >>"$ERRORFILE" 2>&1 160add :$SERVERNUM $XAUTHPROTO $MCOOKIE 161EOF 162 # handle SIGUSR1 so Xvfb knows to send a signal when it's ready to accept 163 # connections 164 trap : USR1 165 (trap '' USR1; exec Xvfb ":$SERVERNUM" $XVFBARGS $LISTENTCP -auth $AUTHFILE >>"$ERRORFILE" 2>&1) & 166 XVFBPID=$! 167 168 wait || : 169 if kill -0 $XVFBPID 2>/dev/null; then 170 break 171 elif [ -n "$AUTONUM" ]; then 172 # The display is in use so try another one (if '-a' was specified). 173 SERVERNUM=$((SERVERNUM + 1)) 174 SERVERNUM=$(find_free_servernum) 175 continue 176 fi 177 error "Xvfb failed to start" >&2 178 XVFBPID= 179 exit 1 180done 181 182# Start the command and save its exit status. 183set +e 184DISPLAY=:$SERVERNUM XAUTHORITY=$AUTHFILE "$@" 185RETVAL=$? 186set -e 187 188# Return the executed command's exit status. 189exit $RETVAL 190 191# vim:set ai et sts=4 sw=4 tw=80: 192