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#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}