summaryrefslogtreecommitdiffstats
path: root/daemon/gdm-x-session.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemon/gdm-x-session.c')
-rw-r--r--daemon/gdm-x-session.c997
1 files changed, 997 insertions, 0 deletions
diff --git a/daemon/gdm-x-session.c b/daemon/gdm-x-session.c
new file mode 100644
index 0000000..0b07ab5
--- /dev/null
+++ b/daemon/gdm-x-session.c
@@ -0,0 +1,997 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+#include "config.h"
+
+#include <locale.h>
+#include <sysexits.h>
+
+#include "gdm-common.h"
+#include "gdm-settings-direct.h"
+#include "gdm-settings-keys.h"
+#include "gdm-log.h"
+
+#include "gdm-manager-glue.h"
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <glib-unix.h>
+#include <glib.h>
+#include <gio/gunixinputstream.h>
+#include <glib-unix.h>
+#include <X11/Xauth.h>
+
+#define DISPLAY_FILENO (STDERR_FILENO + 1)
+#define BUS_ADDRESS_FILENO (DISPLAY_FILENO + 1)
+
+typedef struct
+{
+ GdmSettings *settings;
+ GCancellable *cancellable;
+
+ GSubprocess *x_subprocess;
+ char *auth_file;
+ char *display_name;
+
+ GSubprocess *bus_subprocess;
+ GDBusConnection *bus_connection;
+ GdmDBusManager *display_manager_proxy;
+ char *bus_address;
+
+ char **environment;
+
+ GSubprocess *session_subprocess;
+ char *session_command;
+ int session_exit_status;
+
+ guint register_session_id;
+
+ GMainLoop *main_loop;
+
+ guint32 debug_enabled : 1;
+} State;
+
+static FILE *
+create_auth_file (char **filename)
+{
+ char *auth_dir = NULL;
+ char *auth_file = NULL;
+ int fd;
+ FILE *fp = NULL;
+
+ auth_dir = g_build_filename (g_get_user_runtime_dir (),
+ "gdm",
+ NULL);
+
+ g_mkdir_with_parents (auth_dir, 0711);
+ auth_file = g_build_filename (auth_dir, "Xauthority", NULL);
+ g_clear_pointer (&auth_dir, g_free);
+
+ fd = g_open (auth_file, O_RDWR | O_CREAT | O_TRUNC, 0700);
+
+ if (fd < 0) {
+ g_debug ("could not open %s to store auth cookie: %m",
+ auth_file);
+ g_clear_pointer (&auth_file, g_free);
+ goto out;
+ }
+
+ fp = fdopen (fd, "w+");
+
+ if (fp == NULL) {
+ g_debug ("could not set up stream for auth cookie file: %m");
+ g_clear_pointer (&auth_file, g_free);
+ close (fd);
+ goto out;
+ }
+
+ *filename = auth_file;
+out:
+ return fp;
+}
+
+static char *
+prepare_auth_file (void)
+{
+ FILE *fp = NULL;
+ char *filename = NULL;
+ GError *error = NULL;
+ gboolean prepared = FALSE;
+ Xauth auth_entry = { 0 };
+ char localhost[HOST_NAME_MAX + 1] = "";
+
+ g_debug ("Preparing auth file for X server");
+
+ fp = create_auth_file (&filename);
+
+ if (fp == NULL) {
+ return NULL;
+ }
+
+ if (gethostname (localhost, HOST_NAME_MAX) < 0) {
+ strncpy (localhost, "localhost", sizeof (localhost) - 1);
+ }
+
+ auth_entry.family = FamilyLocal;
+ auth_entry.address = localhost;
+ auth_entry.address_length = strlen (auth_entry.address);
+ auth_entry.name = "MIT-MAGIC-COOKIE-1";
+ auth_entry.name_length = strlen (auth_entry.name);
+
+ auth_entry.data_length = 16;
+ auth_entry.data = gdm_generate_random_bytes (auth_entry.data_length, &error);
+
+ if (error != NULL) {
+ goto out;
+ }
+
+ if (!XauWriteAuth (fp, &auth_entry) || fflush (fp) == EOF) {
+ goto out;
+ }
+
+ auth_entry.family = FamilyWild;
+ if (!XauWriteAuth (fp, &auth_entry) || fflush (fp) == EOF) {
+ goto out;
+ }
+
+ prepared = TRUE;
+
+out:
+ g_clear_pointer (&auth_entry.data, g_free);
+ g_clear_pointer (&fp, fclose);
+
+ if (!prepared) {
+ g_clear_pointer (&filename, g_free);
+ }
+
+ return filename;
+}
+
+static void
+on_x_server_finished (GSubprocess *subprocess,
+ GAsyncResult *result,
+ State *state)
+{
+ gboolean cancelled;
+
+ cancelled = !g_subprocess_wait_finish (subprocess, result, NULL);
+
+ if (cancelled) {
+ goto out;
+ }
+
+ if (g_subprocess_get_if_exited (subprocess)) {
+ int exit_status;
+
+ exit_status = g_subprocess_get_exit_status (subprocess);
+
+ g_debug ("X server exited with status %d", exit_status);
+ } else {
+ int signal_number;
+
+ signal_number = g_subprocess_get_term_sig (subprocess);
+ g_debug ("X server was killed with status %d", signal_number);
+ }
+
+ g_clear_object (&state->x_subprocess);
+out:
+ g_main_loop_quit (state->main_loop);
+}
+
+static gboolean
+spawn_x_server (State *state,
+ gboolean allow_remote_connections,
+ GCancellable *cancellable)
+{
+ GPtrArray *arguments = NULL;
+ GSubprocessLauncher *launcher = NULL;
+ GSubprocess *subprocess = NULL;
+ GInputStream *input_stream = NULL;
+ GDataInputStream *data_stream = NULL;
+ GError *error = NULL;
+
+ char *auth_file;
+ gboolean is_running = FALSE;
+ int ret;
+ int pipe_fds[2];
+ char *display_fd_string = NULL;
+ char *vt_string = NULL;
+ char *display_number;
+ gsize display_number_size;
+
+ auth_file = prepare_auth_file ();
+
+ g_debug ("Running X server");
+
+ ret = g_unix_open_pipe (pipe_fds, FD_CLOEXEC, &error);
+
+ if (!ret) {
+ g_debug ("could not open pipe: %s", error->message);
+ goto out;
+ }
+
+ arguments = g_ptr_array_new ();
+ launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_INHERIT);
+ g_subprocess_launcher_setenv (launcher, "XORG_RUN_AS_USER_OK", "1", TRUE);
+ g_subprocess_launcher_take_fd (launcher, pipe_fds[1], DISPLAY_FILENO);
+
+ if (g_getenv ("XDG_VTNR") != NULL) {
+ int vt;
+
+ vt = atoi (g_getenv ("XDG_VTNR"));
+
+ if (vt > 0 && vt < 64) {
+ vt_string = g_strdup_printf ("vt%d", vt);
+ }
+ }
+
+ display_fd_string = g_strdup_printf ("%d", DISPLAY_FILENO);
+
+ g_ptr_array_add (arguments, X_SERVER);
+
+ if (vt_string != NULL) {
+ g_ptr_array_add (arguments, vt_string);
+ }
+
+ g_ptr_array_add (arguments, "-displayfd");
+ g_ptr_array_add (arguments, display_fd_string);
+
+ g_ptr_array_add (arguments, "-auth");
+ g_ptr_array_add (arguments, auth_file);
+
+ /* If we were compiled with Xserver >= 1.17 we need to specify
+ * '-listen tcp' as the X server doesn't listen on tcp sockets
+ * by default anymore. In older versions we need to pass
+ * -nolisten tcp to disable listening on tcp sockets.
+ */
+ if (!allow_remote_connections) {
+ g_ptr_array_add (arguments, "-nolisten");
+ g_ptr_array_add (arguments, "tcp");
+ }
+
+#ifdef HAVE_XSERVER_WITH_LISTEN
+ if (allow_remote_connections) {
+ g_ptr_array_add (arguments, "-listen");
+ g_ptr_array_add (arguments, "tcp");
+ }
+#endif
+
+ g_ptr_array_add (arguments, "-background");
+ g_ptr_array_add (arguments, "none");
+
+ g_ptr_array_add (arguments, "-noreset");
+ g_ptr_array_add (arguments, "-keeptty");
+ g_ptr_array_add (arguments, "-novtswitch");
+
+ g_ptr_array_add (arguments, "-verbose");
+ if (state->debug_enabled) {
+ g_ptr_array_add (arguments, "7");
+ } else {
+ g_ptr_array_add (arguments, "3");
+ }
+
+ if (state->debug_enabled) {
+ g_ptr_array_add (arguments, "-core");
+ }
+ g_ptr_array_add (arguments, NULL);
+
+ subprocess = g_subprocess_launcher_spawnv (launcher,
+ (const char * const *) arguments->pdata,
+ &error);
+ g_free (display_fd_string);
+ g_clear_object (&launcher);
+ g_ptr_array_free (arguments, TRUE);
+
+ if (subprocess == NULL) {
+ g_debug ("could not start X server: %s", error->message);
+ goto out;
+ }
+
+ input_stream = g_unix_input_stream_new (pipe_fds[0], TRUE);
+ data_stream = g_data_input_stream_new (input_stream);
+ g_clear_object (&input_stream);
+
+ display_number = g_data_input_stream_read_line (data_stream,
+ &display_number_size,
+ cancellable,
+ &error);
+
+ if (error != NULL) {
+ g_debug ("could not read display string from X server: %s", error->message);
+ goto out;
+ }
+
+ if (display_number == NULL) {
+ g_debug ("X server did not write display string");
+ goto out;
+ }
+
+ state->display_name = g_strdup_printf (":%s", display_number);
+ g_clear_pointer (&display_number, g_free);
+
+ state->auth_file = g_strdup (auth_file);
+ state->x_subprocess = g_object_ref (subprocess);
+
+ g_subprocess_wait_async (state->x_subprocess,
+ cancellable,
+ (GAsyncReadyCallback)
+ on_x_server_finished,
+ state);
+
+ is_running = TRUE;
+out:
+ g_clear_pointer (&auth_file, g_free);
+ g_clear_object (&data_stream);
+ g_clear_object (&subprocess);
+ g_clear_object (&launcher);
+ g_clear_error (&error);
+
+ return is_running;
+}
+
+static void
+on_bus_finished (GSubprocess *subprocess,
+ GAsyncResult *result,
+ State *state)
+{
+ gboolean cancelled;
+
+ cancelled = !g_subprocess_wait_finish (subprocess, result, NULL);
+
+ if (cancelled) {
+ goto out;
+ }
+
+ if (g_subprocess_get_if_exited (subprocess)) {
+ int exit_status;
+
+ exit_status = g_subprocess_get_exit_status (subprocess);
+
+ g_debug ("message bus exited with status %d", exit_status);
+ } else {
+ int signal_number;
+
+ signal_number = g_subprocess_get_term_sig (subprocess);
+ g_debug ("message bus was killed with status %d", signal_number);
+ }
+
+ g_clear_object (&state->bus_subprocess);
+out:
+ g_main_loop_quit (state->main_loop);
+}
+
+static gboolean
+update_bus_environment (State *state,
+ GCancellable *cancellable)
+{
+ GVariantBuilder builder;
+ GVariant *reply = NULL;
+ GError *error = NULL;
+ gboolean environment_updated = FALSE;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
+ g_variant_builder_add (&builder, "{ss}", "DISPLAY", state->display_name);
+ g_variant_builder_add (&builder, "{ss}", "XAUTHORITY", state->auth_file);
+
+ reply = g_dbus_connection_call_sync (state->bus_connection,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "UpdateActivationEnvironment",
+ g_variant_new ("(@a{ss})",
+ g_variant_builder_end (&builder)),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, cancellable, &error);
+
+ if (reply == NULL) {
+ g_debug ("could not update activation environment: %s", error->message);
+ goto out;
+ }
+
+ g_variant_unref (reply);
+
+ environment_updated = TRUE;
+
+out:
+ g_clear_error (&error);
+
+ return environment_updated;
+}
+
+static gboolean
+spawn_bus (State *state,
+ GCancellable *cancellable)
+{
+ GDBusConnection *bus_connection = NULL;
+ GPtrArray *arguments = NULL;
+ GSubprocessLauncher *launcher = NULL;
+ GSubprocess *subprocess = NULL;
+ GInputStream *input_stream = NULL;
+ GDataInputStream *data_stream = NULL;
+ GError *error = NULL;
+ char *bus_address_fd_string;
+ char *bus_address = NULL;
+ gsize bus_address_size;
+
+ gboolean is_running = FALSE;
+ int ret;
+ int pipe_fds[2];
+
+ g_debug ("Running session message bus");
+
+ bus_connection = g_bus_get_sync (G_BUS_TYPE_SESSION,
+ cancellable,
+ NULL);
+
+ if (bus_connection != NULL) {
+ g_debug ("session message bus already running, not starting another one");
+ state->bus_connection = bus_connection;
+ return TRUE;
+ }
+
+ ret = g_unix_open_pipe (pipe_fds, FD_CLOEXEC, &error);
+
+ if (!ret) {
+ g_debug ("could not open pipe: %s", error->message);
+ goto out;
+ }
+
+ arguments = g_ptr_array_new ();
+ launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
+
+ g_subprocess_launcher_take_fd (launcher, pipe_fds[1], BUS_ADDRESS_FILENO);
+
+ bus_address_fd_string = g_strdup_printf ("%d", BUS_ADDRESS_FILENO);
+
+ g_ptr_array_add (arguments, "dbus-daemon");
+
+ g_ptr_array_add (arguments, "--print-address");
+ g_ptr_array_add (arguments, bus_address_fd_string);
+ g_ptr_array_add (arguments, "--session");
+ g_ptr_array_add (arguments, NULL);
+
+ subprocess = g_subprocess_launcher_spawnv (launcher,
+ (const char * const *) arguments->pdata,
+ &error);
+ g_free (bus_address_fd_string);
+ g_clear_object (&launcher);
+ g_ptr_array_free (arguments, TRUE);
+
+ if (subprocess == NULL) {
+ g_debug ("could not start dbus-daemon: %s", error->message);
+ goto out;
+ }
+
+ input_stream = g_unix_input_stream_new (pipe_fds[0], TRUE);
+ data_stream = g_data_input_stream_new (input_stream);
+ g_clear_object (&input_stream);
+
+ bus_address = g_data_input_stream_read_line (data_stream,
+ &bus_address_size,
+ cancellable,
+ &error);
+
+ if (error != NULL) {
+ g_debug ("could not read address from session message bus: %s", error->message);
+ goto out;
+ }
+
+ if (bus_address == NULL) {
+ g_debug ("session message bus did not write address");
+ goto out;
+ }
+
+ state->bus_address = bus_address;
+
+ state->bus_subprocess = g_object_ref (subprocess);
+
+ g_subprocess_wait_async (state->bus_subprocess,
+ cancellable,
+ (GAsyncReadyCallback)
+ on_bus_finished,
+ state);
+
+
+ bus_connection = g_dbus_connection_new_for_address_sync (state->bus_address,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+ G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+ NULL,
+ cancellable,
+ &error);
+
+ if (bus_connection == NULL) {
+ g_debug ("could not open connection to session bus: %s",
+ error->message);
+ goto out;
+ }
+
+ state->bus_connection = bus_connection;
+
+ is_running = TRUE;
+out:
+ g_clear_object (&data_stream);
+ g_clear_object (&subprocess);
+ g_clear_object (&launcher);
+ g_clear_error (&error);
+
+ return is_running;
+}
+
+static gboolean
+import_environment (State *state,
+ GCancellable *cancellable)
+{
+ g_autoptr(GVariant) reply = NULL;
+ g_autoptr(GVariant) environment_variant = NULL;
+ g_autoptr(GError) error = NULL;
+
+ reply = g_dbus_connection_call_sync (state->bus_connection,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)",
+ "org.freedesktop.systemd1.Manager",
+ "Environment"),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, cancellable, &error);
+
+ if (reply == NULL) {
+ g_debug ("could not fetch environment: %s", error->message);
+ return FALSE;
+ }
+
+ g_variant_get (reply, "(v)", &environment_variant);
+
+ state->environment = g_variant_dup_strv (environment_variant, NULL);
+
+ return TRUE;
+}
+
+static void
+on_session_finished (GSubprocess *subprocess,
+ GAsyncResult *result,
+ State *state)
+{
+ gboolean cancelled;
+
+ cancelled = !g_subprocess_wait_finish (subprocess, result, NULL);
+
+ if (cancelled) {
+ goto out;
+ }
+
+ if (g_subprocess_get_if_exited (subprocess)) {
+ int exit_status;
+
+ exit_status = g_subprocess_get_exit_status (subprocess);
+
+ g_debug ("session exited with status %d", exit_status);
+
+ state->session_exit_status = exit_status;
+ } else {
+ int signal_number;
+
+ signal_number = g_subprocess_get_term_sig (subprocess);
+ g_debug ("session was killed with status %d", signal_number);
+ }
+
+ g_clear_object (&state->session_subprocess);
+out:
+ g_main_loop_quit (state->main_loop);
+}
+
+static gboolean
+spawn_session (State *state,
+ gboolean run_script,
+ GCancellable *cancellable)
+{
+ GSubprocessLauncher *launcher = NULL;
+ GSubprocess *subprocess = NULL;
+ GError *error = NULL;
+ gboolean is_running = FALSE;
+ const char *vt;
+ static const char *session_variables[] = { "DISPLAY",
+ "XAUTHORITY",
+ "WAYLAND_DISPLAY",
+ "WAYLAND_SOCKET",
+ "GNOME_SHELL_SESSION_MODE",
+ NULL };
+ /* The environment variables listed below are those we have set (or
+ * received from our own execution environment) only as a fallback to
+ * make things work, as opposed to a information directly pertaining to
+ * the session about to be started. Variables listed here will not
+ * overwrite the existing environment (possibly) imported from the
+ * systemd --user instance.
+ * As an example: We need a PATH for some of the launched subprocesses
+ * to work, but if the user (or the distributor) has customized the PATH
+ * via one of systemds user-environment-generators, that version should
+ * be preferred. */
+ static const char *fallback_variables[] = { "PATH", NULL };
+
+ g_debug ("Running X session");
+
+ launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
+
+ if (state->environment != NULL) {
+ size_t i;
+
+ for (i = 0; state->environment[i] != NULL; i++) {
+ g_auto(GStrv) environment_entry = NULL;
+
+ if (state->environment[i][0] == '\0') {
+ continue;
+ }
+
+ environment_entry = g_strsplit (state->environment[i], "=", 2);
+
+ if (environment_entry[0] == NULL || environment_entry[1] == NULL) {
+ continue;
+ }
+
+ /* Merge the environment block imported from systemd --user with the
+ * environment we have set for ourselves (and thus pass on to the
+ * launcher process). Variables we have set have precedence, as to not
+ * import stale data from prior user sessions, with the exception of
+ * those listed in fallback_variables. See the comment there for more
+ * explanations. */
+ g_subprocess_launcher_setenv (launcher,
+ environment_entry[0],
+ environment_entry[1],
+ g_strv_contains (fallback_variables, environment_entry[0]));
+ }
+
+ /* Don't allow session specific environment variables from earlier sessions to
+ * leak through */
+ for (i = 0; session_variables[i] != NULL; i++) {
+ if (g_getenv (session_variables[i]) == NULL) {
+ g_subprocess_launcher_unsetenv (launcher, session_variables[i]);
+ }
+ }
+ }
+
+ g_subprocess_launcher_setenv (launcher, "DISPLAY", state->display_name, TRUE);
+ g_subprocess_launcher_setenv (launcher, "XAUTHORITY", state->auth_file, TRUE);
+
+ if (state->bus_address != NULL) {
+ g_subprocess_launcher_setenv (launcher, "DBUS_SESSION_BUS_ADDRESS", state->bus_address, TRUE);
+ }
+
+ vt = g_getenv ("XDG_VTNR");
+
+ if (vt != NULL) {
+ g_subprocess_launcher_setenv (launcher, "WINDOWPATH", vt, TRUE);
+ }
+
+ if (run_script) {
+ subprocess = g_subprocess_launcher_spawn (launcher,
+ &error,
+ GDMCONFDIR "/Xsession",
+ state->session_command,
+ NULL);
+ } else {
+ int ret;
+ char **argv;
+
+ ret = g_shell_parse_argv (state->session_command,
+ NULL,
+ &argv,
+ &error);
+
+ if (!ret) {
+ g_debug ("could not parse session arguments: %s", error->message);
+ goto out;
+ }
+ subprocess = g_subprocess_launcher_spawnv (launcher,
+ (const char * const *) argv,
+ &error);
+ g_strfreev (argv);
+ }
+
+ if (subprocess == NULL) {
+ g_debug ("could not start session: %s", error->message);
+ goto out;
+ }
+
+ state->session_subprocess = g_object_ref (subprocess);
+
+ g_subprocess_wait_async (state->session_subprocess,
+ cancellable,
+ (GAsyncReadyCallback)
+ on_session_finished,
+ state);
+
+ is_running = TRUE;
+out:
+ g_clear_object (&subprocess);
+ return is_running;
+}
+
+static void
+signal_subprocesses (State *state)
+{
+ if (state->session_subprocess != NULL) {
+ g_subprocess_send_signal (state->session_subprocess, SIGTERM);
+ }
+
+ if (state->bus_subprocess != NULL) {
+ g_subprocess_send_signal (state->bus_subprocess, SIGTERM);
+ }
+
+ if (state->x_subprocess != NULL) {
+ g_subprocess_send_signal (state->x_subprocess, SIGTERM);
+ }
+}
+
+static void
+wait_on_subprocesses (State *state)
+{
+ if (state->x_subprocess != NULL) {
+ g_subprocess_wait (state->x_subprocess, NULL, NULL);
+ }
+
+ if (state->bus_subprocess != NULL) {
+ g_subprocess_wait (state->bus_subprocess, NULL, NULL);
+ }
+
+ if (state->session_subprocess != NULL) {
+ g_subprocess_wait (state->session_subprocess, NULL, NULL);
+ }
+}
+
+static gboolean
+register_display (State *state,
+ GCancellable *cancellable)
+{
+ GError *error = NULL;
+ gboolean registered = FALSE;
+ GVariantBuilder details;
+
+ g_variant_builder_init (&details, G_VARIANT_TYPE ("a{ss}"));
+ g_variant_builder_add (&details, "{ss}", "session-type", "x11");
+ g_variant_builder_add (&details, "{ss}", "x11-display-name", state->display_name);
+
+ registered = gdm_dbus_manager_call_register_display_sync (state->display_manager_proxy,
+ g_variant_builder_end (&details),
+ cancellable,
+ &error);
+ if (error != NULL) {
+ g_debug ("Could not register display: %s", error->message);
+ g_error_free (error);
+ }
+
+ return registered;
+}
+
+static void
+init_state (State **state)
+{
+ static State state_allocation;
+
+ *state = &state_allocation;
+}
+
+static void
+clear_state (State **out_state)
+{
+ State *state = *out_state;
+
+ g_clear_object (&state->cancellable);
+ g_clear_object (&state->bus_connection);
+ g_clear_object (&state->session_subprocess);
+ g_clear_object (&state->x_subprocess);
+ g_clear_pointer (&state->environment, g_strfreev);
+ g_clear_pointer (&state->auth_file, g_free);
+ g_clear_pointer (&state->display_name, g_free);
+ g_clear_pointer (&state->main_loop, g_main_loop_unref);
+ g_clear_handle_id (&state->register_session_id, g_source_remove);
+ *out_state = NULL;
+}
+
+static gboolean
+on_sigterm (State *state)
+{
+ g_cancellable_cancel (state->cancellable);
+
+ if (g_main_loop_is_running (state->main_loop)) {
+ g_main_loop_quit (state->main_loop);
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+register_session_timeout_cb (gpointer user_data)
+{
+ State *state;
+ GError *error = NULL;
+
+ state = (State *) user_data;
+
+ gdm_dbus_manager_call_register_session_sync (state->display_manager_proxy,
+ g_variant_new ("a{sv}", NULL),
+ state->cancellable,
+ &error);
+
+ if (error != NULL) {
+ g_warning ("Could not register session: %s", error->message);
+ g_error_free (error);
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+connect_to_display_manager (State *state)
+{
+ g_autoptr (GError) error = NULL;
+
+ state->display_manager_proxy = gdm_dbus_manager_proxy_new_for_bus_sync (
+ G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+ "org.gnome.DisplayManager",
+ "/org/gnome/DisplayManager/Manager",
+ state->cancellable,
+ &error);
+
+ if (state->display_manager_proxy == NULL) {
+ g_printerr ("gdm-x-session: could not contact display manager: %s\n",
+ error->message);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ State *state = NULL;
+ GOptionContext *context = NULL;
+ static char **args = NULL;
+ static gboolean run_script = FALSE;
+ static gboolean allow_remote_connections = FALSE;
+ gboolean debug = FALSE;
+ gboolean ret;
+ int exit_status = EX_OK;
+ static gboolean register_session = FALSE;
+
+ static GOptionEntry entries [] = {
+ { "run-script", 'r', 0, G_OPTION_ARG_NONE, &run_script, N_("Run program through /etc/gdm/Xsession wrapper script"), NULL },
+ { "allow-remote-connections", 'a', 0, G_OPTION_ARG_NONE, &allow_remote_connections, N_("Listen on TCP socket"), NULL },
+ { "register-session", 0, 0, G_OPTION_ARG_NONE, &register_session, "Register session after a delay", NULL },
+ { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args, "", "" },
+ { NULL }
+ };
+
+ bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+ textdomain (GETTEXT_PACKAGE);
+ setlocale (LC_ALL, "");
+
+ gdm_log_init ();
+
+ context = g_option_context_new (_("GNOME Display Manager X Session Launcher"));
+ g_option_context_add_main_entries (context, entries, NULL);
+
+ g_option_context_parse (context, &argc, &argv, NULL);
+ g_option_context_free (context);
+
+ if (args == NULL || args[0] == NULL || args[1] != NULL) {
+ g_warning ("gdm-x-session takes one argument (the session)");
+ exit_status = EX_USAGE;
+ goto out;
+ }
+
+ init_state (&state);
+
+ state->session_command = args[0];
+
+ state->settings = gdm_settings_new ();
+ ret = gdm_settings_direct_init (state->settings, DATADIR "/gdm/gdm.schemas", "/");
+
+ if (!ret) {
+ g_printerr ("Unable to initialize settings\n");
+ exit_status = EX_DATAERR;
+ goto out;
+ }
+
+ gdm_settings_direct_get_boolean (GDM_KEY_DEBUG, &debug);
+ state->debug_enabled = debug;
+
+ gdm_log_set_debug (debug);
+
+ state->main_loop = g_main_loop_new (NULL, FALSE);
+ state->cancellable = g_cancellable_new ();
+
+ g_unix_signal_add (SIGTERM, (GSourceFunc) on_sigterm, state);
+
+ ret = spawn_x_server (state, allow_remote_connections, state->cancellable);
+
+ if (!ret) {
+ g_printerr ("Unable to run X server\n");
+ exit_status = EX_SOFTWARE;
+ goto out;
+ }
+
+ ret = spawn_bus (state, state->cancellable);
+
+ if (!ret) {
+ g_printerr ("Unable to run session message bus\n");
+ exit_status = EX_SOFTWARE;
+ goto out;
+ }
+
+ import_environment (state, state->cancellable);
+
+ ret = update_bus_environment (state, state->cancellable);
+
+ if (!ret) {
+ g_printerr ("Unable to update bus environment\n");
+ exit_status = EX_SOFTWARE;
+ goto out;
+ }
+
+ if (!connect_to_display_manager (state))
+ goto out;
+
+ ret = register_display (state, state->cancellable);
+
+ if (!ret) {
+ g_printerr ("Unable to register display with display manager\n");
+ exit_status = EX_SOFTWARE;
+ goto out;
+ }
+
+ ret = spawn_session (state, run_script, state->cancellable);
+
+ if (!ret) {
+ g_printerr ("Unable to run session\n");
+ exit_status = EX_SOFTWARE;
+ goto out;
+ }
+
+ if (register_session) {
+ g_debug ("gdm-x-session: Will register session in %d seconds", REGISTER_SESSION_TIMEOUT);
+ state->register_session_id = g_timeout_add_seconds (REGISTER_SESSION_TIMEOUT,
+ register_session_timeout_cb,
+ state);
+ } else {
+ g_debug ("gdm-x-session: Session will register itself");
+ }
+
+ g_main_loop_run (state->main_loop);
+
+ /* Only use exit status of session if we're here because it exit */
+
+ if (state->session_subprocess == NULL) {
+ exit_status = state->session_exit_status;
+ }
+
+out:
+ if (state != NULL) {
+ signal_subprocesses (state);
+ wait_on_subprocesses (state);
+ clear_state (&state);
+ }
+
+ return exit_status;
+}