[cogl/wip/vts] Add libvts (Virtual Terminal Session) api



commit 8839290127de9ec61a5696643725420014ed3396
Author: Robert Bragg <robert linux intel com>
Date:   Tue Apr 2 21:34:44 2013 +0100

    Add libvts (Virtual Terminal Session) api
    
    This adds a small library - libvts - to the cogl repo for safely
    handling the privileged operations involved in opening a tty, opening a
    pam session (optional), claiming drmMaster() mode for using KMS and
    opening evdev input devices for the session-seat.
    
    This was written to support the launching of Wayland based compositors
    but there is nothing Wayland specific about the code so it can be used
    to launch any kind of full screen application that wants to run on a
    Linux virtual console via KMS.
    
    The code has very minimal dependencies (no glib usage for example) and
    could be factored out into a standalone project. This would enable
    weston-launch to share the same code.
    
    In the meantime we will maintain this code in Cogl so that applications
    using the KMS backend can automatically take advantage of it.

 configure.ac     |    6 +
 deps/Makefile.am |    4 +
 deps/vts/vts.c   | 1566 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 deps/vts/vts.h   |  132 +++++
 4 files changed, 1708 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index f0bb096..35d4def 100644
--- a/configure.ac
+++ b/configure.ac
@@ -992,6 +992,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"])
@@ -1144,6 +1146,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)
 PKG_CHECK_MODULES(COGL_DEP, [$COGL_PKG_REQUIRES])
@@ -1314,6 +1319,7 @@ deps/Makefile
 deps/glib/Makefile
 deps/gmodule/Makefile
 deps/gmodule/gmoduleconf.h
+deps/vts/Makefile
 cogl/Makefile
 cogl/cogl2.pc
 cogl/cogl-defines.h
diff --git a/deps/Makefile.am b/deps/Makefile.am
index 2cd3cb5..0a9a4fb 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/vts.c b/deps/vts/vts.c
new file mode 100644
index 0000000..456b1d8
--- /dev/null
+++ b/deps/vts/vts.c
@@ -0,0 +1,1566 @@
+/*
+ * 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 <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"
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+struct _VTSServer
+{
+  bool stop_child_enabled;
+  char *privileged_group;
+  bool require_systemd_session;
+  bool kernel_key_bindings_enabled;
+
+  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 has_vt;
+  bool expecting_vt_leave_ack;
+
+  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_ACK,
+  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...) \
+  sd_journal_print (LOG_DEBUG, x, ##a)
+
+#define vts_warning(x, 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);
+}
+
+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;
+
+  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;
+}
+
+/* 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;
+}
+
+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;
+
+  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;
+    }
+
+  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_BLOCK, &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/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 (!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;
+        }
+    }
+
+  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 = SIGUSR1;
+  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_ack (VTSServer *server,
+                            struct msghdr *msg,
+                            ssize_t len,
+                            VTSError **error)
+{
+  if (!server->expecting_vt_leave_ack)
+    {
+      set_error (error, 0, "Spurious VT leave ACK");
+      return FALSE;
+    }
+
+  if ((size_t)len < sizeof (VTSMessage))
+    {
+      set_error (error, 0, "Unexpected VTLeaveACK message size");
+      return FALSE;
+    }
+
+  server->has_vt = FALSE;
+
+  ioctl (server->tty, VT_RELDISP, 1);
+
+  server->expecting_vt_leave_ack = 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_ACK:
+      return server_handle_vt_leave_ack (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, VTSError **error)
+{
+  if (server->has_vt)
+    {
+      if (server->expecting_vt_leave_ack)
+        {
+#warning "XXX: would be good to understand if this is a valid concern..."
+          /* XXX: does the kernel allow these signals to pile up? If
+           * so what are the ACK semantics, does the kernel require
+           * an ACK for each signal? */
+          vts_warning ("Spurious vt switch signal seen while handling switch");
+          return TRUE;
+        }
+
+      if (!send_vt_switch_message (server,
+                                   VTS_VT_SWITCH_DIRECTION_LEAVE, error))
+        return FALSE;
+
+      server->expecting_vt_leave_ack = TRUE;
+    }
+  else
+    {
+      ioctl (server->tty, VT_RELDISP, VT_ACKACQ);
+      server->has_vt = TRUE;
+
+      if (!send_vt_switch_message (server,
+                                   VTS_VT_SWITCH_DIRECTION_ENTER, error))
+        return FALSE;
+#warning "XXX: do we need to consider receiving an enter signal while waiting for a leave ACK?"
+    }
+
+  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;
+    }
+
+  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)
+        kill (server->child, SIGTERM);
+      return TRUE;
+    case SIGUSR1:
+      return server_handle_vt_signal (server, error);
+    default:
+      vts_warning ("Spurious signal received %d", sig.ssi_signo);
+      return TRUE;
+    }
+
+  return TRUE;
+}
+
+static void
+setup_child (VTSServer *server)
+{
+  char **env;
+
+  vts_debug ("Spawned child with pid: %d\n", getpid ());
+
+  if (server->tty != STDIN_FILENO)
+    {
+      if (setsid () < 0)
+        {
+          vts_warning ("Failed to ensure session leadership");
+          exit (EXIT_FAILURE);
+        }
+
+      if (ioctl (server->tty, TIOCSCTTY, 0) < 0)
+        {
+          vts_warning ("Failed to set controlling terminal");
+          exit (EXIT_FAILURE);
+        }
+    }
+
+  close (server->sock[0]);
+
+  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);
+    }
+
+  env = pam_getenvlist (server->ph);
+  if (env)
+    {
+      int i;
+
+      for (i = 0; env[i]; 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)
+{
+#warning "XXX: double check it's safe to trust the SUDO_USER environment variable like this..."
+  /* XXX: if the program was set-uid-root where sudo is not required
+   * could this be abused by a user to choose an arbitrary username to
+   * run as? */
+  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;
+    }
+
+  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);
+
+  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;
+    }
+
+  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;
+
+  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;
+
+  switch ((server->child = fork()))
+    {
+    case -1:
+      set_error_from_errno (error, "Fork failed");
+      goto server_shutdown;
+    case 0:
+      setup_child (server);
+      return vts_client_new (server->sock[1], NULL);
+    default:
+      close (server->sock[1]);
+
+      while (!server->quit)
+        {
+          struct epoll_event ev;
+          int n;
+
+          do {
+            n = epoll_wait (server->epollfd, &ev, 1, -1);
+          } while (n < 0 && errno == EINTR);
+
+          if (n < 0)
+            {
+              set_error_from_errno (error, "epoll_wait failed");
+              goto server_shutdown;
+            }
+
+          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:
+
+#warning "Should we kill(server->child) here?"
+
+  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)));
+    }
+
+  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)
+    {
+      VTSMessage ack;
+
+      if (client->vt_leave_callback)
+        client->vt_leave_callback (client, client->vt_leave_user_data);
+
+      ack.id = VTS_MESSAGE_ID_VT_LEAVE_ACK;
+
+      do {
+        len = send (client->fd, &ack, sizeof ack, 0);
+      } while (len < 0 && errno == EINTR);
+
+      if (len < sizeof ack)
+        {
+          set_error_from_errno (error, "Failed to send VT leave ACK");
+          return FALSE;
+        }
+    }
+  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, -1);
+      } 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;
+        }
+    }
+}
+
+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 0000000..5e4c90a
--- /dev/null
+++ b/deps/vts/vts.h
@@ -0,0 +1,132 @@
+/*
+ * 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..."
+void
+vts_server_set_require_systemd_session (VTSServer *server,
+                                        bool session_required);
+
+void
+vts_server_set_kernel_key_bindings_enabled (VTSServer *server,
+                                            bool enabled);
+
+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);
+
+/* XXX: we should have a separate API for acknowledging a VT leave
+ * request so that a compositor can animate some form of VT switch
+ * transition if desired. */
+#warning "TODO: add a separate vts_client_ack_vt_leave"
+
+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



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]