summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configure.ac6
-rw-r--r--deps/Makefile.am4
-rw-r--r--deps/vts/test-hang.c83
-rw-r--r--deps/vts/test.c82
-rw-r--r--deps/vts/vts.c1716
-rw-r--r--deps/vts/vts.h158
6 files changed, 2049 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac
index c5d56609..36d286d1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1093,6 +1093,8 @@ AS_IF([test "x$enable_kms_egl_platform" = "xyes"],
AC_DEFINE_UNQUOTED([COGL_GBM_MICRO], [$GBM_MICRO], [The micro version for libgbm])
COGL_DEFINES_SYMBOLS="$COGL_DEFINES_SYMBOLS COGL_HAS_EGL_PLATFORM_KMS_SUPPORT"
+
+ VTS_PKG_REQUIRES="libsystemd-daemon libsystemd-journal libsystemd-login libudev libdrm"
])
AM_CONDITIONAL(SUPPORT_EGL_PLATFORM_KMS,
[test "x$enable_kms_egl_platform" = "xyes"])
@@ -1250,6 +1252,9 @@ AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,
[The prefix for our gettext translation domains.])
AS_ALL_LINGUAS
+AS_IF([test "x$enable_kms_egl_platform" = "xyes"],
+ [PKG_CHECK_MODULES(VTS_DEP, [$VTS_PKG_REQUIRES])]
+)
AC_SUBST(COGL_PKG_REQUIRES)
if test -n "$COGL_PKG_REQUIRES"; then
@@ -1442,6 +1447,7 @@ deps/Makefile
deps/glib/Makefile
deps/gmodule/Makefile
deps/gmodule/gmoduleconf.h
+deps/vts/Makefile
test-fixtures/Makefile
cogl/Makefile
cogl/cogl2.pc
diff --git a/deps/Makefile.am b/deps/Makefile.am
index 2cd3cb51..0a9a4fba 100644
--- a/deps/Makefile.am
+++ b/deps/Makefile.am
@@ -3,3 +3,7 @@ SUBDIRS =
if !USE_GLIB
SUBDIRS += glib gmodule
endif
+
+if SUPPORT_EGL_PLATFORM_KMS
+SUBDIRS += vts
+endif
diff --git a/deps/vts/test-hang.c b/deps/vts/test-hang.c
new file mode 100644
index 00000000..5568b028
--- /dev/null
+++ b/deps/vts/test-hang.c
@@ -0,0 +1,83 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <poll.h>
+#include <errno.h>
+
+#include <vts.h>
+
+static void
+on_enter (VTSClient *client, void *user_data)
+{
+ printf ("VT Enter\n");
+}
+
+static void
+on_leave (VTSClient *client, void *user_data)
+{
+ printf ("VT Leave\n");
+ printf ("Hang...\n");
+ while (1)
+ sleep (5);
+}
+
+static void
+run_client (VTSClient *client)
+{
+ struct pollfd pollfd;
+
+ printf ("Run Client\n");
+
+ vts_client_set_vt_enter_callback (client, on_enter, NULL);
+ vts_client_set_vt_leave_callback (client, on_leave, NULL);
+
+ pollfd.fd = vts_client_get_poll_fd (client);
+ pollfd.events = POLLIN;
+
+ while (1)
+ {
+ int n;
+ VTSError *error = NULL;
+
+ do {
+ n = poll (&pollfd, 1, -1);
+ } while (n < 0 && errno == EINTR);
+
+ if (n < 0)
+ fprintf (stderr, "epoll_wait failed: %m");
+
+ printf ("client dispatch\n");
+ if (n)
+ {
+ if (!vts_client_dispatch (client, &error))
+ {
+ fprintf (stderr, "Failed to dispatch vts events: %s\n",
+ error->message);
+ exit (1);
+ }
+ }
+ }
+}
+
+int
+main (int argc, char **argv)
+{
+ VTSServer *server = vts_server_new ();
+ VTSClient *client;
+ VTSError *error = NULL;
+
+ vts_server_set_pam_enabled (server, 1);
+ vts_server_set_kernel_key_bindings_enabled (server, 1);
+
+ client = vts_server_run (server, &error);
+
+ if (error)
+ {
+ fprintf (stderr, "Failed to run server: %s\n", error->message);
+ vts_error_free (error);
+ return EXIT_FAILURE;
+ }
+
+ if (client)
+ run_client (client);
+}
diff --git a/deps/vts/test.c b/deps/vts/test.c
new file mode 100644
index 00000000..71173bcb
--- /dev/null
+++ b/deps/vts/test.c
@@ -0,0 +1,82 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <poll.h>
+#include <errno.h>
+
+#include <vts.h>
+
+static void
+on_enter (VTSClient *client, void *user_data)
+{
+ printf ("VT Enter\n");
+}
+
+static void
+on_leave (VTSClient *client, void *user_data)
+{
+ printf ("VT Leave\n");
+
+ vts_client_accept_vt_leave (client, NULL);
+}
+
+static void
+run_client (VTSClient *client)
+{
+ struct pollfd pollfd;
+
+ printf ("Run Client\n");
+
+ vts_client_set_vt_enter_callback (client, on_enter, NULL);
+ vts_client_set_vt_leave_callback (client, on_leave, NULL);
+
+ pollfd.fd = vts_client_get_poll_fd (client);
+ pollfd.events = POLLIN;
+
+ while (1)
+ {
+ int n;
+ VTSError *error = NULL;
+
+ do {
+ n = poll (&pollfd, 1, -1);
+ } while (n < 0 && errno == EINTR);
+
+ if (n < 0)
+ fprintf (stderr, "epoll_wait failed: %m");
+
+ printf ("client dispatch\n");
+ if (n)
+ {
+ if (!vts_client_dispatch (client, &error))
+ {
+ fprintf (stderr, "Failed to dispatch vts events: %s\n",
+ error->message);
+ exit (1);
+ }
+ }
+ }
+}
+
+int
+main (int argc, char **argv)
+{
+ VTSServer *server = vts_server_new ();
+ VTSClient *client;
+ VTSError *error = NULL;
+
+ vts_server_set_pam_enabled (server, 1);
+ vts_server_set_kernel_key_bindings_enabled (server, 1);
+
+ client = vts_server_run (server, &error);
+
+ if (error)
+ {
+ fprintf (stderr, "Failed to run server: %s\n", error->message);
+ vts_error_free (error);
+ return EXIT_FAILURE;
+ }
+
+ if (client)
+ run_client (client);
+}
diff --git a/deps/vts/vts.c b/deps/vts/vts.c
new file mode 100644
index 00000000..2b4f1d72
--- /dev/null
+++ b/deps/vts/vts.c
@@ -0,0 +1,1716 @@
+/*
+ * Copyright © 2012 Benjamin Franzke
+ * Copyright © 2010,2012,2013 Intel Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ * Benjamin Franzke
+ * Kristian Høgsberg
+ * Robert Bragg
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <error.h>
+#include <getopt.h>
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/epoll.h>
+#include <sys/signalfd.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+
+#include <termios.h>
+#include <linux/vt.h>
+#include <linux/major.h>
+#include <linux/kd.h>
+
+#include <pwd.h>
+#include <grp.h>
+#include <security/pam_appl.h>
+
+#include <xf86drm.h>
+
+#include <systemd/sd-login.h>
+#include <systemd/sd-journal.h>
+
+#include "vts.h"
+
+struct _VTSServer
+{
+ bool stop_child_enabled;
+ char *privileged_group;
+ bool require_systemd_session;
+ bool kernel_key_bindings_enabled;
+ bool preserve_environment;
+
+ bool pam_enabled;
+ struct pam_conv pc;
+ pam_handle_t *ph;
+ int pam_status;
+ bool pam_session_opened;
+
+ char *tty_name;
+ int tty;
+ int ttynr;
+ struct termios terminal_attributes;
+ bool saved_terminal_attributes;
+ int kb_mode;
+ bool saved_kb_mode;
+ bool handling_vt_leave;
+
+ bool watchdog_enabled;
+ uint64_t watchdog_timestamp;
+ uint64_t watchdog_timeout;
+
+ struct sigaction old_sigchld_action;
+ bool setup_sigchld_action;
+ sigset_t old_signal_mask;
+ bool masked_signals;
+ int signalfd;
+
+ int sock[2];
+ struct passwd *pw;
+
+ int epollfd;
+
+ pid_t child;
+ int child_wait_status;
+
+ bool quit;
+};
+
+typedef void (*VTSSwitchCallback) (VTSClient *client, void *user_data);
+
+struct _VTSClient
+{
+ int fd;
+ int epollfd;
+
+ VTSSwitchCallback vt_enter_callback;
+ void *vt_enter_user_data;
+
+ VTSSwitchCallback vt_leave_callback;
+ void *vt_leave_user_data;
+};
+
+typedef enum
+{
+ VTS_MESSAGE_ID_VT_SWITCH = 1,
+ VTS_MESSAGE_ID_VT_LEAVE_ACCEPT,
+ VTS_MESSAGE_ID_VT_LEAVE_DENY,
+ VTS_MESSAGE_ID_OPEN_INPUT,
+ VTS_MESSAGE_ID_SET_MASTER
+} VTSMessageID;
+
+typedef struct
+{
+ VTSMessageID id;
+} VTSMessage;
+
+typedef struct
+{
+ VTSMessage header;
+ int flags;
+ char path[0];
+} VTSOpenInputMessage;
+
+typedef struct
+{
+ VTSMessage header;
+ bool set_master;
+} VTSSetMasterMessage;
+
+typedef enum {
+ VTS_VT_SWITCH_DIRECTION_ENTER,
+ VTS_VT_SWITCH_DIRECTION_LEAVE
+} VTSVTSwitchDirection;
+
+typedef struct
+{
+ VTSMessage header;
+ VTSVTSwitchDirection direction;
+} VTSVTSwitchMessage;
+
+union cmsg_data { unsigned char b[4]; int fd; };
+
+#define vts_debug(x, a...) \
+ fprintf (stderr, "[DEBUG] " x "\n", ##a)
+// sd_journal_print (LOG_DEBUG, x, ##a)
+
+#define vts_warning(x, a...) \
+ fprintf (stderr, "[WARNING] " x "\n", ##a)
+// sd_journal_print (LOG_WARNING, x, ##a)
+
+static void *
+xmalloc (size_t size)
+{
+ void *ptr = malloc (size);
+ if (!ptr)
+ abort ();
+ return ptr;
+}
+
+static void *
+xmalloc0 (size_t size)
+{
+ void *ptr = xmalloc (size);
+ memset (ptr, 0, size);
+ return ptr;
+}
+
+#define xnew0(TYPE, COUNT) xmalloc0 (sizeof(TYPE) * COUNT);
+
+static char *
+xstrdup (const char *str)
+{
+ char *ret;
+ if (str)
+ {
+ ret = strdup (str);
+ if (!ret)
+ abort ();
+ }
+ else
+ ret = NULL;
+ return ret;
+}
+
+static void
+set_error (VTSError **error,
+ int status,
+ const char *format,
+ ...)
+{
+ va_list args;
+ VTSError *err;
+
+ va_start (args, format);
+
+ if (error == NULL)
+ {
+ //vts_warning (format, args);
+ exit (EXIT_FAILURE);
+ }
+
+ if (*error != NULL)
+ {
+ vts_warning ("Spurious attempt to overwrite error message:");
+ //vts_warning (format, args);
+ return;
+ }
+
+ err = xnew0 (VTSError, 1);
+ err->status = status;
+ if (vasprintf (&err->message, format, args) < 0)
+ err->message = NULL;
+ *error = err;
+
+ va_end (args);
+}
+
+#define set_error_from_errno(error, format, a...) \
+ set_error (error, errno, "%s: " format, strerror(errno), ##a)
+
+void
+vts_error_free (VTSError *error)
+{
+ free (error->message);
+ free (error);
+}
+
+uint64_t
+get_time (void)
+{
+ struct timespec ts;
+
+ clock_gettime (CLOCK_MONOTONIC, &ts);
+
+ return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
+}
+
+VTSServer *
+vts_server_new (void)
+{
+ VTSServer *server = xnew0 (VTSServer, 1);
+
+ server->tty = -1;
+ server->epollfd = -1;
+ server->signalfd = -1;
+ server->sock[0] = -1;
+ server->sock[1] = -1;
+ server->pam_status = PAM_SUCCESS;
+ server->watchdog_timeout = 10000;
+
+ return server;
+}
+
+void
+vts_server_set_pam_enabled (VTSServer *server, bool enabled)
+{
+ server->pam_enabled = enabled;
+}
+
+void
+vts_server_set_tty (VTSServer *server, const char *tty)
+{
+ free (server->tty_name);
+ server->tty_name = xstrdup (tty);
+}
+
+void
+vts_server_set_privileged_group (VTSServer *server,
+ const char *group)
+{
+ free (server->privileged_group);
+ server->privileged_group = xstrdup (group);
+}
+
+#warning "think about this a bit more..."
+void
+vts_server_set_require_systemd_session (VTSServer *server,
+ bool session_required)
+{
+ server->require_systemd_session = session_required;
+}
+
+void
+vts_server_set_kernel_key_bindings_enabled (VTSServer *server,
+ bool enabled)
+{
+ server->kernel_key_bindings_enabled = enabled;
+}
+
+void
+vts_server_set_watchdog_timeout_secs (VTSServer *server,
+ int timeout)
+{
+ server->watchdog_timeout = timeout * 1000;
+}
+
+/* If true then the child process will be sent a SIGSTOP enabling a
+ * debugger to be attached before the child is setup.
+ */
+void
+vts_server_set_stop_child_enabled (VTSServer *server,
+ bool stop_child_enabled)
+{
+ server->stop_child_enabled = stop_child_enabled;
+}
+
+void
+vts_server_set_preserve_environment (VTSServer *server,
+ bool preserve_environment)
+{
+ server->preserve_environment = preserve_environment;
+}
+
+static gid_t *
+read_groups (VTSError **error)
+{
+ int n;
+ gid_t *groups;
+
+ n = getgroups (0, NULL);
+
+ if (n < 0)
+ {
+ set_error_from_errno (error, "Unable to retrieve groups");
+ return NULL;
+ }
+
+ groups = xnew0 (gid_t, n);
+ if (!groups)
+ return NULL;
+
+ if (getgroups (n, groups) < 0)
+ {
+ set_error_from_errno (error, "Unable to retrieve groups");
+ free (groups);
+ return NULL;
+ }
+
+ return groups;
+}
+
+/* XXX: Note that a return value of false here does not imply an error
+ * (unlike most other functions that take an error pointer) and so
+ * error should be explicitly check after calling this api.
+ */
+static bool
+in_priviledged_group (VTSServer *server, VTSError **error)
+{
+ struct group *gr;
+ gid_t *groups;
+ int i;
+
+ if (getuid () == 0)
+ return true;
+
+ gr = getgrnam (server->privileged_group);
+ if (!gr)
+ {
+ set_error_from_errno (error, "Failed to lookup '%s' in group database",
+ server->privileged_group);
+ return false;
+ }
+
+ groups = read_groups (error);
+ if (!groups)
+ return false;
+
+ for (i = 0; groups[i]; i++)
+ {
+ if (groups[i] == gr->gr_gid)
+ {
+ free (groups);
+ return true;
+ }
+ }
+ free (groups);
+#warning "For consistency we could return a VTSError here?"
+
+ return false;
+}
+
+bool
+have_systemd_seat_session (VTSServer *server)
+{
+ char *session, *seat;
+ int err;
+
+ err = sd_pid_get_session (getpid (), &session);
+ if (err == 0 && session)
+ {
+ if (sd_session_is_active (session) &&
+ sd_session_get_seat (session, &seat) == 0)
+ {
+ free (seat);
+ free (session);
+ return true;
+ }
+ free (session);
+ }
+
+ return false;
+}
+
+static int
+pam_conversation_fn (int msg_count,
+ const struct pam_message **messages,
+ struct pam_response **responses,
+ void *user_data)
+{
+ return PAM_SUCCESS;
+}
+
+static bool
+open_pam_login_session (VTSServer *server, VTSError **error)
+{
+ server->pc.conv = pam_conversation_fn;
+ server->pc.appdata_ptr = server;
+
+ vts_debug ("Opening PAM session...");
+
+ server->pam_status = pam_start ("login",
+ server->pw->pw_name,
+ &server->pc, &server->ph);
+ if (server->pam_status != PAM_SUCCESS)
+ {
+ set_error (error, 0,
+ "Failed to start pam transaction: %d: %s\n",
+ server->pam_status,
+ pam_strerror (server->ph, server->pam_status));
+ return false;
+ }
+
+ vts_debug ("PAM_TTY=%s", ttyname (server->tty));
+ server->pam_status = pam_set_item (server->ph,
+ PAM_TTY, ttyname (server->tty));
+ if (server->pam_status != PAM_SUCCESS)
+ {
+ set_error (error, 0,
+ "Failed to set PAM_TTY item: %d: %s\n",
+ server->pam_status,
+ pam_strerror (server->ph, server->pam_status));
+ return false;
+ }
+
+ server->pam_status = pam_open_session (server->ph, 0);
+ if (server->pam_status != PAM_SUCCESS)
+ {
+ set_error (error, 0,
+ "Failed to open pam session: %d: %s\n",
+ server->pam_status,
+ pam_strerror (server->ph, server->pam_status));
+ return false;
+ }
+
+ server->pam_session_opened = true;
+
+ return true;
+}
+
+static bool
+create_socketpair (VTSServer *server,
+ VTSError **error)
+{
+ struct epoll_event ev;
+
+ if (socketpair (AF_LOCAL, SOCK_DGRAM, 0, server->sock) < 0)
+ {
+ set_error_from_errno (error, "Failed to create socketpair");
+ return false;
+ }
+
+ fcntl (server->sock[0], F_SETFD, O_CLOEXEC);
+
+ memset (&ev, 0, sizeof ev);
+ ev.events = EPOLLIN;
+ ev.data.fd = server->sock[0];
+ if (epoll_ctl (server->epollfd, EPOLL_CTL_ADD, ev.data.fd, &ev) < 0)
+ {
+ set_error_from_errno (error, "Failed to setup epoll fd");
+ close (server->sock[0]);
+ close (server->sock[1]);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+reset_signals (VTSServer *server)
+{
+ close (server->signalfd);
+ server->signalfd = -1;
+
+ if (server->masked_signals)
+ {
+ sigprocmask (SIG_SETMASK, &server->old_signal_mask, NULL);
+ server->masked_signals = false;
+ }
+
+ if (server->setup_sigchld_action)
+ {
+ sigaction (SIGCHLD, &server->old_sigchld_action, NULL);
+ server->setup_sigchld_action = false;
+ }
+}
+
+static bool
+setup_signals (VTSServer *server, VTSError **error)
+{
+ sigset_t mask;
+ struct sigaction sa;
+ struct epoll_event ev;
+ int err;
+
+ memset (&sa, 0, sizeof sa);
+ sa.sa_handler = SIG_DFL;
+ sa.sa_flags = SA_NOCLDSTOP | SA_RESTART;
+ if (sigaction (SIGCHLD, &sa, &server->old_sigchld_action) < 0)
+ {
+ set_error_from_errno (error, "Failed to set SIGCHLD action");
+ goto error;
+ }
+ server->setup_sigchld_action = true;
+
+ err = sigemptyset (&mask);
+ assert (err == 0);
+ sigaddset (&mask, SIGCHLD);
+ sigaddset (&mask, SIGINT);
+ sigaddset (&mask, SIGTERM);
+ sigaddset (&mask, SIGUSR1); /* VT release */
+ sigaddset (&mask, SIGUSR2); /* VT acquire */
+ if (sigprocmask (SIG_BLOCK, &mask, &server->old_signal_mask) < 0)
+ {
+ set_error_from_errno (error, "Failed to set signal mask");
+ goto error;
+ }
+ server->masked_signals = true;
+
+ server->signalfd = signalfd (-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
+ if (server->signalfd < 0)
+ {
+ set_error_from_errno (error, "Failed to get signal file descriptor");
+ goto error;
+ }
+
+ memset (&ev, 0, sizeof ev);
+ ev.events = EPOLLIN;
+ ev.data.fd = server->signalfd;
+ if (epoll_ctl (server->epollfd, EPOLL_CTL_ADD, ev.data.fd, &ev) < 0)
+ {
+ set_error_from_errno (error, "Failed to add signal fd to epoll fd");
+ goto error;
+ }
+
+ return true;
+
+error:
+
+ reset_signals (server);
+
+ return false;
+}
+
+static bool
+open_tty (VTSServer *server, VTSError **error)
+{
+ if (server->tty_name)
+ {
+ char *t = ttyname (STDIN_FILENO);
+ if (t && strcmp (t, server->tty_name) == 0)
+ server->tty = STDIN_FILENO; /* XXX: should we fcntl (O_CLOEXEC) ? */
+ else
+ {
+ struct stat buf;
+
+ if (stat (server->tty_name, &buf) < 0)
+ {
+ set_error_from_errno (error, "Failed to stat '%s'",
+ server->tty_name);
+ return false;
+ }
+
+ if (major (buf.st_rdev) != TTY_MAJOR)
+ {
+ set_error (error, 0, "Invalid tty device: %s", server->tty_name);
+ return false;
+ }
+
+ server->ttynr = minor (buf.st_rdev);
+ }
+
+ server->tty = open (server->tty_name, O_RDWR | O_NOCTTY | O_CLOEXEC);
+ }
+ else
+ {
+ int tty0 = open ("/dev/tty0", O_WRONLY | O_CLOEXEC);
+ char filename[16];
+
+ if (tty0 < 0)
+ {
+ set_error_from_errno (error, "Could not open tty0");
+ return false;
+ }
+
+ if (ioctl (tty0, VT_OPENQRY, &server->ttynr) < 0 || server->ttynr == -1)
+ {
+ set_error_from_errno (error, "failed to find free console");
+ close (tty0);
+ return false;
+ }
+
+ close (tty0);
+
+ snprintf (filename, sizeof filename, "/dev/tty%d", server->ttynr);
+ server->tty = open (filename, O_RDWR | O_NOCTTY);
+ }
+
+ if (server->tty < 0)
+ {
+ set_error_from_errno (error, "Failed to open tty%d", server->ttynr);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+on_tty_input (VTSServer *server)
+{
+ /* Ignore input to tty */
+ tcflush (server->tty, TCIFLUSH);
+}
+
+static void
+reset_tty (VTSServer *server)
+{
+ if (server->tty >= 0)
+ {
+ if (ioctl (server->tty, KDSETMODE, KD_TEXT))
+ vts_warning ("failed to set KD_TEXT mode on tty: %m\n");
+
+ if (server->saved_kb_mode &&
+ ioctl (server->tty, KDSKBMODE, server->kb_mode))
+ vts_warning ("failed to restore keyboard mode: %m\n");
+
+ if (server->saved_terminal_attributes &&
+ tcsetattr (server->tty, TCSANOW, &server->terminal_attributes) < 0)
+ vts_warning ("Could not restore original tty attributes\n");
+ }
+}
+
+static bool
+setup_tty (VTSServer *server, VTSError **error)
+{
+ struct termios raw_attributes;
+ struct vt_mode mode = { 0 };
+ int ret;
+
+ if (ioctl (server->tty, VT_ACTIVATE, server->ttynr) < 0 ||
+ ioctl (server->tty, VT_WAITACTIVE, server->ttynr) < 0)
+ {
+ set_error_from_errno (error, "Failed to switch to tty");
+ return false;
+ }
+
+ if (tcgetattr (server->tty, &server->terminal_attributes) < 0)
+ {
+ set_error_from_errno (error, "Could not get terminal attributes");
+ return false;
+ }
+ server->saved_terminal_attributes = true;
+
+ /* Note: up until this point errors return immediately instead of
+ * jumping to ERROR: since that relies on calling vts_tty_destroy()
+ * which would try and restore the terminal attributes which, up
+ * until this point, were in an undefined state. */
+
+ /* Ignore control characters and disable echo */
+ raw_attributes = server->terminal_attributes;
+ cfmakeraw (&raw_attributes);
+
+ /* Fix up line endings to be normal (cfmakeraw hoses them) */
+ raw_attributes.c_oflag |= OPOST | OCRNL;
+
+ if (tcsetattr (server->tty, TCSANOW, &raw_attributes) < 0)
+ {
+ set_error_from_errno (error, "Could not put terminal into raw mode");
+ goto error;
+ }
+
+ ioctl (server->tty, KDGKBMODE, &server->kb_mode);
+ server->saved_kb_mode = true;
+
+#if 0
+ if (!server->kernel_key_bindings_enabled &&
+ ioctl (server->tty, KDSKBMODE, K_OFF));
+ {
+ ret = ioctl (server->tty, KDSKBMODE, K_RAW);
+ if (ret)
+ {
+ set_error_from_errno (error, "failed to set keyboard mode on tty");
+ goto error;
+ }
+ }
+#endif
+
+ ret = ioctl (server->tty, KDSETMODE, KD_GRAPHICS);
+ if (ret)
+ {
+ set_error_from_errno (error, "Failed to set KD_GRAPHICS mode on tty");
+ goto error;
+ }
+
+ mode.mode = VT_PROCESS;
+ mode.relsig = SIGUSR1;
+ mode.acqsig = SIGUSR2;
+ if (ioctl (server->tty, VT_SETMODE, &mode) < 0)
+ {
+ set_error_from_errno (error, "Failed to take control of vt handling");
+ goto error;
+ }
+
+ return true;
+
+error:
+
+ reset_tty (server);
+
+ return false;
+}
+
+static bool
+server_handle_setmaster (VTSServer *server,
+ struct msghdr *msg,
+ ssize_t len,
+ VTSError **error)
+{
+ struct cmsghdr *cmsg;
+ VTSSetMasterMessage *message;
+ union cmsg_data *data;
+ int fd = -1;
+ int ret;
+ bool status = false;
+
+ if (len != sizeof (*message))
+ {
+ set_error (error, 0, "Spurious SetMaster message size");
+ goto exit;
+ }
+
+ message = msg->msg_iov->iov_base;
+
+ cmsg = CMSG_FIRSTHDR (msg);
+ if (!cmsg ||
+ cmsg->cmsg_level != SOL_SOCKET ||
+ cmsg->cmsg_type != SCM_RIGHTS)
+ {
+ set_error (error, 0, "Invalid control message");
+ goto exit;
+ }
+
+ data = (union cmsg_data *) CMSG_DATA (cmsg);
+ if (data->fd == -1)
+ {
+ set_error (error, 0, "missing drm fd for SetMessage request");
+ goto exit;
+ }
+ fd = data->fd;
+
+#warning "XXX: how can we validate that this drm device belongs to the session-seat before we SetMaster?"
+
+ if (message->set_master)
+ ret = drmSetMaster (fd);
+ else
+ ret = drmDropMaster (fd);
+
+ if (ret == -1)
+ {
+ set_error_from_errno (error, "Failed to Set/Drop drm master");
+ goto exit;
+ }
+
+ status = true;
+
+exit:
+ close (fd);
+
+ do {
+ len = send (server->sock[0], &ret, sizeof ret, 0);
+ } while (len < 0 && errno == EINTR);
+
+ if (len < sizeof ret)
+ {
+ set_error_from_errno (error, "Failed to send SetMaster reply");
+ return false;
+ }
+
+ return status;
+}
+
+static bool
+server_handle_open_input (VTSServer *server,
+ struct msghdr *msg,
+ ssize_t len,
+ VTSError **error)
+{
+ int fd = -1, ret = -1;
+ char control[CMSG_SPACE (sizeof (fd))];
+ struct cmsghdr *cmsg;
+ struct stat s;
+ struct msghdr nmsg;
+ struct iovec iov;
+ VTSOpenInputMessage *message;
+ union cmsg_data *data;
+ bool status = false;
+
+ message = msg->msg_iov->iov_base;
+ if ((size_t)len < sizeof (*message))
+ {
+ set_error (error, 0, "Unexpected OpenInput message size");
+ goto exit;
+ }
+
+ /* Ensure path is null-terminated */
+ ((char *) message)[len-1] = '\0';
+
+ if (stat (message->path, &s) < 0)
+ {
+ set_error_from_errno (error, "Failed to stat input device");
+ goto exit;
+ }
+
+#warning "XXX: the input device might not belong to this seat!?"
+ if (major (s.st_rdev) != INPUT_MAJOR)
+ {
+ set_error (error, 0, "Unexpected OpenInput message size");
+ goto exit;
+ }
+
+ fd = open (message->path,
+ message->flags & (O_RDONLY | O_WRONLY | O_RDWR | O_NONBLOCK));
+ if (fd < 0)
+ {
+ set_error_from_errno (error, "Failed to open input device");
+ goto exit;
+ }
+
+ status = true;
+
+exit:
+ memset (&nmsg, 0, sizeof nmsg);
+ nmsg.msg_iov = &iov;
+ nmsg.msg_iovlen = 1;
+ if (fd != -1)
+ {
+ nmsg.msg_control = control;
+ nmsg.msg_controllen = sizeof control;
+ cmsg = CMSG_FIRSTHDR (&nmsg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN (sizeof (fd));
+ data = (union cmsg_data *) CMSG_DATA(cmsg);
+ data->fd = fd;
+ nmsg.msg_controllen = cmsg->cmsg_len;
+ ret = 0;
+ }
+ iov.iov_base = &ret;
+ iov.iov_len = sizeof ret;
+
+ vts_debug ("Opened %s: ret: %d, fd: %d\n",
+ message->path, ret, fd);
+ do {
+ len = sendmsg (server->sock[0], &nmsg, 0);
+ } while (len < 0 && errno == EINTR);
+
+ if (len < 0)
+ {
+ set_error_from_errno (error, "Failed to send reply");
+ return false;
+ }
+
+ return status;
+}
+
+static bool
+server_handle_vt_leave_response (VTSServer *server,
+ struct msghdr *msg,
+ ssize_t len,
+ VTSError **error)
+{
+ VTSMessage *message;
+
+ if (!server->handling_vt_leave)
+ {
+ set_error (error, 0, "Spurious VT leave response");
+ return false;
+ }
+
+ if ((size_t)len < sizeof (VTSMessage))
+ {
+ set_error (error, 0, "Unexpected VT leave response size");
+ return false;
+ }
+
+ message = msg->msg_iov->iov_base;
+
+ if (message->id == VTS_MESSAGE_ID_VT_LEAVE_ACCEPT)
+ {
+#warning "Check in this case that drmDropMaster() was called if necessary"
+ vts_debug ("VT leave accept received");
+ ioctl (server->tty, VT_RELDISP, 1);
+ }
+ else
+ {
+ vts_debug ("VT leave deny received");
+ ioctl (server->tty, VT_RELDISP, 0);
+ }
+
+ server->watchdog_enabled = false;
+ server->handling_vt_leave = false;
+
+ return true;
+}
+
+static bool
+server_handle_message (VTSServer *server, VTSError **error)
+{
+ char control[CMSG_SPACE (sizeof (int))];
+ char buf[BUFSIZ];
+ struct msghdr msg;
+ struct iovec iov;
+ ssize_t len;
+ VTSMessage *message;
+
+ memset (&msg, 0, sizeof (msg));
+ iov.iov_base = buf;
+ iov.iov_len = sizeof buf;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = control;
+ msg.msg_controllen = sizeof control;
+
+ do {
+ len = recvmsg (server->sock[0], &msg, 0);
+ } while (len < 0 && errno == EINTR);
+
+ if (len < 1)
+ {
+ set_error_from_errno (error, "Failed to receive server message");
+ return false;
+ }
+
+ message = (void *) buf;
+ switch (message->id)
+ {
+ case VTS_MESSAGE_ID_VT_LEAVE_ACCEPT:
+ case VTS_MESSAGE_ID_VT_LEAVE_DENY:
+ return server_handle_vt_leave_response (server, &msg, len, error);
+ case VTS_MESSAGE_ID_OPEN_INPUT:
+ return server_handle_open_input (server, &msg, len, error);
+ case VTS_MESSAGE_ID_SET_MASTER:
+ return server_handle_setmaster (server, &msg, len, error);
+ default:
+ set_error (error, 0, "Unknown message type received by server");
+ return false;
+ }
+}
+
+static bool
+send_vt_switch_message (VTSServer *server,
+ VTSVTSwitchDirection direction,
+ VTSError **error)
+{
+ VTSVTSwitchMessage message;
+ ssize_t len;
+
+ message.header.id = VTS_MESSAGE_ID_VT_SWITCH;
+ message.direction = direction;
+
+ do {
+ len = send (server->sock[0], &message, sizeof (message), 0);
+ } while (len < 0 && errno == EINTR);
+
+ if (len < 0)
+ {
+ set_error_from_errno (error, "Failed to send VT switch message");
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+server_handle_vt_signal (VTSServer *server,
+ VTSVTSwitchDirection direction,
+ VTSError **error)
+{
+ vts_debug ("VT switch signal received");
+
+ if (server->handling_vt_leave)
+ {
+ /* XXX: The kernel may pile up VT release signals but in
+ * that case only one ACK is required so we ignore additional
+ * release requests while we are waiting for an ACK.
+ *
+ * In the case of acquire signals it looks like the kernel
+ * kindly blocks those for us while a release is pending
+ * but since that doesn't seem to be documented anywhere
+ * we won't assume that behaviour.
+ */
+ vts_warning ("Ignoring vt switch signal while handling switch");
+ return true;
+ }
+
+ if (direction == VTS_VT_SWITCH_DIRECTION_LEAVE)
+ {
+ if (!send_vt_switch_message (server,
+ VTS_VT_SWITCH_DIRECTION_LEAVE, error))
+ return false;
+
+ if (server->watchdog_timeout > 0)
+ {
+ vts_debug ("Watchdog enabled for %d seconds",
+ (int)server->watchdog_timeout / 1000);
+ server->watchdog_timestamp = get_time ();
+ server->watchdog_enabled = true;
+ }
+
+ server->handling_vt_leave = true;
+ }
+ else
+ {
+ ioctl (server->tty, VT_RELDISP, VT_ACKACQ);
+
+ if (!send_vt_switch_message (server,
+ VTS_VT_SWITCH_DIRECTION_ENTER, error))
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+server_handle_signal (VTSServer *server, VTSError **error)
+{
+ struct signalfd_siginfo sig;
+ int pid, status;
+
+ if (read (server->signalfd, &sig, sizeof sig) != sizeof sig)
+ {
+ set_error_from_errno (error, "Failed to read signal fd");
+ return false;
+ }
+
+ vts_debug ("Signal received: %s", strsignal (sig.ssi_signo));
+
+ switch (sig.ssi_signo)
+ {
+ case SIGCHLD:
+ do {
+ pid = wait (&status);
+ } while (pid == -1 && errno == EINTR);
+
+ if (pid == (pid_t)-1)
+ {
+ set_error_from_errno (error, "Failed to wait for child exit status");
+ return false;
+ }
+ else if (pid == server->child)
+ {
+ server->child = 0;
+ server->child_wait_status = status;
+
+ server->quit = true;
+ return true;
+ }
+ break;
+ case SIGTERM:
+ if (server->child)
+ kill (server->child, SIGTERM);
+
+ server->quit = true;
+ return true;
+ case SIGINT:
+ if (server->child)
+ {
+ printf ("Killing child...\n");
+ kill (server->child, SIGTERM);
+ }
+ return true;
+ case SIGUSR1:
+ return server_handle_vt_signal (server, VTS_VT_SWITCH_DIRECTION_LEAVE,
+ error);
+ case SIGUSR2:
+ return server_handle_vt_signal (server, VTS_VT_SWITCH_DIRECTION_ENTER,
+ error);
+ default:
+ vts_warning ("Spurious signal received %d", sig.ssi_signo);
+ return true;
+ }
+
+ return true;
+}
+
+static void
+setup_child (VTSServer *server, char **saved_environ)
+{
+ char **env;
+
+ vts_debug ("Spawned child with pid: %d\n", getpid ());
+
+ reset_signals (server);
+
+#warning "Have a server_close_fds() function for this..."
+ close (server->sock[0]);
+ close (server->epollfd);
+
+ if (setgid (server->pw->pw_gid) < 0 ||
+ setuid (server->pw->pw_uid) < 0)
+ {
+ vts_warning ("Failed to drop privileges");
+ exit (EXIT_FAILURE);
+ }
+
+ if (server->stop_child_enabled)
+ {
+ vts_debug ("Stopping child\n");
+ raise (SIGSTOP);
+ }
+
+ if (saved_environ)
+ {
+ int i;
+ vts_debug ("Restoring saved environment variables...");
+ for (i = 0; saved_environ[i]; i++)
+ {
+ vts_debug ("putenv (%s)", saved_environ[i]);
+ if (putenv (saved_environ[i]) < 0)
+ vts_warning ("putenv %s failed", saved_environ[i]);
+ }
+ }
+
+ env = pam_getenvlist (server->ph);
+ if (env)
+ {
+ int i;
+ vts_debug ("setting PAM environment variables...");
+
+ for (i = 0; env[i]; i++)
+ {
+ vts_debug ("putenv (%s)", env[i]);
+
+ if (putenv (env[i]) < 0)
+ vts_warning ("putenv %s failed", env[i]);
+ }
+ free (env);
+ }
+}
+
+VTSClient *
+vts_client_new (int fd, VTSError **error)
+{
+ int epollfd = -1;
+ struct epoll_event ev;
+ VTSClient *client;
+
+ epollfd = epoll_create1 (EPOLL_CLOEXEC);
+ if (epollfd < 0)
+ {
+ set_error_from_errno (error, "Failed to create epoll fd");
+ return NULL;
+ }
+
+ memset (&ev, 0, sizeof ev);
+ ev.events = EPOLLIN;
+ ev.data.fd = fd;
+ if (epoll_ctl (epollfd, EPOLL_CTL_ADD, ev.data.fd, &ev) < 0)
+ {
+ set_error_from_errno (error, "Failed to setup epoll fd");
+ return NULL;
+ }
+
+ client = xnew0 (VTSClient, 1);
+ client->fd = fd;
+ client->epollfd = epollfd;
+
+ return client;
+}
+
+VTSClient *
+vts_server_run (VTSServer *server, VTSError **error)
+{
+ char **saved_environ = NULL;
+
+ /* For the convenience of not needing to setuid a launcher during
+ * development we also check the SUDO_USER variable if the real user
+ * is root.
+ */
+ if (getuid () == 0 && getenv ("SUDO_USER"))
+ server->pw = getpwnam (getenv ("SUDO_USER"));
+ else
+ server->pw = getpwuid (getuid ());
+ if (server->pw == NULL)
+ {
+ set_error_from_errno (error, "Failed to get username");
+ return NULL;
+ }
+
+ /* We need to clear the environment before running privileged code
+ * but optionally we save the environment to be restored once
+ * privileges have been dropped which can be useful for keeping
+ * variables that affect the non-privileged client, such as debug
+ * options.
+ */
+ if (server->preserve_environment)
+ {
+ int i, j;
+
+ for (i = 0; environ[i]; i++)
+ ;
+ saved_environ = xmalloc (sizeof (char *) * (i + 1));
+ for (j = 0; j < i; j++)
+ saved_environ[i] = xstrdup (environ[i]);
+ saved_environ[j] = NULL;
+ }
+
+ clearenv ();
+
+ setenv ("USER", server->pw->pw_name, 1);
+ setenv ("LOGNAME", server->pw->pw_name, 1);
+ setenv ("HOME", server->pw->pw_dir, 1);
+ setenv ("SHELL", server->pw->pw_shell, 1);
+
+ vts_debug ("USER=%s", server->pw->pw_name);
+ vts_debug ("HOME=%s", server->pw->pw_dir);
+ vts_debug ("SHELL=%s", server->pw->pw_shell);
+
+ if (server->privileged_group && !in_priviledged_group (server, error))
+ {
+ if (error && *error)
+ return NULL;
+
+ set_error (error, EPERM,
+ "Permission denied.\n"
+ "User does not belong to privileged '%s' group.\n",
+ server->privileged_group);
+ return NULL;
+ }
+
+ vts_debug ("Privileges OK\n");
+
+ if (server->require_systemd_session && !have_systemd_seat_session (server))
+ {
+ set_error (error, EPERM,
+ "Permission denied. You must run from an active "
+ "and local systemd session:\n");
+ return NULL;
+ }
+
+ if (!open_tty (server, error))
+ goto server_shutdown;
+
+ vts_warning ("TTY %d opened", server->ttynr);
+
+ if (server->pam_enabled && !open_pam_login_session (server, error))
+ goto server_shutdown;
+
+ if (!setup_tty (server, error))
+ goto server_shutdown;
+
+#warning "XXX: Do we need to setup signals/epollfd before setup_tty?"
+ server->epollfd = epoll_create1 (EPOLL_CLOEXEC);
+ if (server->epollfd < 0)
+ {
+ set_error_from_errno (error, "Failed to create epoll fd");
+ goto server_shutdown;
+ }
+
+ if (!create_socketpair (server, error))
+ goto server_shutdown;
+
+ if (!setup_signals (server, error))
+ goto server_shutdown;
+
+ server->child_wait_status = 0;
+
+ vts_warning ("Forking to run client");
+
+ switch ((server->child = fork()))
+ {
+ case -1:
+ set_error_from_errno (error, "Fork failed");
+ goto server_shutdown;
+ case 0:
+ setup_child (server, saved_environ);
+ return vts_client_new (server->sock[1], NULL);
+ default:
+ close (server->sock[1]);
+
+ vts_debug ("Running mainloop...");
+ while (!server->quit)
+ {
+ struct epoll_event ev;
+ int n;
+
+ do
+ {
+ int timeout = -1;
+
+ if (server->watchdog_enabled)
+ {
+ uint64_t elapsed = get_time () - server->watchdog_timestamp;
+ timeout = server->watchdog_timeout - elapsed;
+ if (timeout <= 0)
+ {
+ vts_warning ("Watchdog triggered shutdown!");
+ kill (server->child, SIGTERM);
+ set_error (error, 0, "Watchdog triggered shutdown");
+ goto server_shutdown;
+ }
+ }
+
+ n = epoll_wait (server->epollfd, &ev, 1, timeout);
+ }
+ while (n == 0 || n < 0 && errno == EINTR);
+
+ if (n < 0)
+ {
+ set_error_from_errno (error, "epoll_wait failed");
+ goto server_shutdown;
+ }
+
+ vts_debug ("Iterating mainloop");
+ if (ev.data.fd == server->sock[0])
+ {
+ if (!server_handle_message (server, error))
+ goto server_shutdown;
+ }
+ else if (ev.data.fd == server->signalfd)
+ {
+ if (!server_handle_signal (server, error))
+ goto server_shutdown;
+ }
+ else if (ev.data.fd == server->tty)
+ on_tty_input (server);
+ }
+ break;
+ }
+
+server_shutdown:
+
+ if (server->child_wait_status)
+ {
+ if (WIFEXITED (server->child_wait_status))
+ vts_warning ("Child exited with status %d",
+ WEXITSTATUS (server->child_wait_status));
+ else if (WIFSIGNALED (server->child_wait_status))
+ vts_warning ("Child exited with signal %s",
+ strsignal (WTERMSIG (server->child_wait_status)));
+ }
+ else if (server->child)
+ kill (server->child, SIGTERM);
+#warning "Should we kill the child here?"
+
+ if (server->pam_enabled)
+ {
+ if (server->pam_session_opened)
+ {
+ server->pam_status = pam_close_session (server->ph, 0);
+ server->pam_session_opened = false;
+ }
+
+ pam_end (server->ph, server->pam_status);
+ }
+
+ if (server->tty >= 0)
+ {
+ reset_tty (server);
+
+ if (server->tty != STDIN_FILENO)
+ close (server->tty);
+ }
+
+ reset_signals (server);
+
+ close (server->epollfd);
+ close (server->sock[0]);
+
+ return NULL;
+}
+
+int
+vts_server_get_child_wait_status (VTSServer *server)
+{
+ return server->child_wait_status;
+}
+
+void
+vts_client_set_vt_enter_callback (VTSClient *client,
+ VTSSwitchCallback callback,
+ void *user_data)
+{
+ client->vt_enter_callback = callback;
+ client->vt_enter_user_data = user_data;
+}
+
+void
+vts_client_set_vt_leave_callback (VTSClient *client,
+ VTSSwitchCallback callback,
+ void *user_data)
+{
+ client->vt_leave_callback = callback;
+ client->vt_leave_user_data = user_data;
+}
+
+int
+vts_client_get_poll_fd (VTSClient *client)
+{
+ return client->fd;
+}
+
+static bool
+client_handle_vt_switch (VTSClient *client,
+ struct msghdr *msg,
+ ssize_t len,
+ VTSError **error)
+{
+ VTSVTSwitchMessage *message;
+
+ if (len != sizeof (*message))
+ {
+ set_error (error, 0, "Spurious VTSwitch message size");
+ return false;
+ }
+
+ message = msg->msg_iov->iov_base;
+
+ if (message->direction == VTS_VT_SWITCH_DIRECTION_LEAVE)
+ {
+ if (client->vt_leave_callback)
+ client->vt_leave_callback (client, client->vt_leave_user_data);
+ }
+ else
+ {
+ if (client->vt_enter_callback)
+ client->vt_enter_callback (client, client->vt_enter_user_data);
+ }
+
+ return true;
+}
+
+static bool
+client_handle_message (VTSClient *client, VTSError **error)
+{
+ char control[CMSG_SPACE (sizeof (int))];
+ char buf[BUFSIZ];
+ struct msghdr msg;
+ struct iovec iov;
+ ssize_t len;
+ VTSMessage *message;
+
+ memset (&msg, 0, sizeof (msg));
+ iov.iov_base = buf;
+ iov.iov_len = sizeof buf;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = control;
+ msg.msg_controllen = sizeof control;
+
+ do {
+ len = recvmsg (client->fd, &msg, 0);
+ } while (len < 0 && errno == EINTR);
+
+ if (len < 1)
+ {
+ set_error_from_errno (error, "Failed to receive server message");
+ return false;
+ }
+
+ message = (void *) buf;
+ switch (message->id)
+ {
+ case VTS_MESSAGE_ID_VT_SWITCH:
+ return client_handle_vt_switch (client, &msg, len, error);
+ default:
+ set_error (error, 0, "Unknown message type received by client");
+ return false;
+ }
+}
+
+bool
+vts_client_dispatch (VTSClient *client, VTSError **error)
+{
+ while (1)
+ {
+ struct epoll_event ev;
+ int n;
+
+ do {
+ n = epoll_wait (client->epollfd, &ev, 1, 0);
+ } while (n < 0 && errno == EINTR);
+
+ if (n < 0)
+ vts_warning ("epoll_wait failed");
+
+ if (n == 0)
+ return true;
+
+ if (ev.data.fd == client->fd)
+ {
+ if (!client_handle_message (client, error))
+ return false;
+ }
+ }
+}
+
+static bool
+send_simple_message (VTSClient *client,
+ VTSMessageID id,
+ VTSError **error)
+{
+ VTSMessage message;
+ int len;
+
+ message.id = id;
+
+ do {
+ len = send (client->fd, &message, sizeof message, 0);
+ } while (len < 0 && errno == EINTR);
+
+ if (len < sizeof message)
+ {
+ set_error_from_errno (error, "Failed to send message");
+ return false;
+ }
+
+ return true;
+}
+
+bool
+vts_client_accept_vt_leave (VTSClient *client, VTSError **error)
+{
+ return send_simple_message (client,
+ VTS_MESSAGE_ID_VT_LEAVE_ACCEPT,
+ error);
+}
+
+bool
+vts_client_deny_vt_leave (VTSClient *client, VTSError **error)
+{
+ return send_simple_message (client,
+ VTS_MESSAGE_ID_VT_LEAVE_DENY,
+ error);
+}
+
+bool
+vts_client_drm_set_master (VTSClient *client,
+ int drm_fd,
+ bool master,
+ VTSError **error)
+{
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ struct iovec iov;
+ char control[CMSG_SPACE (sizeof (drm_fd))];
+ int ret;
+ ssize_t len;
+ VTSSetMasterMessage message;
+ union cmsg_data *data;
+
+ memset (&msg, 0, sizeof msg);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = control;
+ msg.msg_controllen = sizeof control;
+ cmsg = CMSG_FIRSTHDR (&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN (sizeof (drm_fd));
+
+ data = (union cmsg_data *) CMSG_DATA (cmsg);
+ data->fd = drm_fd;
+ msg.msg_controllen = cmsg->cmsg_len;
+
+ iov.iov_base = &message;
+ iov.iov_len = sizeof message;
+
+ message.header.id= VTS_MESSAGE_ID_SET_MASTER;
+ message.set_master = master;
+
+ do {
+ len = sendmsg (client->fd, &msg, 0);
+ } while (len < 0 && errno == EINTR);
+
+ if (len < 0)
+ {
+ set_error_from_errno (error, "Failed to send SetMaster message");
+ return false;
+ }
+
+ do {
+ len = recv (client->fd, &ret, sizeof ret, 0);
+ } while (len < 0 && errno == EINTR);
+
+ if (len < 0)
+ {
+ set_error (error, 0, "SetMaster request failed");
+ return false;
+ }
+
+ return true;
+}
+
+int
+vts_client_open_input_device (VTSClient *client,
+ const char *device,
+ int open_flags,
+ VTSError **error)
+{
+ int sock = client->fd;
+ int n, ret = -1;
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ struct iovec iov;
+ union cmsg_data *data;
+ char control[CMSG_SPACE (sizeof data->fd)];
+ ssize_t len;
+ VTSOpenInputMessage *message;
+
+ n = sizeof (*message) + strlen (device) + 1;
+ message = malloc (n);
+ if (!message)
+ {
+ set_error_from_errno (error, "Failed to allocate OpenInput message");
+ return false;
+ }
+
+ message->header.id = VTS_MESSAGE_ID_OPEN_INPUT;
+ /* NB: The server will effectively */
+ message->flags = open_flags;
+ strcpy (message->path, device);
+
+ do {
+ len = send (sock, message, n, 0);
+ } while (len < 0 && errno == EINTR);
+
+ if (len < 0)
+ {
+ set_error_from_errno (error, "Failed to send OpenInput message");
+ return -1;
+ }
+
+ free (message);
+
+ memset (&msg, 0, sizeof msg);
+ iov.iov_base = &ret;
+ iov.iov_len = sizeof ret;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = control;
+ msg.msg_controllen = sizeof control;
+
+ do {
+ len = recvmsg (sock, &msg, MSG_CMSG_CLOEXEC);
+ } while (len < 0 && errno == EINTR);
+
+ if (len != sizeof ret || ret < 0)
+ {
+ set_error (error, 0, "Failed to retrieve OpenInput return value");
+ return -1;
+ }
+
+ cmsg = CMSG_FIRSTHDR (&msg);
+ if (!cmsg ||
+ cmsg->cmsg_level != SOL_SOCKET ||
+ cmsg->cmsg_type != SCM_RIGHTS)
+ {
+ set_error (error, 0, "Invalid control message");
+ return -1;
+ }
+
+ data = (union cmsg_data *) CMSG_DATA (cmsg);
+ if (data->fd == -1)
+ {
+ set_error (error, 0, "missing fd in OpenInput reply\n");
+ return -1;
+ }
+
+ return data->fd;
+}
diff --git a/deps/vts/vts.h b/deps/vts/vts.h
new file mode 100644
index 00000000..f3e1a937
--- /dev/null
+++ b/deps/vts/vts.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright © 2013 Intel Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _VTS_H_
+#define _VTS_H_
+
+#include <stdbool.h>
+
+typedef struct
+{
+ int status;
+ char *message;
+} VTSError;
+
+void
+vts_error_free (VTSError *error);
+
+typedef struct _VTSServer VTSServer;
+typedef struct _VTSClient VTSClient;
+
+VTSServer *
+vts_server_new (void);
+
+/* Should we print out any diagnosis messages? */
+void
+vts_server_set_verbose (VTSServer *server, bool verbose);
+
+/* Should a PAM session be opened, or can we assume that something
+ * else will have opened a PAM session for us?
+ */
+void
+vts_server_set_pam_enabled (VTSServer *server, bool enabled);
+
+/* What TTY device should be opened? */
+void
+vts_server_set_tty (VTSServer *server,
+ const char *tty);
+
+void
+vts_server_set_privileged_group (VTSServer *server,
+ const char *group);
+
+#warning "think about this a bit more..."
+/* XXX: set_require_local_seat() might be more descriptive */
+void
+vts_server_set_require_systemd_session (VTSServer *server,
+ bool session_required);
+
+void
+vts_server_set_kernel_key_bindings_enabled (VTSServer *server,
+ bool enabled);
+
+/* This timeout is used while a VT leave is in progress in case the
+ * client has hung and in that case the server will shutdown, killing
+ * the client and reseting the tty.
+ *
+ * A timeout <= 0 will disable the watchdog
+ */
+void
+vts_server_set_watchdog_timeout_secs (VTSServer *server,
+ int timeout);
+
+/* Send a SIGSTOP to the child process after forking to give an
+ * opportunity to attach a debugger */
+void
+vts_server_set_stop_child_enabled (VTSServer *server,
+ bool stop_child_enabled);
+
+/* Should the original environment be preserved for the un-privileged
+ * client code? (Note the environment is unconditionally cleared
+ * before running the priviledged server code)
+ */
+void
+vts_server_set_preserve_environment (VTSServer *server,
+ bool preserve_environment);
+
+VTSClient *
+vts_server_run (VTSServer *server, VTSError **error);
+
+typedef void (*VTSSwitchCallback) (VTSClient *client, void *user_data);
+
+/* Optional.
+ *
+ * If exec is called by the child process after returning from
+ * vts_server_run() then a VTSClient can be re-created so long as the
+ * socket fd is known... */
+VTSClient *
+vts_client_new (int fd, VTSError **error);
+
+void
+vts_client_set_vt_enter_callback (VTSClient *client,
+ VTSSwitchCallback callback,
+ void *user_data);
+
+void
+vts_client_set_vt_leave_callback (VTSClient *client,
+ VTSSwitchCallback callback,
+ void *user_data);
+
+bool
+vts_client_accept_vt_leave (VTSClient *client, VTSError **error);
+
+bool
+vts_client_deny_vt_leave (VTSClient *client, VTSError **error);
+
+bool
+vts_client_drm_set_master (VTSClient *client,
+ int drm_fd,
+ bool master,
+ VTSError **error);
+
+/*
+ * The open_flags are restricted to:
+ * O_RDONLY, O_WRONLY, O_RDWR and O_NONBLOCK
+ *
+ * Returns -1 on error
+ */
+int
+vts_client_open_input_device (VTSClient *client,
+ const char *device,
+ int open_flags,
+ VTSError **error);
+
+/* Returns an epoll fd
+ *
+ * NB: If using poll(2), then it's only necessary to poll for POLLIN
+ */
+int
+vts_client_get_poll_fd (VTSClient *client);
+
+bool
+vts_client_dispatch (VTSClient *client, VTSError **error);
+
+/* Can be called after vts_server_run() returns to determine how the
+ * client exited. */
+int
+vts_server_get_child_wait_status (VTSServer *server);
+
+#endif