main 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	close(xserver.abstract_fd);
106retry1:
107	unlink(lock_name);
108retry0:
109	if (++xserver.display > 32) {
110		ERROR("No open display in first 32\n");
111		return false;
112	}
113
114begin:
115	snprintf(lock_name, sizeof(lock_name), LOCK_FMT, xserver.display);
116	lock_fd = open(lock_name, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0444);
117
118	if (lock_fd == -1) {
119		char *end;
120		pid_t owner;
121
122		/* Check if the owning process is still alive. */
123		if ((lock_fd = open(lock_name, O_RDONLY)) == -1) {
124			goto retry0;
125		}
126
127		if (read(lock_fd, pid, sizeof(pid) - 1) != sizeof(pid) - 1) {
128			goto retry0;
129		}
130
131		owner = strtol(pid, &end, 10);
132
133		if (end != pid + 10) {
134			goto retry0;
135		}
136
137		if (kill(owner, 0) == 0 || errno != ESRCH) {
138			goto retry0;
139		}
140
141		if (unlink(lock_name) != 0) {
142			goto retry0;
143		}
144
145		goto begin;
146	}
147
148	snprintf(pid, sizeof(pid), "%10d\n", getpid());
149	if (write(lock_fd, pid, sizeof(pid) - 1) != sizeof(pid) - 1) {
150		ERROR("Failed to write PID file\n");
151		unlink(lock_name);
152		close(lock_fd);
153		return false;
154	}
155
156	close(lock_fd);
157
158	#ifdef __linux__
159	/* Bind to abstract socket */
160	addr.sun_path[0] = '\0';
161	snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, SOCKET_FMT,
162	         xserver.display);
163	if ((xserver.abstract_fd = open_socket(&addr)) < 0) {
164		goto retry1;
165	}
166	#else
167	xserver.abstract_fd=-1;
168	#endif
169
170	/* Bind to unix socket */
171	mkdir(SOCKET_DIR, 0777);
172	snprintf(addr.sun_path, sizeof(addr.sun_path), SOCKET_FMT, xserver.display);
173	if ((xserver.unix_fd = open_socket(&addr)) < 0) {
174		goto retry2;
175	}
176
177	snprintf(xserver.display_name, sizeof(xserver.display_name), ":%d",
178	         xserver.display);
179	setenv("DISPLAY", xserver.display_name, true);
180
181	return true;
182}
183
184static void
185close_display(void)
186{
187	char path[64];
188
189	#ifdef __linux__
190	close(xserver.abstract_fd);
191	#endif
192	close(xserver.unix_fd);
193
194	snprintf(path, sizeof(path), SOCKET_FMT, xserver.display);
195	unlink(path);
196	snprintf(path, sizeof(path), LOCK_FMT, xserver.display);
197	unlink(path);
198
199	unsetenv("DISPLAY");
200}
201
202static int
203handle_usr1(int signal_number, void *data)
204{
205	if (xwm_initialize(xserver.wm_fd)) {
206		xserver.xwm_initialized = true;
207	} else {
208		ERROR("Failed to initialize X window manager\n");
209		/* XXX: How do we handle this case? */
210	}
211
212	wl_event_source_remove(xserver.usr1_source);
213
214	return 0;
215}
216
217static void
218handle_client_destroy(struct wl_listener *listener, void *data)
219{
220	swc_xserver.client = NULL;
221}
222
223static struct wl_listener client_destroy_listener = {
224    .notify = handle_client_destroy,
225};
226
227bool
228xserver_initialize(void)
229{
230	int wl[2], wm[2];
231
232	/* Open an X display */
233	if (!open_display()) {
234		ERROR("Failed to get X lockfile and sockets\n");
235		goto error0;
236	}
237
238	xserver.usr1_source =
239	    wl_event_loop_add_signal(swc.event_loop, SIGUSR1, &handle_usr1, NULL);
240
241	if (!xserver.usr1_source) {
242		ERROR("Failed to create SIGUSR1 event source\n");
243		goto error1;
244	}
245
246	/* Open a socket for the Wayland connection from Xwayland. */
247	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wl) != 0) {
248		ERROR("Failed to create socketpair: %s\n", strerror(errno));
249		goto error2;
250	}
251
252	/* Open a socket for the X connection to Xwayland. */
253	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wm) != 0) {
254		ERROR("Failed to create socketpair: %s\n", strerror(errno));
255		goto error3;
256	}
257
258	if (!(swc_xserver.client = wl_client_create(swc.display, wl[0]))) {
259		goto error4;
260	}
261
262	wl_client_add_destroy_listener(swc_xserver.client,
263	                               &client_destroy_listener);
264	xserver.wm_fd = wm[0];
265
266	/* Start the X server */
267	switch (fork()) {
268	case 0: {
269		int fds[] = {wl[1], wm[1], xserver.abstract_fd, xserver.unix_fd};
270		char strings[ARRAY_LENGTH(fds)][16];
271		unsigned index;
272		struct sigaction action = {.sa_handler = SIG_IGN};
273
274		/* Unset the FD_CLOEXEC flag on the FDs that will get passed to
275		 * Xwayland. */
276		for (index = 0; index < ARRAY_LENGTH(fds); ++index) {
277#if defined(__FreeBSD__)
278		  if (fds[index]==-1) continue;
279#endif
280			if (fcntl(fds[index], F_SETFD, 0) != 0) {
281				ERROR("fcntl() failed: %s\n", strerror(errno));
282				goto fail;
283			}
284
285			if (snprintf(strings[index], sizeof(strings[index]), "%d",
286			             fds[index]) >= sizeof(strings[index])) {
287				ERROR("FD is too large\n");
288				goto fail;
289			}
290		}
291
292		/* Ignore the USR1 signal so that Xwayland will send a USR1 signal to
293		 * the parent process (us) after it finishes initializing. See
294		 * Xserver(1) for more details. */
295		if (sigaction(SIGUSR1, &action, NULL) != 0) {
296			ERROR("Failed to set SIGUSR1 handler to SIG_IGN: %s\n",
297			      strerror(errno));
298			goto fail;
299		}
300
301		setenv("WAYLAND_SOCKET", strings[0], true);
302
303		#ifdef __linux__
304		execlp("Xwayland", "Xwayland", xserver.display_name, "-rootless",
305		       "-terminate", "-listen", strings[2], "-listen", strings[3],
306		       "-wm", strings[1], NULL);
307		#else
308		execlp("Xwayland", "Xwayland", xserver.display_name, "-rootless",
309		       "-terminate", "-listen", strings[3],
310		       "-wm", strings[1], NULL);
311		#endif
312
313	fail:
314		exit(EXIT_FAILURE);
315	}
316	case -1:
317		ERROR("fork() failed when trying to start X server: %s\n",
318		      strerror(errno));
319		goto error5;
320	}
321
322	close(wl[1]);
323	close(wm[1]);
324
325	return true;
326
327error5:
328	wl_client_destroy(swc_xserver.client);
329error4:
330	close(wm[1]);
331	close(wm[0]);
332error3:
333	close(wl[1]);
334	close(wl[0]);
335error2:
336	wl_event_source_remove(xserver.usr1_source);
337error1:
338	close_display();
339error0:
340	return false;
341}
342
343void
344xserver_finalize(void)
345{
346	if (xserver.xwm_initialized) {
347		xwm_finalize();
348	}
349	if (swc_xserver.client) {
350		wl_client_destroy(swc_xserver.client);
351	}
352	close_display();
353}