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}