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