/* -*- 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;
}
