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}