diff options
author | Alan Coopersmith <alan.coopersmith@oracle.com> | 2013-03-16 19:38:14 -0700 |
---|---|---|
committer | Alan Coopersmith <alan.coopersmith@oracle.com> | 2013-03-30 20:29:00 -0700 |
commit | 2fc68068b8d61725fb887acde8b007b7ca4115aa (patch) | |
tree | c9633aae80c55821ceb62bc898185e85cb5ca763 |
Create initial framework
This provides a local static libXhiv.a library that forks and runs a
fake X server process that simply responds with a canned set of replies,
and which test cases can be written that check to see how libraries handle
those replies.
Signed-off-by: Alan Coopersmith <alan.coopersmith@oracle.com>
-rw-r--r-- | .gitignore | 79 | ||||
-rw-r--r-- | Makefile.am | 40 | ||||
-rwxr-xr-x | autogen.sh | 14 | ||||
-rw-r--r-- | configure.ac | 58 | ||||
-rw-r--r-- | include/xhiv.h | 64 | ||||
-rw-r--r-- | src/Makefile.am | 12 | ||||
-rw-r--r-- | src/proto.h | 45 | ||||
-rw-r--r-- | src/server.c | 777 | ||||
-rw-r--r-- | src/xcb_client.c | 85 | ||||
-rw-r--r-- | src/xlib_client.c | 67 | ||||
-rw-r--r-- | src/xstrans.c | 30 |
11 files changed, 1271 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e0d8c42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,79 @@ +# +# X.Org module default exclusion patterns +# The next section if for module specific patterns +# +# Do not edit the following section +# GNU Build System (Autotools) +aclocal.m4 +autom4te.cache/ +autoscan.log +ChangeLog +compile +config.guess +config.h +config.h.in +config.log +config-ml.in +config.py +config.status +config.status.lineno +config.sub +configure +configure.scan +depcomp +.deps/ +INSTALL +install-sh +.libs/ +libtool +libtool.m4 +ltmain.sh +lt~obsolete.m4 +ltoptions.m4 +ltsugar.m4 +ltversion.m4 +Makefile +Makefile.in +mdate-sh +missing +mkinstalldirs +*.pc +py-compile +stamp-h? +symlink-tree +texinfo.tex +ylwrap + +# Do not edit the following section +# Edit Compile Debug Document Distribute +*~ +*.[0-9] +*.[0-9]x +*.bak +*.bin +core +*.dll +*.exe +*-ISO*.bdf +*-JIS*.bdf +*-KOI8*.bdf +*.kld +*.ko +*.ko.cmd +*.lai +*.l[oa] +*.[oa] +*.obj +*.patch +*.so +*.pcf.gz +*.pdb +*.tar.bz2 +*.tar.gz +# +# Add & Override patterns for xhiv +# +# Edit the following section as needed +# For example, !report.pc overrides *.pc. See 'man gitignore' +# + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..e20a786 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,40 @@ +# +# Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# + +SUBDIRS = src + +noinst_HEADERS = include/xhiv.h + +EXTRA_DIST = autogen.sh + +MAINTAINERCLEANFILES = ChangeLog INSTALL + +.PHONY: ChangeLog INSTALL + +INSTALL: + $(INSTALL_CMD) + +ChangeLog: + $(CHANGELOG_CMD) + +dist-hook: ChangeLog INSTALL diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..fc34bd5 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,14 @@ +#! /bin/sh + +srcdir=`dirname $0` +test -z "$srcdir" && srcdir=. + +ORIGDIR=`pwd` +cd $srcdir + +autoreconf -v --install || exit 1 +cd $ORIGDIR || exit $? + +if test -z "$NOCONFIGURE"; then + $srcdir/configure "$@" +fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..e4b7be0 --- /dev/null +++ b/configure.ac @@ -0,0 +1,58 @@ +dnl Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. +dnl +dnl Permission is hereby granted, free of charge, to any person obtaining a +dnl copy of this software and associated documentation files (the "Software"), +dnl to deal in the Software without restriction, including without limitation +dnl the rights to use, copy, modify, merge, publish, distribute, sublicense, +dnl and/or sell copies of the Software, and to permit persons to whom the +dnl Software is furnished to do so, subject to the following conditions: +dnl +dnl The above copyright notice and this permission notice (including the next +dnl paragraph) shall be included in all copies or substantial portions of the +dnl Software. +dnl +dnl THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +dnl IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +dnl FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +dnl THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +dnl LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +dnl FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +dnl DEALINGS IN THE SOFTWARE. +dnl +dnl Process this file with autoconf to create configure. + +# Initialize Autoconf +AC_PREREQ([2.60]) +AC_INIT([xhiv], [0.0.1], + [https://bugs.freedesktop.org/enter_bug.cgi?product=xorg], [xhiv]) +AC_CONFIG_SRCDIR([Makefile.am]) +AC_CONFIG_HEADERS([config.h]) +AC_USE_SYSTEM_EXTENSIONS + +# Initialize Automake +AM_INIT_AUTOMAKE([foreign dist-bzip2]) + +# Dependencies for automake's static library support +AC_PROG_RANLIB + +# Require xorg-macros 1.8 or later for MAN_SUBSTS set by XORG_MANPAGE_SECTIONS +m4_ifndef([XORG_MACROS_VERSION], + [m4_fatal([must install xorg-macros 1.8 or later before running autoconf/autogen])]) +XORG_MACROS_VERSION(1.8) +XORG_DEFAULT_OPTIONS + +# Transport selection macro from xtrans.m4 +XTRANS_CONNECTION_FLAGS + +# Dependencies for the xhiv framework itself +PKG_CHECK_MODULES(XHIV, [xproto >= 7.0.22 bigreqsproto xtrans]) + +# Use pkg-config to get required cflags & libraries for each library we test +PKG_CHECK_MODULES(LIBX11, [x11]) +PKG_CHECK_MODULES(LIBXCB, [xcb]) + +AC_CONFIG_FILES([ + Makefile + src/Makefile + ]) +AC_OUTPUT diff --git a/include/xhiv.h b/include/xhiv.h new file mode 100644 index 0000000..b644e09 --- /dev/null +++ b/include/xhiv.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef XHIV_H +#define XHIV_H + +#include <inttypes.h> +#include <sys/types.h> + +/* Structures definining data to send to client after given requests */ +#define XHIV_REQ_IGNORE 1024 /* Match only seq number, not req code */ +#define XHIV_REQ_CONN_SETUP 1025 /* Initial handshake */ + +#define XHIV_SEQ_IGNORE 0xFFFFFFFF /* Match only req code, not seq number */ + +#define XHIV_NO_SET_SEQUENCE 0x01 /* Don't set sequence number in reply */ + +typedef struct xhiv_response { + struct xhiv_response *next; + uint16_t reqType; /* Request code, extension number or XHIV_REQ constant */ + uint16_t reqMinor; /* Extension minor request code */ + uint32_t sequence; /* Sequence number, or XHIV_SEQ_IGNORE */ + uint32_t length; /* Total length of reply packet, in 4-byte words */ + const void *response_data; /* Data to return */ + uint32_t response_datalen; /* Length of response_data, in bytes */ + /* Response will be filled with random data to fill the difference + between response_datalen & length */ + uint32_t flags; /* flags controlling various options */ +} xhiv_response; + +/* Fork a server process and return the string needed to connect to it. */ +extern char *XhivOpenServer(xhiv_response *responses, pid_t *return_pid); + +/* Open a Xlib display connection to a new Xhiv server */ +#include <X11/Xlib.h> +extern Display *XhivOpenDisplay(xhiv_response *responses); +extern int XhivCloseDisplay(Display *dpy); + +/* Open an xcb display connection to a new Xhiv server */ +#include <xcb/xcb.h> +extern xcb_connection_t *xhiv_connect(xhiv_response *responses); +extern int xhiv_disconnect(xcb_connection_t *conn); + +#endif /* XHIV_H */ diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..d2bbe7d --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,12 @@ +noinst_LIBRARIES = libXhiv.a + +libXhiv_a_SOURCES = \ + proto.h \ + xcb_client.c \ + xlib_client.c \ + xstrans.c \ + server.c + +AM_CFLAGS = $(XHIV_CFLAGS) $(CWARNFLAGS) $(LIBX11_CFLAGS) $(LIBXCB_CFLAGS) + +AM_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir)/include diff --git a/src/proto.h b/src/proto.h new file mode 100644 index 0000000..8534d26 --- /dev/null +++ b/src/proto.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <inttypes.h> + +/* Major op code (reqType) */ +#define X_XHIV_PROTO_REQTYPE 254 + +/* Minor op code (reqMinor) */ +#define XhivSeqStart 0 /* start sequence counting */ + +/* Fake extension protocol between xhiv client setup & server */ +typedef struct { + uint8_t reqType; /* XHIV_PROTO_REQTYPE */ + uint8_t reqMinor; /* XHIV_PROTO_SEQSTART */ + uint16_t length; /* 1 - no more data needed */ +} xXhivSeqStartReq; +#define sz_xXhivSeqStartReq 4 + +/* internal API - not really proto */ +extern int XhivWaitServer(pid_t server_pid); diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000..50f5e41 --- /dev/null +++ b/src/server.c @@ -0,0 +1,777 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "xhiv.h" +#include "proto.h" + +#define X11_t +#define TRANS_SERVER +#include <X11/Xtrans/Xtrans.h> +#include <X11/Xtrans/Xtransint.h> +#include <X11/X.h> +#include <X11/Xproto.h> +#include <X11/extensions/bigreqsproto.h> + +#include <sys/types.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> +#include <poll.h> +#include <assert.h> +#include <errno.h> +#include <limits.h> +#include <sys/wait.h> + +#ifndef O_CLOEXEC +# define O_CLOEXEC 0 +#endif + +/* Data waiting to be written to clients */ +typedef struct client_response_buffer { + struct client_response_buffer *next; + const void *response_data; /* Data to return */ + uint32_t response_datalen; /* Length of response_data, in bytes */ + uint32_t length; /* Total length of reply packet, in 4-byte words */ + uint64_t response_written; /* Number of bytes written so far */ + uint32_t response_sequence; /* Sequence number to set or XHIV_SEQ_IGNORE */ +} client_response_buffer; + +typedef struct client_state { + XtransConnInfo conn; + struct client_response_buffer *crb; + uint32_t sequence; /* sequence reported to client */ + uint32_t match_sequence; /* sequence used for matching responses */ + uint32_t req_len_remaining; /* remaining length to discard from last req */ +} client_state; + +/************************************************************************* + * Data for server/client handshake + */ + +#define ARRAY_SIZE(a) (sizeof((a)) / sizeof((a)[0])) +#define bytes_to_int32(b) (((b) + 3) >> 2) + +static const char default_vendor_string[] = PACKAGE_STRING; + +static const xConnSetup default_conn_setup = { + .release = (PACKAGE_VERSION_MAJOR << 24) | + (PACKAGE_VERSION_MINOR << 16) | + (PACKAGE_VERSION_PATCHLEVEL << 8), + .ridBase = 0x00200000, + .ridMask = 0x001fffff, + .motionBufferSize = 256, + .nbytesVendor = sizeof(default_vendor_string), + .maxRequestSize = SHRT_MAX, + .numRoots = 1, + .numFormats = 7, /* must be same as default_pixmap_formats below */ + .imageByteOrder = LSBFirst, + .bitmapBitOrder = LSBFirst, + .bitmapScanlineUnit = 32, + .bitmapScanlinePad = 32, + .minKeyCode = 8, + .maxKeyCode = 255, +}; + +static const xPixmapFormat default_pixmap_formats[7] = { + /* depth, bits-per-pixel, scanline-pad */ + { 1, 1, 32 }, + { 4, 4, 32 }, + { 8, 8, 32 }, + { 15, 15, 32 }, + { 16, 16, 32 }, + { 24, 24, 32 }, + { 32, 32, 32 } +}; + +static const xWindowRoot default_root_window = { + .windowId = 0x47, + .defaultColormap = 0x20, + .whitePixel = 0x00ffffffU, + .blackPixel = 0, + .currentInputMask = 0, + .pixWidth = 1024, + .pixHeight = 768, +#define pixels_to_mm(p) (((p) * 25.4) / 96) + .mmWidth = pixels_to_mm(1024), + .mmHeight = pixels_to_mm(768), + .minInstalledMaps = 1, + .maxInstalledMaps = 1, + .rootVisualID = 0x21, + .backingStore = NotUseful, + .saveUnders = xFalse, + .rootDepth = 24, + .nDepths = 7 /* must be same as default_depths below */ +}; + +static const xDepth default_depths[7] = { + { .depth = 1, .nVisuals = 0 }, + { .depth = 4, .nVisuals = 0 }, + { .depth = 8, .nVisuals = 0 }, + { .depth = 15, .nVisuals = 0 }, + { .depth = 16, .nVisuals = 0 }, + { .depth = 32, .nVisuals = 0 }, + { .depth = 24, .nVisuals = 1 }, /* must be same as default_visuals below */ +}; + +static const xVisualType default_visuals[1] = { + { + .visualID = 0x21, + .class = TrueColor, + .bitsPerRGB = 8, + .colormapEntries = 256, + .redMask = 0x000000ff, + .greenMask = 0x0000ff00, + .blueMask = 0x00ff0000 + } +}; + +static const xConnSetupPrefix default_conn_setup_prefix = { + .success = xTrue, + .lengthReason = 0, + .majorVersion = X_PROTOCOL, + .minorVersion = X_PROTOCOL_REVISION, + .length = bytes_to_int32(sizeof(xConnSetup) + + sizeof(default_pixmap_formats) + + sizeof(default_root_window) + + sizeof(default_depths) + + sizeof(default_visuals) ) + + bytes_to_int32(sizeof(default_vendor_string)) +}; + +static const xhiv_response default_conn_response[] = { + { + .length = bytes_to_int32(sz_xConnSetupPrefix), + .response_data = &default_conn_setup_prefix, + .response_datalen = sizeof(default_conn_setup_prefix), + .flags = XHIV_NO_SET_SEQUENCE + }, + + { + .length = bytes_to_int32(sz_xConnSetup), + .response_data = &default_conn_setup, + .response_datalen = sizeof(default_conn_setup), + .flags = XHIV_NO_SET_SEQUENCE + }, + + { + .length = bytes_to_int32(sizeof(default_vendor_string)), + .response_data = default_vendor_string, + .response_datalen = sizeof(default_vendor_string), + .flags = XHIV_NO_SET_SEQUENCE + }, + + { + .length = bytes_to_int32(sizeof(default_pixmap_formats)), + .response_data = default_pixmap_formats, + .response_datalen = sizeof(default_pixmap_formats), + .flags = XHIV_NO_SET_SEQUENCE + }, + + { + .length = bytes_to_int32(sizeof(default_root_window)), + .response_data = &default_root_window, + .response_datalen = sizeof(default_root_window), + .flags = XHIV_NO_SET_SEQUENCE + }, + + { + .length = bytes_to_int32(sizeof(default_depths)), + .response_data = default_depths, + .response_datalen = sizeof(default_depths), + .flags = XHIV_NO_SET_SEQUENCE + }, + + { + .length = bytes_to_int32(sizeof(default_visuals)), + .response_data = default_visuals, + .response_datalen = sizeof(default_visuals), + .flags = XHIV_NO_SET_SEQUENCE + }, +}; + +/* In order to simulate BigRequests, we simply implement it with a hardcoded + extension request value */ +#define BIGREQ_REQTYPE 255 + +/************************************************************************* + * Data management functions + */ + +static client_response_buffer * +AddResponseToBuffer(client_response_buffer *crb, const xhiv_response *response, + uint32_t sequence) +{ + client_response_buffer *new_crb; + + uint64_t total_bytes= ((uint64_t) response->length) << 2; + assert(total_bytes >= response->response_datalen); + + new_crb = calloc(1, sizeof(client_response_buffer)); + assert(new_crb != NULL); + + new_crb->response_data = response->response_data; + new_crb->response_datalen = response->response_datalen; + new_crb->length = response->length; + new_crb->response_sequence = (response->flags & XHIV_NO_SET_SEQUENCE) + ? XHIV_SEQ_IGNORE : sequence; + + if (crb == NULL) + crb = new_crb; + else { + client_response_buffer *n; + + for (n = crb ; n->next != NULL; n = n->next) { + /* find end of list */ + } + n->next = new_crb; + } + return crb; +} + +/* Find the first response matching the criteria */ +static xhiv_response * +FindXhivResponse(xhiv_response *xrlist, uint16_t reqType, uint16_t reqMinor, + uint32_t sequence) +{ + xhiv_response *r; + + for (r = xrlist; r != NULL ; r = r->next) { + if (((r->reqType == reqType) || (r->reqType == XHIV_REQ_IGNORE)) && + ((r->reqMinor == reqMinor) || (r->reqMinor == XHIV_REQ_IGNORE)) && + ((r->sequence == sequence) || (r->sequence == XHIV_SEQ_IGNORE))) + return r; + } + return NULL; +} + + +/************************************************************************* + * Client communication functions + */ + +static void +CloseListenTrans(XtransConnInfo *ListenTransConns, int ListenTransCount) +{ + int i; + + for (i = 0; i < ListenTransCount; i++) + _X11TransClose(ListenTransConns[i]); +} + +static XtransConnInfo +WaitForClient(XtransConnInfo *ListenTransConns, int ListenTransCount) +{ + struct pollfd *pollfds; + int i; + XtransConnInfo ClientTransConn = NULL; + + pollfds = calloc(ListenTransCount, sizeof(struct pollfd)); + assert (pollfds != NULL); + + for (i = 0; i < ListenTransCount; i++) { + pollfds[i].fd = _X11TransGetConnectionNumber(ListenTransConns[i]); + pollfds[i].events = POLLIN; + } + + while (ClientTransConn == NULL) { + int readyfds = poll(pollfds, ListenTransCount, -1); + + if (readyfds > 0) { + for (i = 0; i < ListenTransCount; i++) { + if (pollfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { + perror("bad state polling for client connection"); + exit(11); + } + else if (pollfds[i].revents) { + int status; + ClientTransConn = + _X11TransAccept(ListenTransConns[i], &status); + if (ClientTransConn) + break; + } + } + } + else if (readyfds < 0) { /* error */ + if (errno != EAGAIN && errno != EINTR) { + perror("polling for client connection"); + exit(11); + } + } + } + + free(pollfds); + CloseListenTrans(ListenTransConns, ListenTransCount); + return ClientTransConn; +} + +static int urandom_fd = -1; + +static void +HandleClientResponses(client_state *client) +{ + while (client->crb != NULL) { + client_response_buffer *crb = client->crb; + uint64_t nbytes, wbytes, total_bytes; + + if ((crb->response_written == 0) && + (crb->response_sequence != XHIV_SEQ_IGNORE)) { + /* Set sequence in initial bytes if needed */ + xGenericReply rep; + +#ifdef DEBUG + printf("Sending reply: seq = %d, length = %d\n", + crb->response_sequence, crb->length); +#endif + + nbytes = sizeof(rep); + if (nbytes > crb->response_datalen) + nbytes = crb->response_datalen; + + memcpy(&rep, crb->response_data, nbytes); + rep.sequenceNumber = (CARD16) crb->response_sequence; + wbytes = _X11TransWrite(client->conn, (char *) &rep, nbytes); + if (wbytes > 0) + crb->response_written += wbytes; + } + + if (crb->response_written < crb->response_datalen) { + nbytes = crb->response_datalen - crb->response_written; + wbytes = _X11TransWrite(client->conn, + (const char *) crb->response_data + crb->response_written, + nbytes); + if (wbytes > 0) + crb->response_written += wbytes; + if (wbytes != nbytes) /* pipe is full, try again later */ + return; + } + + total_bytes = ((uint64_t) crb->length) << 2; + nbytes = total_bytes - crb->response_written; + if (nbytes > 0) { + char ranbuf[32768]; + + if (urandom_fd < 0) { + urandom_fd = open("/dev/urandom", + O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (urandom_fd < 0) { + perror("Could not open /dev/urandom"); + exit(11); + } + } + + /* + * Read some random bytes to write to fill buffer. + * If it fails, ignore the error and continue with whatever + * (not truly random) uninitialized data is on our stack. + */ + if (nbytes > sizeof(ranbuf)) + nbytes = sizeof(ranbuf); + read(urandom_fd, ranbuf, nbytes); + + do { + wbytes = _X11TransWrite(client->conn, ranbuf, nbytes); + if (wbytes > 0) + crb->response_written += wbytes; + if (wbytes != nbytes) /* pipe is full, try again later */ + return; + nbytes = total_bytes - crb->response_written; + if (nbytes > sizeof(ranbuf)) + nbytes = sizeof(ranbuf); + } while (nbytes > 0); + } + + /* Are we done with this response buffer? If so, nuke it & move on. */ + if (total_bytes == crb->response_written) { + client->crb = crb->next; + free(crb); + } + else { + assert(total_bytes > crb->response_written); + return; /* if we couldn't finish this one, wait for poll to say + the client is ready for more */ + } + } +} + +static unsigned char readbuf[65536]; + +static void +DiscardRequestData(client_state *client) +{ + /* Read & ignore all the data in the request we don't care about */ + while (client->req_len_remaining) { + int nbytes = (client->req_len_remaining > sizeof(readbuf)) ? + sizeof(readbuf) : client->req_len_remaining; + int rbytes = _X11TransRead(client->conn, (char *)readbuf, nbytes); + if (rbytes <= 0) + break; + client->req_len_remaining -= rbytes; +#ifdef DEBUG + printf("Discarded %d bytes of request data\n", rbytes); +#endif + } +} + +static void +HandleClientRequest(client_state *client, xhiv_response *responses) +{ + if (client->sequence == 0) { /* handshaking */ + xhiv_response *r; + int i = 0; + + client->req_len_remaining = sizeof(xConnClientPrefix); + DiscardRequestData(client); + + while ((r = FindXhivResponse(responses, XHIV_REQ_CONN_SETUP, + XHIV_REQ_IGNORE, i))) { + client->crb = AddResponseToBuffer(client->crb, r, 0); + i++; + } + + if (i == 0) { /* use default connection sequence if none provided */ + for (i = 0; i < ARRAY_SIZE(default_conn_response); i++) + client->crb = AddResponseToBuffer(client->crb, + &default_conn_response[i], + 0); + } + } else { /* normal protocol request/reply cycle */ + xhiv_response *r; + xReq req; + int rbytes; + uint32_t length; + + if (client->req_len_remaining) /* still reading last request */ + DiscardRequestData(client); + if (client->req_len_remaining) /* not all there yet */ + return; /* back to poll again for more data */ + + errno = 0; + rbytes = _X11TransRead(client->conn, (char *)&req, sizeof(req)); + if ((rbytes == 0) && (errno == 0)) { + /* client disconnected */ + _X11TransClose(client->conn); + return; + } + if (rbytes <= 0) { + if ((errno == EINTR) || (errno == EAGAIN)) + return; + perror("Reading from client"); + exit(11); + } + assert(rbytes == sizeof(req)); + if (req.length == 0) { /* BIG Request */ + rbytes = _X11TransRead(client->conn, (char *)&length, 4); + assert(rbytes == 4); + } + else + length = req.length; + + /* X11 packets count the initial header as part of their length */ + client->req_len_remaining = (length << 2) - sizeof(req); + + r = FindXhivResponse(responses, req.reqType, req.data, + client->match_sequence); + if (r != NULL) + client->crb = AddResponseToBuffer(client->crb, r, + client->sequence); + else { /* If match not found, check against builtin responses */ + switch (req.reqType) { + case X_QueryExtension: + /* XOpenDisplay checks for BIG-REQUESTS & XKB extensions. + We only simulate BIG-REQUESTS for now */ + { + int nbytes = client->req_len_remaining; + char extension[32] = ""; + xQueryExtensionReply qext_reply = { + .type = X_Reply, + .length = 0, + .present = xFalse, + .major_opcode = 0, + .first_event = 0, + .first_error = 0 + }; + xhiv_response qext_response = { + .length = bytes_to_int32(sz_xQueryExtensionReply), + .response_data = &qext_reply, + .response_datalen = sizeof(qext_reply) + }; + + if (nbytes > sizeof(extension)) + nbytes = sizeof(extension); + rbytes = _X11TransRead(client->conn, (char *)&extension, + nbytes); + if (rbytes > 0) { + assert(client->req_len_remaining >= rbytes); + client->req_len_remaining -= rbytes; + if (strncmp(extension + 4, XBigReqExtensionName, + sizeof(XBigReqExtensionName)) == 0) { + qext_reply.present = xTrue; + qext_reply.major_opcode = BIGREQ_REQTYPE; + } + } + else { + assert(rbytes == 0); + } + client->crb = + AddResponseToBuffer(client->crb, &qext_response, + client->sequence); + } + break; + + case X_GetProperty: + /* XOpenDisplay requests the root window resource property. + We just claim all properties don't exist. */ + { + const xGetPropertyReply getp_reply = { + .type = X_Reply, + .format = 0, + .length = 0, + .propertyType = None, + .bytesAfter = 0, + .nItems = 0 + }; + xhiv_response getp_response = { + .length = bytes_to_int32(sz_xGetPropertyReply), + .response_data = &getp_reply, + .response_datalen = sizeof(getp_reply) + }; + client->crb = + AddResponseToBuffer(client->crb, &getp_response, + client->sequence); + } + break; + + case X_GetInputFocus: + /* XSync() sends this request to force a quick reply */ + { + const xGetInputFocusReply getif_reply = { + .type = X_Reply, + .revertTo = None, + .length = 0, + .focus = None + }; + xhiv_response getif_response = { + .length = bytes_to_int32(sz_xGetInputFocusReply), + .response_data = &getif_reply, + .response_datalen = sizeof(getif_reply) + }; + client->crb = + AddResponseToBuffer(client->crb, &getif_response, + client->sequence); + } + break; + + case X_XHIV_PROTO_REQTYPE: /* our fake extension */ + if (req.data == XhivSeqStart) + client->match_sequence = 0; + break; + + case BIGREQ_REQTYPE: /* our fake BIG-REQUESTS extension */ + if (req.data == X_BigReqEnable) { + const xBigReqEnableReply bigreq_reply = { + .type = X_Reply, + .length = 0, + .max_request_size = UINT32_MAX + }; + xhiv_response bigreq_response = { + .length = bytes_to_int32(sz_xBigReqEnableReply), + .response_data = &bigreq_reply, + .response_datalen = sizeof(bigreq_reply) + }; + client->crb = + AddResponseToBuffer(client->crb, &bigreq_response, + client->sequence); + } + break; + + default: +#ifdef DEBUG + printf("Discarded unhandled request type %d.%d (%d/%d)\n", + req.reqType, req.data, + client->match_sequence, client->sequence); +#endif + break; + } + } + /* don't need any more data from the request now */ + DiscardRequestData(client); + } + client->sequence++; + client->match_sequence++; + + if (client->crb != NULL) + HandleClientResponses(client); +} + +/* main I/O loop of the server */ +static void _X_NORETURN +XhivRunServer(XtransConnInfo *ListenTransConns, int ListenTransCount, + xhiv_response *responses) +{ + struct pollfd clientfd = { .events = POLLIN }; + client_state client = { 0 }; + + /* Just in case connection transport signals hangups w/ SIGPIPE */ + signal(SIGPIPE, SIG_IGN); + + /* Wait for a client to connect - when it does, the connections + passed in are closed, and only the client socket remains open */ + client.conn = WaitForClient(ListenTransConns, ListenTransCount); + clientfd.fd = _X11TransGetConnectionNumber(client.conn); + _X11TransSetOption(client.conn, TRANS_NONBLOCKING, 1); + + for (;;) { /* repeat until client hangs up on us */ + int readyfds = poll(&clientfd, 1, -1); + + if (readyfds > 0) { + if (clientfd.revents & (POLLERR | POLLHUP | POLLNVAL)) { + /* assume client hungup, so we do too */ + break; + } + else { + if (clientfd.revents & (POLLIN | POLLRDNORM)) + HandleClientRequest(&client, responses); + if (clientfd.revents & (POLLOUT | POLLWRNORM)) + HandleClientResponses(&client); + } + } + else if (readyfds < 0) { /* error */ + if (errno != EAGAIN && errno != EINTR) { + perror("polling for client requests"); + exit(11); + } + } + + if (client.crb == NULL) + clientfd.events = POLLIN; + else + clientfd.events = POLLIN | POLLOUT; + } + _X11TransClose(client.conn); + exit(0); +} + +int +XhivWaitServer(pid_t server_pid) { + int pidstat; + pid_t waitret; + + assert(server_pid > 0); + + while ((waitret = waitpid(server_pid, &pidstat, 0)) == -1) { + if (errno != EAGAIN && errno != EINTR) { + perror("waiting for server to exit"); + exit(11); + } + } + if (WIFEXITED(pidstat)) { + int exitstat = WEXITSTATUS(pidstat); + + if (exitstat != 0) + fprintf(stderr, "Server %ld exited with %d.\n", + (long) server_pid, exitstat); + + return exitstat; + } else if (WIFSIGNALED(pidstat)) { + int sig = WTERMSIG(pidstat); + char signame[SIG2STR_MAX]; + + if (sig2str(sig, signame) == -1) + snprintf(signame, sizeof(signame), "unknown"); + + fprintf(stderr, "Server %ld killed by signal %d (%s)%s.\n", + (long) server_pid, sig, signame, + WCOREDUMP(pidstat) ? " -- core dumped" : ""); + return sig; + } else { + assert(pidstat); + } + return 0; +} + +/************************************************************************* + * External interface to this framework + */ + +char * +XhivOpenServer(xhiv_response *responses, pid_t *return_pid) +{ + char *display; + pid_t kidpid; + int i, found; + XtransConnInfo *ListenTransConns = NULL; + int ListenTransCount = 0; + + /* TODO: configure nolisten options */ + + /* find an unused socket to run on */ + for (i = 20, found = 0; i < 65535 - X_TCP_PORT; i++) { + char port[8]; + int partial = 0; + + snprintf(port, sizeof(port), "%d", i); + + if (_X11TransMakeAllCOTSServerListeners( + port, &partial, &ListenTransCount, &ListenTransConns) >= 0) + { + snprintf(port, sizeof(port), ":%d", i); + display = strdup(port); + found = 1; + break; + } + } + if (found == 0) { + perror("Could not open server socket on any port"); + exit(11); + } + + fflush(stdout); + fflush(stderr); + + kidpid = fork(); + if (kidpid == 0) { /* child */ + free(display); + XhivRunServer(ListenTransConns, ListenTransCount, responses); + } else if (kidpid == -1) { /* error */ + perror("Fork of server process failed"); + exit(11); + } + /* else parent */ + CloseListenTrans(ListenTransConns, ListenTransCount); + printf("Client %ld forked server child %ld on display %s\n", + (long) getpid(), (long) kidpid, display); + + /* hack to allow sleeping long enough to attach debugger when needed */ + if (getenv("XHIV_SLEEP")) + sleep(atoi(getenv("XHIV_SLEEP"))); + + if (return_pid != NULL) + *return_pid = kidpid; + return display; +} diff --git a/src/xcb_client.c b/src/xcb_client.c new file mode 100644 index 0000000..9587ddd --- /dev/null +++ b/src/xcb_client.c @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "xhiv.h" +#include "proto.h" +#include <xcb/xcbext.h> +#include <assert.h> +#include <sys/types.h> + +static pid_t server_pid; + +static struct xcb_extension_t xcb_xhiv_id = { + .name = "XHIV", + .global_id = X_XHIV_PROTO_REQTYPE +}; + +static const xcb_protocol_request_t xcb_req = { + .count = 1, + .ext = &xcb_xhiv_id, + .opcode = XhivSeqStart, + .isvoid = 1 +}; + +static xXhivSeqStartReq xssreq = { + .reqType = X_XHIV_PROTO_REQTYPE, + .reqMinor = XhivSeqStart, + .length = 1 /* no more data needed */ +}; + +xcb_connection_t * +xhiv_connect(xhiv_response *responses) { + char *displayname; + xcb_connection_t *conn; + int screen; + struct iovec xcb_parts[4] = { + [2] = { .iov_base = &xssreq, .iov_len = sizeof(xssreq) }, + [3] = { .iov_base = 0, .iov_len = 0 /* no padding needed */ } + }; + + displayname = XhivOpenServer(responses, &server_pid); + assert(displayname != NULL); + + conn = xcb_connect(displayname, &screen); + assert(conn != NULL); + assert(screen == 0); + + xcb_send_request(conn, XCB_REQUEST_RAW, xcb_parts + 2, &xcb_req); + + return conn; +} + +int +xhiv_disconnect(xcb_connection_t *conn) { + pid_t waitfor; + + assert(xcb_connection_has_error(conn) == 0); + xcb_disconnect(conn); + + waitfor = server_pid; + server_pid = -1; + return XhivWaitServer(waitfor); +} diff --git a/src/xlib_client.c b/src/xlib_client.c new file mode 100644 index 0000000..03b56c8 --- /dev/null +++ b/src/xlib_client.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "xhiv.h" +#include "proto.h" +#include <X11/Xlibint.h> +#include <assert.h> +#include <sys/types.h> + +static pid_t server_pid; + +Display * +XhivOpenDisplay(xhiv_response *responses) { + char *dpyname; + Display *dpy; + xReq *xssreq; + + dpyname = XhivOpenServer(responses, &server_pid); + assert(dpyname != NULL); + + dpy = XOpenDisplay(dpyname); + assert(dpy != NULL); + + LockDisplay(dpy); + GetEmptyReq(XHIV_PROTO_REQTYPE, xssreq); + xssreq->data = XhivSeqStart; + UnlockDisplay(dpy); + SyncHandle(); + + return dpy; +} + +int +XhivCloseDisplay(Display *dpy) { + int ret; + pid_t waitfor; + + ret = XCloseDisplay(dpy); + assert(ret == Success); + + waitfor = server_pid; + server_pid = -1; + return XhivWaitServer(waitfor); +} diff --git a/src/xstrans.c b/src/xstrans.c new file mode 100644 index 0000000..2381f77 --- /dev/null +++ b/src/xstrans.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#define TRANS_SERVER +#define X11_t +#include <X11/Xtrans/transport.c> |