main shrub/neuswc / launch / launch.c
  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}