main shrub/neuswc / libswc / xserver.c
  1/* swc: libswc/xserver.c
  2 *
  3 * Copyright (c) 2013 Michael Forney
  4 *
  5 * Based in part upon xwayland/launcher.c from weston, which is
  6 *
  7 *     Copyright © 2011 Intel Corporation
  8 *
  9 * Permission is hereby granted, free of charge, to any person obtaining a copy
 10 * of this software and associated documentation files (the "Software"), to deal
 11 * in the Software without restriction, including without limitation the rights
 12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 13 * copies of the Software, and to permit persons to whom the Software is
 14 * furnished to do so, subject to the following conditions:
 15 *
 16 * The above copyright notice and this permission notice shall be included in
 17 * all copies or substantial portions of the Software.
 18 *
 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 25 * SOFTWARE.
 26 */
 27
 28#include "xserver.h"
 29#include "internal.h"
 30#include "util.h"
 31#include "xwm.h"
 32
 33#include <errno.h>
 34#include <fcntl.h>
 35#include <signal.h>
 36#include <stdio.h>
 37#include <stdlib.h>
 38#include <sys/socket.h>
 39#include <sys/stat.h>
 40#include <sys/un.h>
 41#include <unistd.h>
 42#include <wayland-server.h>
 43
 44#define LOCK_FMT "/tmp/.X%d-lock"
 45#define SOCKET_DIR "/tmp/.X11-unix"
 46#define SOCKET_FMT SOCKET_DIR "/X%d"
 47
 48static struct {
 49	struct wl_resource *resource;
 50	struct wl_event_source *usr1_source;
 51	int display;
 52	char display_name[16];
 53	int abstract_fd, unix_fd, wm_fd;
 54	bool xwm_initialized;
 55} xserver;
 56
 57struct swc_xserver swc_xserver;
 58
 59static int
 60open_socket(struct sockaddr_un *addr)
 61{
 62	int fd;
 63
 64	if ((fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0)) < 0) {
 65		goto error0;
 66	}
 67
 68	/* Unlink the socket location in case it was being used by a process which
 69	 * left around a stale lockfile. */
 70	unlink(addr->sun_path);
 71
 72	if (bind(fd, (struct sockaddr *)addr, sizeof(*addr)) < 0) {
 73		goto error1;
 74	}
 75
 76	if (listen(fd, 1) < 0) {
 77		goto error2;
 78	}
 79
 80	return fd;
 81
 82error2:
 83	if (addr->sun_path[0]) {
 84		unlink(addr->sun_path);
 85	}
 86error1:
 87	close(fd);
 88error0:
 89	return -1;
 90}
 91
 92static bool
 93open_display(void)
 94{
 95	char lock_name[64], pid[12];
 96	int lock_fd;
 97	struct sockaddr_un addr = {.sun_family = AF_LOCAL};
 98
 99	xserver.display = 0;
100
101	/* Create X lockfile and server sockets */
102	goto begin;
103
104retry2:
105#ifdef __linux__
106	close(xserver.abstract_fd);
107retry1:
108#endif
109	unlink(lock_name);
110retry0:
111	if (++xserver.display > 32) {
112		ERROR("No open display in first 32\n");
113		return false;
114	}
115
116begin:
117	snprintf(lock_name, sizeof(lock_name), LOCK_FMT, xserver.display);
118	lock_fd = open(lock_name, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0444);
119
120	if (lock_fd == -1) {
121		char *end;
122		pid_t owner;
123
124		/* Check if the owning process is still alive. */
125		if ((lock_fd = open(lock_name, O_RDONLY)) == -1) {
126			goto retry0;
127		}
128
129		if (read(lock_fd, pid, sizeof(pid) - 1) != sizeof(pid) - 1) {
130			goto retry0;
131		}
132
133		owner = strtol(pid, &end, 10);
134
135		if (end != pid + 10) {
136			goto retry0;
137		}
138
139		if (kill(owner, 0) == 0 || errno != ESRCH) {
140			goto retry0;
141		}
142
143		if (unlink(lock_name) != 0) {
144			goto retry0;
145		}
146
147		goto begin;
148	}
149
150	snprintf(pid, sizeof(pid), "%10d\n", getpid());
151	if (write(lock_fd, pid, sizeof(pid) - 1) != sizeof(pid) - 1) {
152		ERROR("Failed to write PID file\n");
153		unlink(lock_name);
154		close(lock_fd);
155		return false;
156	}
157
158	close(lock_fd);
159
160	#ifdef __linux__
161	/* Bind to abstract socket */
162	addr.sun_path[0] = '\0';
163	snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, SOCKET_FMT,
164	         xserver.display);
165	if ((xserver.abstract_fd = open_socket(&addr)) < 0) {
166		goto retry1;
167	}
168	#else
169	xserver.abstract_fd=-1;
170	#endif
171
172	/* Bind to unix socket */
173	mkdir(SOCKET_DIR, 0777);
174	snprintf(addr.sun_path, sizeof(addr.sun_path), SOCKET_FMT, xserver.display);
175	if ((xserver.unix_fd = open_socket(&addr)) < 0) {
176		goto retry2;
177	}
178
179	snprintf(xserver.display_name, sizeof(xserver.display_name), ":%d",
180	         xserver.display);
181	setenv("DISPLAY", xserver.display_name, true);
182
183	return true;
184}
185
186static void
187close_display(void)
188{
189	char path[64];
190
191	#ifdef __linux__
192	close(xserver.abstract_fd);
193	#endif
194	close(xserver.unix_fd);
195
196	snprintf(path, sizeof(path), SOCKET_FMT, xserver.display);
197	unlink(path);
198	snprintf(path, sizeof(path), LOCK_FMT, xserver.display);
199	unlink(path);
200
201	unsetenv("DISPLAY");
202}
203
204static int
205handle_usr1(int signal_number, void *data)
206{
207	if (xwm_initialize(xserver.wm_fd)) {
208		xserver.xwm_initialized = true;
209	} else {
210		ERROR("Failed to initialize X window manager\n");
211		/* XXX: How do we handle this case? */
212	}
213
214	wl_event_source_remove(xserver.usr1_source);
215
216	return 0;
217}
218
219static void
220handle_client_destroy(struct wl_listener *listener, void *data)
221{
222	swc_xserver.client = NULL;
223}
224
225static struct wl_listener client_destroy_listener = {
226    .notify = handle_client_destroy,
227};
228
229bool
230xserver_initialize(void)
231{
232	int wl[2], wm[2];
233
234	/* Open an X display */
235	if (!open_display()) {
236		ERROR("Failed to get X lockfile and sockets\n");
237		goto error0;
238	}
239
240	xserver.usr1_source =
241	    wl_event_loop_add_signal(swc.event_loop, SIGUSR1, &handle_usr1, NULL);
242
243	if (!xserver.usr1_source) {
244		ERROR("Failed to create SIGUSR1 event source\n");
245		goto error1;
246	}
247
248	/* Open a socket for the Wayland connection from Xwayland. */
249	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wl) != 0) {
250		ERROR("Failed to create socketpair: %s\n", strerror(errno));
251		goto error2;
252	}
253
254	/* Open a socket for the X connection to Xwayland. */
255	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wm) != 0) {
256		ERROR("Failed to create socketpair: %s\n", strerror(errno));
257		goto error3;
258	}
259
260	if (!(swc_xserver.client = wl_client_create(swc.display, wl[0]))) {
261		goto error4;
262	}
263
264	wl_client_add_destroy_listener(swc_xserver.client,
265	                               &client_destroy_listener);
266	xserver.wm_fd = wm[0];
267
268	/* Start the X server */
269	switch (fork()) {
270	case 0: {
271		int fds[] = {wl[1], wm[1], xserver.abstract_fd, xserver.unix_fd};
272		char strings[ARRAY_LENGTH(fds)][16];
273		unsigned index;
274		struct sigaction action = {.sa_handler = SIG_IGN};
275
276		/* Unset the FD_CLOEXEC flag on the FDs that will get passed to
277		 * Xwayland. */
278		for (index = 0; index < ARRAY_LENGTH(fds); ++index) {
279#if defined(__FreeBSD__) || defined(__OpenBSD__)
280			if (fds[index] == -1)
281				continue;
282#endif
283			if (fcntl(fds[index], F_SETFD, 0) != 0) {
284				ERROR("fcntl() failed: %s\n", strerror(errno));
285				goto fail;
286			}
287
288			if (snprintf(strings[index], sizeof(strings[index]), "%d",
289			             fds[index]) >= sizeof(strings[index])) {
290				ERROR("FD is too large\n");
291				goto fail;
292			}
293		}
294
295		/* Ignore the USR1 signal so that Xwayland will send a USR1 signal to
296		 * the parent process (us) after it finishes initializing. See
297		 * Xserver(1) for more details. */
298		if (sigaction(SIGUSR1, &action, NULL) != 0) {
299			ERROR("Failed to set SIGUSR1 handler to SIG_IGN: %s\n",
300			      strerror(errno));
301			goto fail;
302		}
303
304		setenv("WAYLAND_SOCKET", strings[0], true);
305
306		#ifdef __linux__
307		execlp("Xwayland", "Xwayland", xserver.display_name, "-rootless",
308		       "-terminate", "-listen", strings[2], "-listen", strings[3],
309		       "-wm", strings[1], NULL);
310		#else
311		execlp("Xwayland", "Xwayland", xserver.display_name, "-rootless",
312		       "-terminate", "-listen", strings[3],
313		       "-wm", strings[1], NULL);
314		#endif
315
316	fail:
317		exit(EXIT_FAILURE);
318	}
319	case -1:
320		ERROR("fork() failed when trying to start X server: %s\n",
321		      strerror(errno));
322		goto error5;
323	}
324
325	close(wl[1]);
326	close(wm[1]);
327
328	return true;
329
330error5:
331	wl_client_destroy(swc_xserver.client);
332error4:
333	close(wm[1]);
334	close(wm[0]);
335error3:
336	close(wl[1]);
337	close(wl[0]);
338error2:
339	wl_event_source_remove(xserver.usr1_source);
340error1:
341	close_display();
342error0:
343	return false;
344}
345
346void
347xserver_finalize(void)
348{
349	if (xserver.xwm_initialized) {
350		xwm_finalize();
351	}
352	if (swc_xserver.client) {
353		wl_client_destroy(swc_xserver.client);
354	}
355	close_display();
356}