swc

Unnamed repository; edit this file 'description' to name the repository.
git clone git://git.nihaljere.xyz/swc
Log | Files | Refs | README | LICENSE

launch.c (11624B)


      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 "protocol.h"
     29 #include "devmajor.h"
     30 
     31 #include <errno.h>
     32 #include <fcntl.h>
     33 #include <limits.h>
     34 #include <poll.h>
     35 #include <spawn.h>
     36 #include <stdbool.h>
     37 #include <stdio.h>
     38 #include <stdlib.h>
     39 #include <stdnoreturn.h>
     40 #include <string.h>
     41 #include <unistd.h>
     42 #include <signal.h>
     43 
     44 #include <sys/socket.h>
     45 #include <sys/stat.h>
     46 #include <sys/wait.h>
     47 #include <sys/ioctl.h>
     48 #include <sys/types.h>
     49 #ifndef minor
     50 #include <sys/sysmacros.h>
     51 #endif
     52 #ifdef __NetBSD__
     53 #include <dev/wscons/wsdisplay_usl_io.h>
     54 #else
     55 #include <linux/input.h>
     56 #include <linux/kd.h>
     57 #include <linux/vt.h>
     58 #endif
     59 #include <xf86drm.h>
     60 
     61 #define ARRAY_LENGTH(array) (sizeof(array) / sizeof(array)[0])
     62 
     63 static bool nflag;
     64 static int sigfd[2], sock[2];
     65 static int input_fds[128], num_input_fds;
     66 static int drm_fds[16], num_drm_fds;
     67 static int tty_fd;
     68 static bool active;
     69 
     70 static struct {
     71 	bool altered;
     72 	int vt;
     73 	long kb_mode;
     74 	long console_mode;
     75 } original_vt_state;
     76 
     77 static void cleanup(void);
     78 
     79 static noreturn void usage(const char *name)
     80 {
     81 	fprintf(stderr, "usage: %s [-n] [-t tty] [--] server [args...]\n", name);
     82 	exit(2);
     83 }
     84 
     85 static noreturn void __attribute__((format(printf, 1, 2)))
     86 die(const char *format, ...)
     87 {
     88 	va_list args;
     89 
     90 	va_start(args, format);
     91 	vfprintf(stderr, format, args);
     92 	va_end(args);
     93 
     94 	if (format[0] && format[strlen(format) - 1] == ':')
     95 		fprintf(stderr, " %s", strerror(errno));
     96 	fputc('\n', stderr);
     97 
     98 	cleanup();
     99 	exit(EXIT_FAILURE);
    100 }
    101 
    102 static void
    103 start_devices(void)
    104 {
    105 	int i;
    106 
    107 	for (i = 0; i < num_drm_fds; ++i) {
    108 		if (drmSetMaster(drm_fds[i]) < 0)
    109 			die("failed to set DRM master");
    110 	}
    111 }
    112 
    113 static void
    114 stop_devices(bool fatal)
    115 {
    116 	int i;
    117 
    118 	for (i = 0; i < num_drm_fds; ++i) {
    119 		if (drmDropMaster(drm_fds[i]) < 0 && fatal)
    120 			die("drmDropMaster:");
    121 	}
    122 	for (i = 0; i < num_input_fds; ++i) {
    123 #ifdef EVIOCREVOKE
    124 		if (ioctl(input_fds[i], EVIOCREVOKE, 0) < 0 && errno != ENODEV && fatal)
    125 			die("ioctl EVIOCREVOKE:");
    126 #endif
    127 		close(input_fds[i]);
    128 	}
    129 	num_input_fds = 0;
    130 }
    131 
    132 static void
    133 cleanup(void)
    134 {
    135 	struct vt_mode mode = {.mode = VT_AUTO};
    136 
    137 	if (!original_vt_state.altered)
    138 		return;
    139 
    140 	/* Cleanup VT */
    141 	ioctl(tty_fd, VT_SETMODE, &mode);
    142 	ioctl(tty_fd, KDSETMODE, original_vt_state.console_mode);
    143 	ioctl(tty_fd, KDSKBMODE, original_vt_state.kb_mode);
    144 
    145 	/* Stop devices before switching the VT to make sure we have released the DRM
    146 	 * device before the next session tries to claim it. */
    147 	stop_devices(false);
    148 	ioctl(tty_fd, VT_ACTIVATE, original_vt_state.vt);
    149 
    150 	kill(0, SIGTERM);
    151 }
    152 
    153 static void
    154 activate(void)
    155 {
    156 	struct swc_launch_event event = {.type = SWC_LAUNCH_EVENT_ACTIVATE};
    157 
    158 	start_devices();
    159 	send(sock[0], &event, sizeof(event), 0);
    160 	active = true;
    161 }
    162 
    163 static void
    164 deactivate(void)
    165 {
    166 	struct swc_launch_event event = {.type = SWC_LAUNCH_EVENT_DEACTIVATE};
    167 
    168 	send(sock[0], &event, sizeof(event), 0);
    169 	stop_devices(true);
    170 	active = false;
    171 }
    172 
    173 static void
    174 handle_signal(int sig)
    175 {
    176 	write(sigfd[1], (char[]){sig}, 1);
    177 }
    178 
    179 static void
    180 handle_socket_data(int socket)
    181 {
    182 	struct swc_launch_request request;
    183 	struct swc_launch_event response;
    184 	char path[PATH_MAX];
    185 	struct iovec request_iov[2] = {
    186 		{.iov_base = &request, .iov_len = sizeof(request)},
    187 		{.iov_base = path, .iov_len = sizeof(path)},
    188 	};
    189 	struct iovec response_iov[1] = {
    190 		{.iov_base = &response, .iov_len = sizeof(response)},
    191 	};
    192 	int fd = -1;
    193 	struct stat st;
    194 	ssize_t size;
    195 
    196 	size = receive_fd(socket, &fd, request_iov, 2);
    197 	if (size == -1 || size == 0 || size < sizeof(request))
    198 		return;
    199 	size -= sizeof(request);
    200 
    201 	response.type = SWC_LAUNCH_EVENT_RESPONSE;
    202 	response.serial = request.serial;
    203 
    204 	switch (request.type) {
    205 	case SWC_LAUNCH_REQUEST_OPEN_DEVICE:
    206 		if (size == 0 || path[size - 1] != '\0') {
    207 			fprintf(stderr, "path is not NULL terminated\n");
    208 			goto fail;
    209 		}
    210 		if ((request.flags & (O_ACCMODE|O_NONBLOCK|O_CLOEXEC)) != request.flags) {
    211 			fprintf(stderr, "invalid open flags\n");
    212 			goto fail;
    213 		}
    214 
    215 		fd = open(path, request.flags);
    216 		if (fd == -1) {
    217 			fprintf(stderr, "open %s: %s\n", path, strerror(errno));
    218 			goto fail;
    219 		}
    220 		if (fstat(fd, &st) == -1) {
    221 			fprintf(stderr, "stat %s: %s\n", path, strerror(errno));
    222 			goto fail;
    223 		}
    224 
    225 		if (device_is_input(st.st_rdev)) {
    226 			if (!active)
    227 				goto fail;
    228 			if (num_input_fds == ARRAY_LENGTH(input_fds)) {
    229 				fprintf(stderr, "too many input devices opened\n");
    230 				goto fail;
    231 			}
    232 			input_fds[num_input_fds++] = fd;
    233 		} else if (device_is_drm(st.st_rdev)) {
    234 			if (num_drm_fds == ARRAY_LENGTH(drm_fds)) {
    235 				fprintf(stderr, "too many DRM devices opened\n");
    236 				goto fail;
    237 			}
    238 			drm_fds[num_drm_fds++] = fd;
    239 		} else {
    240 			fprintf(stderr, "requested fd is not a DRM or input device\n");
    241 			goto fail;
    242 		}
    243 		break;
    244 	case SWC_LAUNCH_REQUEST_ACTIVATE_VT:
    245 		if (!active)
    246 			goto fail;
    247 
    248 		if (ioctl(tty_fd, VT_ACTIVATE, request.vt) == -1)
    249 			fprintf(stderr, "failed to activate VT %d: %s\n", request.vt, strerror(errno));
    250 		break;
    251 	default:
    252 		fprintf(stderr, "unknown request %u\n", request.type);
    253 		goto fail;
    254 	}
    255 
    256 	response.success = true;
    257 	goto done;
    258 
    259 fail:
    260 	response.success = false;
    261 	if (fd != -1)
    262 		close(fd);
    263 	fd = -1;
    264 done:
    265 	send_fd(socket, fd, response_iov, 1);
    266 }
    267 
    268 static void
    269 find_vt(char *vt, size_t size)
    270 {
    271 #ifdef __NetBSD__
    272 	if (snprintf(vt, size, "/dev/ttyE1") >= size)
    273 		die("VT number is too large");
    274 #else
    275 	char *vtnr;
    276 	int tty0_fd, vt_num;
    277 
    278 	/* If we are running from an existing X or wayland session, always open a new
    279 	 * VT instead of using the current one. */
    280 	if (getenv("DISPLAY") || getenv("WAYLAND_DISPLAY") || !(vtnr = getenv("XDG_VTNR"))) {
    281 		tty0_fd = open("/dev/tty0", O_RDWR);
    282 		if (tty0_fd == -1)
    283 			die("open /dev/tty0:");
    284 		if (ioctl(tty0_fd, VT_OPENQRY, &vt_num) != 0)
    285 			die("VT open query failed:");
    286 		close(tty0_fd);
    287 		if (snprintf(vt, size, "/dev/tty%d", vt_num) >= size)
    288 			die("VT number is too large");
    289 	} else {
    290 		if (snprintf(vt, size, "/dev/tty%s", vtnr) >= size)
    291 			die("XDG_VTNR is too long");
    292 	}
    293 #endif
    294 }
    295 
    296 static int
    297 open_tty(const char *tty_name)
    298 {
    299 	char *stdin_tty;
    300 	int fd;
    301 
    302 	/* Check if we are already running on the desired VT */
    303 	if ((stdin_tty = ttyname(STDIN_FILENO)) && strcmp(tty_name, stdin_tty) == 0)
    304 		return STDIN_FILENO;
    305 
    306 	fd = open(tty_name, O_RDWR | O_NOCTTY);
    307 	if (fd < 0)
    308 		die("open %s:", tty_name);
    309 
    310 	return fd;
    311 }
    312 
    313 static void
    314 setup_tty(int fd)
    315 {
    316 	struct stat st;
    317 	int vt;
    318 	struct vt_stat state;
    319 	struct vt_mode mode = {
    320 		.mode = VT_PROCESS,
    321 		.relsig = SIGUSR1,
    322 		.acqsig = SIGUSR2
    323 	};
    324 
    325 	if (fstat(fd, &st) == -1)
    326 		die("failed to stat TTY fd:");
    327 	vt = minor(st.st_rdev);
    328 
    329 	if (!device_is_tty(st.st_rdev) || vt == 0)
    330 		die("not a valid VT");
    331 
    332 	if (ioctl(fd, VT_GETSTATE, &state) == -1)
    333 		die("failed to get the current VT state:");
    334 	original_vt_state.vt = state.v_active;
    335 #ifdef KDGETMODE
    336 	if (ioctl(fd, KDGKBMODE, &original_vt_state.kb_mode))
    337 		die("failed to get keyboard mode:");
    338 	if (ioctl(fd, KDGETMODE, &original_vt_state.console_mode))
    339 		die("failed to get console mode:");
    340 #else
    341 	original_vt_state.kb_mode = K_XLATE;
    342 	original_vt_state.console_mode = KD_TEXT;
    343 #endif
    344 
    345 #ifdef K_OFF
    346 	if (ioctl(fd, KDSKBMODE, K_OFF) == -1)
    347 		die("failed to set keyboard mode to K_OFF:");
    348 #endif
    349 	if (ioctl(fd, KDSETMODE, KD_GRAPHICS) == -1) {
    350 		perror("failed to set console mode to KD_GRAPHICS");
    351 		goto error0;
    352 	}
    353 	if (ioctl(fd, VT_SETMODE, &mode) == -1) {
    354 		perror("failed to set VT mode");
    355 		goto error1;
    356 	}
    357 
    358 	if (vt == original_vt_state.vt) {
    359 		activate();
    360 	} else if (!nflag) {
    361 		if (ioctl(fd, VT_ACTIVATE, vt) == -1) {
    362 			perror("failed to activate VT");
    363 			goto error2;
    364 		}
    365 
    366 		if (ioctl(fd, VT_WAITACTIVE, vt) == -1) {
    367 			perror("failed to wait for VT to become active");
    368 			goto error2;
    369 		}
    370 	}
    371 
    372 	original_vt_state.altered = true;
    373 
    374 	return;
    375 
    376 error2:
    377 	mode = (struct vt_mode){.mode = VT_AUTO };
    378 	ioctl(fd, VT_SETMODE, &mode);
    379 error1:
    380 	ioctl(fd, KDSETMODE, original_vt_state.console_mode);
    381 error0:
    382 	ioctl(fd, KDSKBMODE, original_vt_state.kb_mode);
    383 	exit(EXIT_FAILURE);
    384 }
    385 
    386 static void
    387 run(int fd) {
    388 	struct pollfd fds[] = {
    389 		{.fd = fd, .events = POLLIN},
    390 		{.fd = sigfd[0], .events = POLLIN},
    391 	};
    392 	int status;
    393 	char sig;
    394 
    395 	for (;;) {
    396 		if (poll(fds, ARRAY_LENGTH(fds), -1) < 0) {
    397 			if (errno == EINTR)
    398 				continue;
    399 			die("poll:");
    400 		}
    401 		if (fds[0].revents)
    402 			handle_socket_data(fd);
    403 		if (fds[1].revents) {
    404 			if (read(sigfd[0], &sig, 1) <= 0)
    405 				continue;
    406 			switch (sig) {
    407 			case SIGCHLD:
    408 				wait(&status);
    409 				cleanup();
    410 				exit(WEXITSTATUS(status));
    411 			case SIGUSR1:
    412 				deactivate();
    413 				ioctl(tty_fd, VT_RELDISP, 1);
    414 				break;
    415 			case SIGUSR2:
    416 				ioctl(tty_fd, VT_RELDISP, VT_ACKACQ);
    417 				activate();
    418 				break;
    419 			}
    420 		}
    421 	}
    422 }
    423 
    424 int
    425 main(int argc, char *argv[])
    426 {
    427 	extern char **environ;
    428 	int option;
    429 	char *vt = NULL, buf[64];
    430 	struct sigaction action = {
    431 		.sa_handler = handle_signal,
    432 		.sa_flags = SA_RESTART,
    433 	};
    434 	sigset_t set;
    435 	pid_t pid;
    436 	posix_spawnattr_t attr;
    437 
    438 	while ((option = getopt(argc, argv, "nt:")) != -1) {
    439 		switch (option) {
    440 		case 'n':
    441 			nflag = true;
    442 			break;
    443 		case 't':
    444 			vt = optarg;
    445 			break;
    446 		default:
    447 			usage(argv[0]);
    448 		}
    449 	}
    450 
    451 	if (argc - optind < 1)
    452 		usage(argv[0]);
    453 
    454 	if (socketpair(AF_LOCAL, SOCK_SEQPACKET, 0, sock) == -1)
    455 		die("socketpair:");
    456 	if (fcntl(sock[0], F_SETFD, FD_CLOEXEC) == -1)
    457 		die("failed set CLOEXEC on socket:");
    458 
    459 	if (pipe2(sigfd, O_CLOEXEC) == -1)
    460 		die("pipe:");
    461 	if (sigaction(SIGCHLD, &action, NULL) == -1)
    462 		die("sigaction SIGCHLD:");
    463 	if (sigaction(SIGUSR1, &action, NULL) == -1)
    464 		die("sigaction SIGUSR1:");
    465 	if (sigaction(SIGUSR2, &action, NULL) == -1)
    466 		die("sigaction SIGUSR2:");
    467 
    468 	sigfillset(&set);
    469 	sigdelset(&set, SIGCHLD);
    470 	sigdelset(&set, SIGUSR1);
    471 	sigdelset(&set, SIGUSR2);
    472 	sigprocmask(SIG_SETMASK, &set, NULL);
    473 
    474 	if (!vt) {
    475 		find_vt(buf, sizeof(buf));
    476 		vt = buf;
    477 	}
    478 
    479 	fprintf(stderr, "running on %s\n", vt);
    480 	tty_fd = open_tty(vt);
    481 	setup_tty(tty_fd);
    482 
    483 	sprintf(buf, "%d", sock[1]);
    484 	setenv(SWC_LAUNCH_SOCKET_ENV, buf, 1);
    485 
    486 	if ((errno = posix_spawnattr_init(&attr)))
    487 		die("posix_spawnattr_init:");
    488 	if ((errno = posix_spawnattr_setflags(&attr, POSIX_SPAWN_RESETIDS|POSIX_SPAWN_SETSIGMASK)))
    489 		die("posix_spawnattr_setflags:");
    490 	sigemptyset(&set);
    491 	if ((errno = posix_spawnattr_setsigmask(&attr, &set)))
    492 		die("posix_spawnattr_setsigmask:");
    493 	if ((errno = posix_spawnp(&pid, argv[optind], NULL, &attr, argv + optind, environ)))
    494 		die("posix_spawnp %s:", argv[optind]);
    495 	posix_spawnattr_destroy(&attr);
    496 
    497 	close(sock[1]);
    498 	run(sock[0]);
    499 
    500 	return EXIT_SUCCESS;
    501 }