#!/bin/sh # xvfb-run-eepp: xvfb-run variant with race-safe automatic display selection. # Defaults tuned for eepp GUI tests: # --auto-servernum enabled by default # --server-args "-screen 0 1280x1024x24" enabled by default # ASAN_OPTIONS includes detect_leaks=0 for the child process set -e PROGNAME=${0##*/} SERVERNUM=99 MAX_SERVERNUM=599 AUTO_SERVERNUM=yes AUTHFILE= ERRORFILE=/dev/null STARTWAIT=3 XVFBARGS="-screen 0 1280x1024x24" LISTENTCP="-nolisten tcp" XAUTHPROTO=. LOCKDIR= XVFB_RUN_TMPDIR= XVFBPID= DEFCOLUMNS=$(stty size 2>/dev/null | awk '{print $2}') || true case "$DEFCOLUMNS" in *[!0-9]*|'') DEFCOLUMNS=80 ;; esac message() { echo "$PROGNAME: $*" | fmt -t -w "${COLUMNS:-$DEFCOLUMNS}" } error() { message "error: $*" >&2 } usage() { [ -n "$*" ] && message "usage error: $*" cat </dev/null 2>&1 || true wait "$XVFBPID" >/dev/null 2>&1 || true fi if [ -n "$AUTHFILE" ]; then XAUTHORITY=$AUTHFILE xauth remove ":$SERVERNUM" >"$ERRORFILE" 2>&1 || true fi [ -n "$XVFB_RUN_TMPDIR" ] && rm -rf "$XVFB_RUN_TMPDIR" || true [ -n "$LOCKDIR" ] && rmdir "$LOCKDIR" >/dev/null 2>&1 || true exit "$status" } is_display_busy() { n=$1 # Xorg/Xvfb convention: lock file plus socket. Check both because stale # lock files and still-existing sockets can happen independently. [ -e "/tmp/.X$n-lock" ] && return 0 [ -e "/tmp/.X11-unix/X$n" ] && return 0 return 1 } claim_display() { n=$1 lock="${TMPDIR:-/tmp}/$PROGNAME-display-$n.lock" # mkdir is atomic on local filesystems and avoids the race in classic # xvfb-run -a, where many jobs can pick the same currently-free display. if mkdir "$lock" 2>/dev/null; then LOCKDIR=$lock SERVERNUM=$n # Helpful for manual cleanup/debugging. Not used as synchronization. echo "$$" > "$LOCKDIR/pid" 2>/dev/null || true return 0 fi # Older versions of this script claimed displays from a command substitution, # which left lock directories behind because LOCKDIR was set in a subshell. # Recover those stale claims, but never touch a lock owned by a live process. if [ -f "$lock/pid" ]; then oldpid=$(cat "$lock/pid" 2>/dev/null || true) case "$oldpid" in *[!0-9]*|'') oldpid= ;; esac if [ -z "$oldpid" ] || ! kill -0 "$oldpid" >/dev/null 2>&1; then rm -f "$lock/pid" 2>/dev/null || true rmdir "$lock" 2>/dev/null || true if mkdir "$lock" 2>/dev/null; then LOCKDIR=$lock SERVERNUM=$n echo "$$" > "$LOCKDIR/pid" 2>/dev/null || true return 0 fi fi fi return 1 } find_and_claim_display() { i=$SERVERNUM while [ "$i" -le "$MAX_SERVERNUM" ]; do if ! is_display_busy "$i" && claim_display "$i"; then return 0 fi i=$((i + 1)) done return 1 } wait_for_xvfb() { if command -v xdpyinfo >/dev/null 2>&1; then i=0 # Poll for up to about 10 seconds. Avoid requiring sleep fractions. while [ "$i" -lt 100 ]; do DISPLAY=:$SERVERNUM XAUTHORITY=$AUTHFILE xdpyinfo >/dev/null 2>&1 && return 0 kill -0 "$XVFBPID" >/dev/null 2>&1 || return 1 sleep 0.1 2>/dev/null || sleep 1 i=$((i + 1)) done return 1 fi sleep "$STARTWAIT" kill -0 "$XVFBPID" >/dev/null 2>&1 } ARGS=$(getopt --options +ae:f:hn:lp:s:w: \ --long auto-servernum,no-auto-servernum,error-file:,auth-file:,help,server-num:,max-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" SHOWHELP= while :; do case "$1" in -a|--auto-servernum) AUTO_SERVERNUM=yes ;; --no-auto-servernum) AUTO_SERVERNUM=no ;; -e|--error-file) ERRORFILE=$2; shift ;; -f|--auth-file) AUTHFILE=$2; shift ;; -h|--help) SHOWHELP=yes ;; -n|--server-num) SERVERNUM=$2; shift ;; --max-server-num) MAX_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 [ "$#" -eq 0 ]; then usage "need a command to run" >&2 exit 2 fi case "$SERVERNUM:$MAX_SERVERNUM" in *[!0-9:]*|:*|*:) error "server numbers must be numeric" exit 2 ;; esac if ! command -v xauth >/dev/null 2>&1; then error "xauth command not found" exit 3 fi if ! command -v Xvfb >/dev/null 2>&1; then error "Xvfb command not found" exit 3 fi if [ "$AUTO_SERVERNUM" = yes ]; then START_SERVERNUM=$SERVERNUM if ! find_and_claim_display; then error "could not find a free X display between $START_SERVERNUM and $MAX_SERVERNUM" exit 4 fi else if is_display_busy "$SERVERNUM"; then error "display :$SERVERNUM is already busy" exit 4 fi if ! claim_display "$SERVERNUM"; then error "display :$SERVERNUM is already claimed by another $PROGNAME process" exit 4 fi fi trap cleanup EXIT INT TERM HUP if [ -z "$AUTHFILE" ]; then XVFB_RUN_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/$PROGNAME.XXXXXXXX") || { error "could not create temporary directory" exit 4 } AUTHFILE=$(mktemp "$XVFB_RUN_TMPDIR/Xauthority.XXXXXXXX") || { error "could not create Xauthority file" exit 4 } fi MCOOKIE=$(mcookie) XAUTHORITY=$AUTHFILE xauth add ":$SERVERNUM" "$XAUTHPROTO" "$MCOOKIE" >"$ERRORFILE" 2>&1 # shellcheck disable=SC2086 # XVFBARGS and LISTENTCP intentionally split like xvfb-run. Xvfb ":$SERVERNUM" $XVFBARGS $LISTENTCP -auth "$AUTHFILE" >"$ERRORFILE" 2>&1 & XVFBPID=$! if ! wait_for_xvfb; then error "Xvfb failed to start on display :$SERVERNUM; see $ERRORFILE" exit 1 fi set +e ASAN_OPTIONS="${ASAN_OPTIONS:+$ASAN_OPTIONS:}detect_leaks=0" \ DISPLAY=:$SERVERNUM \ XAUTHORITY=$AUTHFILE \ "$@" RETVAL=$? set -e exit "$RETVAL"