Running Xvfb on a RHEL Shared Host (without X)

This is how I compiled the Xorg Server for RHEL on a CentOS machine with modifications to create a portable Xvfb binary.

Xvfb minimal files

Xvfb (X virtual framebuffer) is an in-memory display server for Linux and Unix-like OSes. It enables running graphical applications without a display such as running a headless browser (e.g. A full-blown Firefox instance without a display nor input devices). Out of the box it needs elevated access, or rather, it needs access to certain paths and auxiliary binaries that only an elevated user can control (i.e. /usr/bin/xkbcomp or /usr/share/X11/xkb). On a shared host X is most likely not installed, so that compounds the problem. Here is how I got it working on a shared host running 64-bit RHEL (CentOS).

Copying over Xvfb doesn’t work because some paths are hard-coded.

Initially I copied over the Xvfb binary and shared libraries like this to the shared host. This was sufficient to run ./Xvfb itself, except Xvfb wanted to compile a keymap file to /tmp/server-99.xkm using binary /usr/bin/xkbcomp. Suppose you were to blissfully get your hosting provider to upload xkbcomp and its shared libraries to that path, the next problem is that the needed keymap files are in the non-existent [system path]/X11/xkb folder (e.g. X11/xkb/rules/evdev), but X isn’t installed. Rats.

Hacking the Xvfb binary to bypass xkbcomp isn’t reliable

A clever person on StackOverflow suggested hacking the Xvfb binary with string manipulation in order to trick/bypass the xkbcomp (keymap compiling) section. Using strings on Xvfb he tracked down this bit of code in xkb/ddxLoad.c:

The idea is to patch the binary with another command which merely copies a pre-compiled keymap file to the correct destination and returns successfully. The full procedure is here. This almost works, except we don’t know 100% of the time what the destination compiled keymap file is supposed to be called. It takes the form /tmp/server-[1..99].xkm plus we only have limited real estate in replacing the string above. I tried to patch the string with a shell NOP command instead (:) and manually copied the default.xkm file, but other problems happened later. Good try though.

Compiling a modified Xvfb from source works

Prerequisites:

  • Virtual machine with 64-bit CentOS built with GLibc 2.12 (e.g. CentOS 6.8, here)
  • Source code of X11 (X11R7.7 is here)
  • Internet connection and root access on the source CentOS machine

First, I obtained a VMWare image of CentOS 6.8 (with GLibc 2.12) from osboxes.org. It’s already set up with X.

The VMWare image of CentOS doesn’t have internet enabled. Run dhclient -v from root to fix this. Also, be sure VMWare Tools is installed if you use the VMWare image of CentOS.

I then verified the Glibc version with ldd --version to make sure it matches that of my shared host (2.12).

Before going any further, it’s best to yum update all the packages before doing anything else.

Running yum update on the VMWare image above will take a long time and may require 500+ MB. You may have to reinstall VMWare Tools afterwards.

Next, I downloaded and unpacked the most recent X11 source code (X11R7.7) from https://www.x.org/wiki/Releases/Download/. In the source folder I needed to modify these files:

  • xkb/xkbInit.c
  • xkb/ddxLoad.c

Near the bottom of xkbInit.c is the function XkbProcessArguments(int argc, char *argv[], int i). At the bottom I added this code to allow environment variables to change the hard-coded locations used in keymap compiling.

This got me to the point where xkbcomp is looking in the right folders, but wouldn’t it be nice if I could omit all the extra folders and files needed to compile a default keymap?

To this end I manually compiled a default keymap default.xkm from default.xkb using the following description1:

and running this command to compile it:

This results in a default.xkm which can be copied into the same folder supplied to XKBDIR. This next modification will use the above-created keymap.

Update: Here is the default.xkm file I used (right click + save as).

Near the top of ddxLoad.c is the function RunXkbComp(xkbcomp_buffer_callback callback, void *userdata). I made the following changes to use the pre-made XKM file:

With the above two source file modifications, the xkbcomp command will be constructed like this:

which effectively becomes a command similar to the following which performs a check on the default.xkm keymap file and copies it to the /tmp folder

Next, several dependencies need to be installed to compile Xvfb. To find out which, I cd‘d to the X11 source files and ran ./configure as an unprivileged user (I used the default osboxes account in the VMWare image). ./configure will helpfully die at dependency failures. For example:

Xvfb compiling first error at gcc

This means a compiler is not present. I use GCC – $ yum search gcc.

To make this task of dependency hunting easier it helped to have two terminals open – one for root and the other for an unprivileged user.
Linux root and normal user side by side
Linux root and normal user side by side

The next dependency missing was pixman.

When messages like this arose, I needed to find the package which supplies the missing dependency using yum search.

I actually want to install the devel.x86_64 version of each dependency so I can make a portable binary of Xvfb.

Here are all the dependencies and how I installed them:

  • PIXMAN (pixman-1 >= 0.27.2) – yum install pixman-devel.x86_64
  • LIBDRM (libdrm >= 2.3.0) – yum install libdrm-devel.x86_64
  • XLIB (x11) – yum install libX11-devel.x86_64
  • GL (glproto >= 1.4.17 gl >= 9.2.0) – yum install mesa-libGL-devel.x86_64
  • SHA1 (OpenSSL) – yum install openssl-devel.x86_64
  • xtrans – yum install xorg-x11-xtrans-devel.noarch
  • xkbfile – yum install libxkbfile-devel.x86_64
  • xfont – yum install libXfont-devel.x86_64
  • PCIACCESS (pciaccess >= 0.12.901) – yum install libpciaccess-devel.x86_64
  • xcb/xcb_keysyms.h – yum install xcb-util-keysyms-devel.x86_64

Finally I was able to run ./configure and successfully finish the script.

Next, I ran make in the same sources directory to build the Xvfb binary. It is found in [sources]/hw/vfb.

Now this binary, and this binary alone, I uploaded to my shared host and checked the linked dependencies:

The good news is it only depends on one shared library – libGL.so.1 (548 Kb) – so I can copy that from my VMWare CentOS to my shared host. I set LD_LIBRARY_PATH to a common path for my lib64 libraries. Here is how I found and copied the library.

On the CentOS VMWare

Then I copied the /tmp/libGL.so.1 to my shared host in the same directory as I set LD_LIBRARY_PATH.

ldd libGL.so.1 shows that it depends on other libraries as well. libglapi.so.0 and libXxf86vm.so.1 need to be copied to the shared host too in a similar manner as libGL.so.1.

Once these three libraries are uploaded to the shared host, I could test Xvfb.

Xvfb minimal files

And that is how I got Xvfb to run on a shared host without X and without root access.

Notes:

  1. This XKB description was found here.