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