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