1/* swc: launch/launch.c
2 *
3 * Copyright (c) 2013, 2014, 2016 Michael Forney
4 *
5 * Based in part upon weston-launch.c from weston which is:
6 *
7 * Copyright © 2012 Benjamin Franzke
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 "devmajor.h"
29#include "protocol.h"
30
31#include <errno.h>
32#include <fcntl.h>
33#include <limits.h>
34#include <poll.h>
35#include <signal.h>
36#include <spawn.h>
37#include <stdbool.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <stdnoreturn.h>
41#include <string.h>
42#include <unistd.h>
43
44#include <sys/ioctl.h>
45#include <sys/socket.h>
46#include <sys/stat.h>
47#include <sys/types.h>
48#include <sys/wait.h>
49#ifdef __linux__
50#include <sys/sysmacros.h>
51#endif
52
53#if defined(__FreeBSD__)
54#include <sys/consio.h>
55#include <sys/kbio.h>
56#include <dev/evdev/input.h> /* needed for eviocgrab */
57#elif defined(__NetBSD__)
58#include <dev/wscons/wsdisplay_usl_io.h>
59
60#elif defined(__OpenBSD__)
61#include <dev/wscons/wsdisplay_usl_io.h>
62
63#elif defined(__linux__)
64#include <linux/input.h>
65#include <linux/kd.h>
66#include <linux/vt.h>
67
68#else
69#include <dev/wscons/wsdisplay_usl_io.h>
70#endif
71
72#include <xf86drm.h>
73
74#define ARRAY_LENGTH(array) (sizeof(array) / sizeof(array)[0])
75
76static void
77activate(void);
78static void
79deactivate(void);
80
81static bool nflag;
82static int sigfd[2], sock[2];
83static int input_fds[128], num_input_fds;
84static int drm_fds[16], num_drm_fds;
85static int tty_fd;
86static bool active;
87
88static struct {
89 bool altered;
90 int vt;
91 long kb_mode;
92 long console_mode;
93} original_vt_state;
94
95static void
96cleanup(void);
97
98static noreturn void
99usage(const char *name)
100{
101 fprintf(stderr, "usage: %s [-n] [-t tty] [--] server [args...]\n", name);
102 exit(2);
103}
104
105static noreturn void __attribute__((format(printf, 1, 2)))
106die(const char *format, ...)
107{
108 va_list args;
109
110 va_start(args, format);
111 vfprintf(stderr, format, args);
112 va_end(args);
113
114 if (format[0] && format[strlen(format) - 1] == ':') {
115 fprintf(stderr, " %s", strerror(errno));
116 }
117 fputc('\n', stderr);
118
119 cleanup();
120 exit(EXIT_FAILURE);
121}
122
123static void
124start_devices(void)
125{
126 int i;
127
128 for (i = 0; i < num_drm_fds; ++i) {
129 if (drmSetMaster(drm_fds[i]) < 0) {
130 die("failed to set DRM master");
131 }
132 }
133}
134
135static void
136stop_devices(bool fatal)
137{
138 int i;
139
140 for (i = 0; i < num_drm_fds; ++i) {
141 if (drmDropMaster(drm_fds[i]) < 0 && fatal) {
142 die("drmDropMaster:");
143 }
144 }
145 for (i = 0; i < num_input_fds; ++i) {
146#ifdef EVIOCREVOKE
147 if (ioctl(input_fds[i], EVIOCREVOKE, 0) < 0 && errno != ENODEV &&
148 fatal) {
149 die("ioctl EVIOCREVOKE:");
150 }
151#endif
152 close(input_fds[i]);
153 }
154 num_input_fds = 0;
155}
156
157static void
158cleanup(void)
159{
160#ifndef __OpenBSD__
161 struct vt_mode mode = {.mode = VT_AUTO};
162#endif
163
164 if (!original_vt_state.altered) {
165 return;
166 }
167
168 /* Stop devices before switching the VT to make sure we have released the
169 * DRM device before the next session tries to claim it. */
170 stop_devices(false);
171
172 /* Cleanup VT */
173#ifndef __OpenBSD__
174 ioctl(tty_fd, VT_SETMODE, &mode);
175 ioctl(tty_fd, KDSKBMODE, original_vt_state.kb_mode);
176#endif
177 ioctl(tty_fd, KDSETMODE, original_vt_state.console_mode);
178
179#ifndef __OpenBSD__
180 ioctl(tty_fd, VT_ACTIVATE, original_vt_state.vt);
181#endif
182
183 kill(0, SIGTERM);
184}
185
186static void
187activate(void)
188{
189 struct swc_launch_event event = {.type = SWC_LAUNCH_EVENT_ACTIVATE};
190
191 start_devices();
192 send(sock[0], &event, sizeof(event), 0);
193 active = true;
194}
195
196static void
197deactivate(void)
198{
199 struct swc_launch_event event = {.type = SWC_LAUNCH_EVENT_DEACTIVATE};
200
201 send(sock[0], &event, sizeof(event), 0);
202 stop_devices(true);
203 active = false;
204}
205
206static void
207handle_signal(int sig)
208{
209 write(sigfd[1], (char[]){sig}, 1);
210}
211
212static void
213handle_socket_data(int socket)
214{
215 struct swc_launch_request request;
216 struct swc_launch_event response;
217 char path[PATH_MAX];
218 struct iovec request_iov[2] = {
219 {.iov_base = &request, .iov_len = sizeof(request)},
220 {.iov_base = path, .iov_len = sizeof(path)},
221 };
222 struct iovec response_iov[1] = {
223 {.iov_base = &response, .iov_len = sizeof(response)},
224 };
225 int fd = -1;
226 struct stat st;
227 ssize_t size;
228
229 size = receive_fd(socket, &fd, request_iov, 2);
230 if (size == -1 || size == 0 || size < sizeof(request)) {
231 return;
232 }
233 size -= sizeof(request);
234
235 response.type = SWC_LAUNCH_EVENT_RESPONSE;
236 response.serial = request.serial;
237
238 switch (request.type) {
239 case SWC_LAUNCH_REQUEST_OPEN_DEVICE:
240 if (size == 0 || path[size - 1] != '\0') {
241 fprintf(stderr, "path is not NULL terminated\n");
242 goto fail;
243 }
244 if ((request.flags & (O_ACCMODE | O_NONBLOCK | O_CLOEXEC)) !=
245 request.flags) {
246 fprintf(stderr, "invalid open flags\n");
247 goto fail;
248 }
249
250 fd = open(path, request.flags | O_CLOEXEC);
251 if (fd == -1) {
252 fprintf(stderr, "open %s: %s\n", path, strerror(errno));
253 goto fail;
254 }
255 if (fstat(fd, &st) == -1) {
256 fprintf(stderr, "stat %s: %s\n", path, strerror(errno));
257 goto fail;
258 }
259
260 if (device_is_input(st.st_rdev)) {
261 if (!active) {
262 goto fail;
263 }
264 if (num_input_fds == ARRAY_LENGTH(input_fds)) {
265 fprintf(stderr, "too many input devices opened\n");
266 goto fail;
267 }
268 #ifdef __FreeBSD__
269 /* this (EVIOCGRAB) is the only way to
270 * prevent the TTY from recieving
271 * input from the neuswc session.
272 */
273 if (ioctl(fd, EVIOCGRAB, (void*)1) == -1)
274 {
275 fprintf(stderr, "EVIOCGRAB %s: %s", path, strerror(errno));
276 goto fail;
277 }
278 #endif
279 input_fds[num_input_fds++] = fd;
280 } else if (device_is_drm(st.st_rdev)) {
281 if (num_drm_fds == ARRAY_LENGTH(drm_fds)) {
282 fprintf(stderr, "too many DRM devices opened\n");
283 goto fail;
284 }
285 drm_fds[num_drm_fds++] = fd;
286 } else {
287 fprintf(stderr, "requested fd is not a DRM or input device\n");
288 goto fail;
289 }
290 break;
291 case SWC_LAUNCH_REQUEST_ACTIVATE_VT:
292 if (!active) {
293 goto fail;
294 }
295
296 if (ioctl(tty_fd, VT_ACTIVATE, request.vt) == -1) {
297 fprintf(stderr, "failed to activate VT %d: %s\n", request.vt,
298 strerror(errno));
299 }
300 break;
301 default:
302 fprintf(stderr, "unknown request %u\n", request.type);
303 goto fail;
304 }
305
306 response.success = true;
307 goto done;
308
309fail:
310 response.success = false;
311 if (fd != -1) {
312 close(fd);
313 }
314 fd = -1;
315done:
316 send_fd(socket, fd, response_iov, 1);
317}
318
319static void
320find_vt(char *vt, size_t size)
321{
322#if defined(__NetBSD__)
323 if (snprintf(vt, size, "/dev/ttyE1") >= size) {
324 die("VT number is too large");
325 }
326#elif defined(__OpenBSD__)
327 const char *tty;
328 tty = ttyname(STDIN_FILENO);
329 if (!tty || strncmp(tty, "/dev/ttyC", 8) != 0) {
330 die("must be run from wscons VT (/dev/ttyC*)");
331 }
332 if (snprintf(vt, size, "%s", tty) >= size) {
333 die("VT number is too large");
334 }
335#else
336 char *vtnr;
337 int tty0_fd, vt_num;
338
339 /* If we are running from an existing X or wayland session, always open a
340 * new VT instead of using the current one. */
341 if (getenv("DISPLAY") || getenv("WAYLAND_DISPLAY") ||
342 !(vtnr = getenv("XDG_VTNR"))) {
343#if defined(__FreeBSD__)
344 tty0_fd=open("/dev/ttyv0", O_RDWR | O_CLOEXEC);
345 #else
346 tty0_fd = open("/dev/tty0", O_RDWR | O_CLOEXEC);
347 #endif
348 if (tty0_fd == -1) {
349 die("open /dev/tty0:");
350 }
351 if (ioctl(tty0_fd, VT_OPENQRY, &vt_num) != 0) {
352 die("VT open query failed:");
353 }
354 close(tty0_fd);
355#if defined(__FreeBSD__)
356 if (snprintf(vt, size, "/dev/ttyv%d", vt_num - 1) >= size) {
357 #else
358 if (snprintf(vt, size, "/dev/tty%d", vt_num) >= size) {
359
360 #endif
361 die("VT number is too large");
362 }
363 } else {
364#if defined(__FreeBSD__)
365 int n=atoi(vtnr);
366 if (snprintf(vt, size, "/dev/ttyv%d", n - 1) >= size) {
367 #else
368 if (snprintf(vt, size, "/dev/tty%s", vtnr) >= size) {
369 #endif
370 die("XDG_VTNR is too long");
371 }
372 }
373#endif
374}
375
376static int
377open_tty(const char *tty_name)
378{
379 char *stdin_tty;
380 int fd;
381
382 /* Check if we are already running on the desired VT */
383 if ((stdin_tty = ttyname(STDIN_FILENO)) &&
384 strcmp(tty_name, stdin_tty) == 0) {
385 return STDIN_FILENO;
386 }
387
388 fd = open(tty_name, O_RDWR | O_NOCTTY | O_CLOEXEC);
389 if (fd < 0) {
390 die("open %s:", tty_name);
391 }
392
393 return fd;
394}
395
396static void
397setup_tty(int fd)
398{
399 struct stat st;
400 int vt;
401#if !defined (__OpenBSD__) && !defined(__FreeBSD__)
402 struct vt_stat state;
403 #endif
404#if !defined(__OpenBSD__)
405 struct vt_mode mode = {
406 .mode = VT_PROCESS, .relsig = SIGUSR1, .acqsig = SIGUSR2};
407#endif
408
409 if (fstat(fd, &st) == -1) {
410 die("failed to stat TTY fd:");
411 }
412 vt = minor(st.st_rdev);
413
414#ifdef __OpenBSD__
415 if (!device_is_tty(st.st_rdev)) {
416 die("not a valid VT");
417 }
418#else
419 if (!device_is_tty(st.st_rdev) || vt == 0) {
420 die("not a valid VT");
421 }
422#endif
423
424#if defined(__OpenBSD__)
425 /* OpenBSD wscons has no VT_GETSTATE */
426 original_vt_state.vt=vt;
427#elif defined(__FreeBSD__)
428 {
429 int vt_num;
430 if (ioctl(fd, VT_GETACTIVE, &vt_num) == -1)
431 {
432 die("failed to get current VT state:");
433 }
434 original_vt_state.vt=vt_num;
435 }
436#else
437 if (ioctl(fd, VT_GETSTATE, &state) == -1) {
438 die("failed to get the current VT state:");
439 }
440#endif
441
442#if !defined (__OpenBSD__) && !defined(__FreeBSD__)
443 original_vt_state.vt = state.v_active;
444#else
445 original_vt_state.vt = vt;
446#endif
447
448#ifdef KDGETMODE
449 if (ioctl(fd, KDGKBMODE, &original_vt_state.kb_mode)) {
450 die("failed to get keyboard mode:");
451 }
452 if (ioctl(fd, KDGETMODE, &original_vt_state.console_mode)) {
453 die("failed to get console mode:");
454 }
455#else
456 original_vt_state.kb_mode = K_XLATE;
457 original_vt_state.console_mode = KD_TEXT;
458#endif
459
460#ifdef K_OFF
461 if (ioctl(fd, KDSKBMODE, K_OFF) == -1) {
462 die("failed to set keyboard mode to K_OFF:");
463 }
464#elif defined(__FreeBSD__)
465 if (ioctl(fd, KDSKBMODE, K_CODE) == -1) {
466 die("failed to set keyboard mode to K_RAW:");
467 }
468#endif
469 if (ioctl(fd, KDSETMODE, KD_GRAPHICS) == -1) {
470 perror("KDSETMODE KD_GRAPHICS");
471 goto error0;
472 }
473
474#if !defined (__OpenBSD__) && !defined (__FreeBSD__)
475 if (ioctl(fd, VT_SETMODE, &mode) == -1) {
476 perror("failed to set VT mode");
477 goto error1;
478 }
479#endif
480
481#ifdef __OpenBSD__
482 /* OpenBSD wscons has no VT_PROCESS mode; just cont. on current ttyC* */
483 activate();
484#else
485 if (vt == original_vt_state.vt) {
486 activate();
487 } else if (!nflag) {
488 if (ioctl(fd, VT_ACTIVATE, vt) == -1) {
489 perror("failed to activate VT");
490 goto error2;
491 }
492
493 if (ioctl(fd, VT_WAITACTIVE, vt) == -1) {
494 perror("failed to wait for VT to become active");
495 goto error2;
496 }
497 }
498#endif
499 original_vt_state.altered = true;
500
501 return;
502
503#ifndef __OpenBSD__
504error2:
505 mode = (struct vt_mode){.mode = VT_AUTO};
506 ioctl(fd, VT_SETMODE, &mode);
507error1:
508 ioctl(fd, KDSKBMODE, original_vt_state.kb_mode);
509#endif
510error0:
511 ioctl(fd, KDSETMODE, original_vt_state.console_mode);
512 exit(EXIT_FAILURE);
513}
514
515static void
516run(int fd)
517{
518 struct pollfd fds[] = {
519 {.fd = fd, .events = POLLIN},
520 {.fd = sigfd[0], .events = POLLIN},
521 };
522 int status;
523 char sig;
524
525 for (;;) {
526 if (poll(fds, ARRAY_LENGTH(fds), -1) < 0) {
527 if (errno == EINTR) {
528 continue;
529 }
530 die("poll:");
531 }
532 if (fds[0].revents) {
533 handle_socket_data(fd);
534 }
535 if (fds[1].revents) {
536 if (read(sigfd[0], &sig, 1) <= 0) {
537 continue;
538 }
539 switch (sig) {
540 case SIGCHLD:
541 wait(&status);
542 cleanup();
543 exit(WEXITSTATUS(status));
544 case SIGUSR1:
545 deactivate();
546 ioctl(tty_fd, VT_RELDISP, 1);
547 break;
548 case SIGUSR2:
549 ioctl(tty_fd, VT_RELDISP, VT_ACKACQ);
550 activate();
551 break;
552 }
553 }
554 }
555}
556
557int
558main(int argc, char *argv[])
559{
560 extern char **environ;
561 int option;
562 char *vt = NULL, buf[64];
563 struct sigaction action = {
564 .sa_handler = handle_signal,
565 .sa_flags = SA_RESTART,
566 };
567 sigset_t set;
568 pid_t pid;
569 posix_spawnattr_t attr;
570
571 while ((option = getopt(argc, argv, "nt:")) != -1) {
572 switch (option) {
573 case 'n':
574 nflag = true;
575 break;
576 case 't':
577 vt = optarg;
578 break;
579 default:
580 usage(argv[0]);
581 }
582 }
583
584 if (argc - optind < 1) {
585 usage(argv[0]);
586 }
587
588
589 if (socketpair(AF_LOCAL,
590 #ifdef __linux__
591 SOCK_SEQPACKET,
592 #else
593 SOCK_STREAM,
594 #endif
595 0, sock) == -1) {
596 die("socketpair:");
597 }
598 if (fcntl(sock[0], F_SETFD, FD_CLOEXEC) == -1) {
599 die("failed set CLOEXEC on socket:");
600 }
601
602 if (pipe2(sigfd, O_CLOEXEC) == -1) {
603 die("pipe:");
604 }
605 if (sigaction(SIGCHLD, &action, NULL) == -1) {
606 die("sigaction SIGCHLD:");
607 }
608 if (sigaction(SIGUSR1, &action, NULL) == -1) {
609 die("sigaction SIGUSR1:");
610 }
611 if (sigaction(SIGUSR2, &action, NULL) == -1) {
612 die("sigaction SIGUSR2:");
613 }
614
615 sigfillset(&set);
616 sigdelset(&set, SIGCHLD);
617 sigdelset(&set, SIGUSR1);
618 sigdelset(&set, SIGUSR2);
619 sigprocmask(SIG_SETMASK, &set, NULL);
620
621 if (!vt) {
622 find_vt(buf, sizeof(buf));
623 vt = buf;
624 }
625
626 fprintf(stderr, "running on %s\n", vt);
627 tty_fd = open_tty(vt);
628 setup_tty(tty_fd);
629
630 sprintf(buf, "%d", sock[1]);
631 setenv(SWC_LAUNCH_SOCKET_ENV, buf, 1);
632
633 /* make sure XDG_RUNTIME_DIR is set */
634 if (!getenv("XDG_RUNTIME_DIR")) {
635 uid_t uid = getuid();
636 snprintf(buf, sizeof(buf), "/tmp/XDG_RUNTIME_DIR_%d", uid);
637 if (mkdir(buf, 0700) == -1 && errno != EEXIST) {
638 die("mkdir %s:", buf);
639 }
640 setenv("XDG_RUNTIME_DIR", buf, 1);
641 fprintf(stderr, "set XDG_RUNTIME_DIR=%s\n", buf);
642 }
643
644 if ((errno = posix_spawnattr_init(&attr))) {
645 die("posix_spawnattr_init:");
646 }
647 if ((errno = posix_spawnattr_setflags(&attr, POSIX_SPAWN_RESETIDS |
648 POSIX_SPAWN_SETSIGMASK))) {
649 die("posix_spawnattr_setflags:");
650 }
651 sigemptyset(&set);
652 if ((errno = posix_spawnattr_setsigmask(&attr, &set))) {
653 die("posix_spawnattr_setsigmask:");
654 }
655 if ((errno = posix_spawnp(&pid, argv[optind], NULL, &attr, argv + optind,
656 environ))) {
657 die("posix_spawnp %s:", argv[optind]);
658 }
659 posix_spawnattr_destroy(&attr);
660
661 close(sock[1]);
662 run(sock[0]);
663
664 return EXIT_SUCCESS;
665}