From 64002e623fea54ab10040206d164c5fdee4a43d2 Mon Sep 17 00:00:00 2001
From: Nirbheek Chauhan <nirbheek@gentoo.org>
Date: Fri, 15 Apr 2011 22:13:44 +0530
Subject: [PATCH] Fix VT grab race with getty causing X to grab the wrong VT

On bootup, if X is spawned without any args, it'll take up the first unused VT.
If GDM starts up before gettys are spawned, X takes up VT1 or VT2 depending on
the init system and bootsplash.

This is problematic because afterwards getty will come up underneath X, and
cause keyboard problems and eventually crash X.

So we read /etc/inittab, check for open VTs, compare the two values, and take
the conservative one.
---
 configure.ac        |    4 ++
 daemon/Makefile.am  |    1 +
 daemon/gdm-server.c |  106 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 110 insertions(+), 1 deletions(-)

diff --git a/configure.ac b/configure.ac
index ca0f8bb..b9e7462 100644
--- a/configure.ac
+++ b/configure.ac
@@ -302,6 +302,10 @@ AC_CHECK_TYPE(socklen_t,,
 AC_CHECK_HEADERS(sys/sockio.h)
 AC_CHECK_FUNCS([setresuid setenv unsetenv clearenv])
 
+dnl Needed for querying the kernel for free VTs
+AC_CHECK_HEADERS(sys/vt.h)
+AC_CHECK_HEADERS(sys/ioctl.h)
+
 dnl checks needed for Darwin compatibility to linux **environ.
 AC_CHECK_HEADERS(crt_externs.h)
 AC_CHECK_FUNCS(_NSGetEnviron)
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index da18835..c1b6bda 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -14,6 +14,7 @@ AM_CPPFLAGS = \
 	-DLIBEXECDIR=\"$(libexecdir)\"			\
 	-DLOGDIR=\"$(logdir)\"				\
 	-DSBINDIR=\"$(sbindir)\"			\
+	-DSYSCONFDIR=\""$(sysconfdir)"\"		\
 	-DGNOMELOCALEDIR=\""$(datadir)/locale"\"	\
 	-DGDM_XAUTH_DIR=\"$(GDM_XAUTH_DIR)\"		\
 	-DGDM_SCREENSHOT_DIR=\"$(GDM_SCREENSHOT_DIR)\"		\
diff --git a/daemon/gdm-server.c b/daemon/gdm-server.c
index 339f3cc..29d16dc 100644
--- a/daemon/gdm-server.c
+++ b/daemon/gdm-server.c
@@ -26,6 +26,8 @@
 #include <unistd.h>
 #include <string.h>
 #include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/vt.h>
 #include <sys/wait.h>
 #include <errno.h>
 #include <ctype.h>
@@ -42,6 +44,7 @@
 #include <glib/gi18n.h>
 #include <glib/gstdio.h>
 #include <glib-object.h>
+#include <gio/gio.h>
 
 #include <X11/Xlib.h> /* for Display */
 
@@ -54,6 +57,8 @@ extern char **environ;
 
 #define GDM_SERVER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_SERVER, GdmServerPrivate))
 
+#define INITTAB SYSCONFDIR"/inittab"
+
 /* These are the servstat values, also used as server
  * process exit codes */
 #define SERVER_TIMEOUT 2        /* Server didn't start */
@@ -674,6 +679,105 @@ gdm_server_spawn (GdmServer  *server,
 }
 
 /**
+ * Parse the inittab file used by getty to spawn VTs to find unused ttys
+ */
+static int
+get_free_vt_from_inittab ()
+{
+        GFile *gfile;
+        GFileInputStream *contents;
+        GDataInputStream *dstream;
+        GRegex *getty;
+        GMatchInfo *tty_match = NULL;
+        GSList *tty_list = NULL;
+        GError *error = NULL;
+        gchar *temp = NULL;
+        int vtno = 0;
+
+        gfile = g_file_new_for_path (INITTAB);
+        contents = g_file_read (gfile, NULL, &error);
+        g_object_unref (gfile);
+        if (!contents) {
+                if (error) {
+                        g_debug ("Unable to open file %s", INITTAB);
+                        g_error_free (error);
+                }
+                goto out;
+        }
+
+        dstream = g_data_input_stream_new (G_INPUT_STREAM (contents));
+        getty = g_regex_new ("^c[0-9]+:.+getty.+tty([0-9]+)", 0, 0, NULL);
+        g_object_unref (contents);
+
+        while (1) {
+                temp = g_data_input_stream_read_line (dstream, NULL, NULL, &error);
+                if (!temp)
+                        break;
+                if (!g_regex_match (getty, temp, 0, &tty_match))
+                        continue;
+                g_free (temp);
+                temp = g_match_info_fetch (tty_match, 1);
+                if (!temp)
+                        continue;
+                tty_list = g_slist_insert_sorted (tty_list, temp, (GCompareFunc)g_strcmp0);
+                g_match_info_free (tty_match);
+        }
+
+        if (error) {
+                g_debug ("Unable to read line from %s", INITTAB);
+                g_error_free (error);
+                goto free;
+        }
+
+        /* Ignore holes in vt allocation, just take the last one */
+        temp = g_slist_last (tty_list)->data;
+        if (temp)
+                vtno = (int) g_ascii_strtoull (temp, NULL, 10) + 1;
+
+free:
+        g_object_unref (dstream);
+        g_regex_unref (getty);
+        g_slist_free_full (tty_list, g_free);
+        g_free (error);
+out:
+        return vtno;
+}
+
+/**
+ * Query the VT_* kernel ioctls to find an empty tty
+ */
+static int
+get_free_vt_from_kernel()
+{
+        int fd, vtno = 0;
+
+        fd = open ("/dev/tty0", O_WRONLY, 0);
+        if ((ioctl(fd, VT_OPENQRY, &vtno) < 0) || (vtno == -1)) {
+                vtno = 0;
+                g_debug ("Unable to find a free vt, falling back to Xorg autodetect");
+        }
+        return vtno;
+}
+
+static gchar*
+get_free_vt ()
+{
+        int inittab_vtno, kernel_vtno;
+        gchar* vt = NULL;
+
+        inittab_vtno = get_free_vt_from_inittab();
+        if (inittab_vtno > 0)
+                g_debug ("Inittab says vt%i is free\n", inittab_vtno);
+        kernel_vtno = get_free_vt_from_kernel();
+        if (kernel_vtno > 0)
+                g_debug ("Kernel says vt%i is free\n", kernel_vtno);
+        /* Select the greater of the two because getty will use the others */
+        if (kernel_vtno != 0 && inittab_vtno != 0)
+                vt = g_strdup_printf ("vt%i", kernel_vtno > inittab_vtno ? kernel_vtno : inittab_vtno);
+        return vt;
+}
+
+/**
  * gdm_server_start:
  * @disp: Pointer to a GdmDisplay structure
  *
@@ -686,7 +790,7 @@ gdm_server_start (GdmServer *server)
         gboolean res;
 
         /* fork X server process */
-        res = gdm_server_spawn (server, NULL);
+        res = gdm_server_spawn (server, get_free_vt());
 
         return res;
 }
-- 
1.7.3.4

