1089 lines
34 KiB
C
1089 lines
34 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
|
||
*
|
||
* Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
|
||
*
|
||
* 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 of the License, 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 <stdlib.h>
|
||
#include <stdio.h>
|
||
#include <fcntl.h>
|
||
#include <unistd.h>
|
||
#include <string.h>
|
||
#include <sys/types.h>
|
||
#include <sys/wait.h>
|
||
#include <errno.h>
|
||
#include <ctype.h>
|
||
#include <pwd.h>
|
||
#include <grp.h>
|
||
#include <signal.h>
|
||
#include <sys/ioctl.h>
|
||
#include <sys/resource.h>
|
||
|
||
#ifdef HAVE_SYS_PRCTL_H
|
||
#include <sys/prctl.h>
|
||
#endif
|
||
|
||
#ifdef WITH_PLYMOUTH
|
||
#include <linux/vt.h>
|
||
#endif
|
||
|
||
#include <systemd/sd-daemon.h>
|
||
|
||
#ifdef ENABLE_SYSTEMD_JOURNAL
|
||
#include <systemd/sd-journal.h>
|
||
#endif
|
||
|
||
#include <glib/gi18n.h>
|
||
#include <glib/gstdio.h>
|
||
#include <gio/gio.h>
|
||
|
||
#ifdef ENABLE_X11_SUPPORT
|
||
#include <X11/Xlib.h> /* for Display */
|
||
#endif
|
||
|
||
#include "gdm-common.h"
|
||
#include "gdm-settings-direct.h"
|
||
#include "gdm-settings-keys.h"
|
||
|
||
#include "gdm-server.h"
|
||
|
||
extern char **environ;
|
||
|
||
#define MAX_LOGS 5
|
||
|
||
struct _GdmServer
|
||
{
|
||
GObject parent;
|
||
|
||
char *command;
|
||
GPid pid;
|
||
|
||
gboolean disable_tcp;
|
||
int priority;
|
||
char *user_name;
|
||
char *session_args;
|
||
|
||
char *log_dir;
|
||
char *display_name;
|
||
char *display_device;
|
||
char *display_seat_id;
|
||
char *auth_file;
|
||
|
||
guint child_watch_id;
|
||
|
||
gboolean is_initial;
|
||
};
|
||
|
||
enum {
|
||
PROP_0,
|
||
PROP_DISPLAY_NAME,
|
||
PROP_DISPLAY_SEAT_ID,
|
||
PROP_DISPLAY_DEVICE,
|
||
PROP_AUTH_FILE,
|
||
PROP_USER_NAME,
|
||
PROP_DISABLE_TCP,
|
||
PROP_IS_INITIAL,
|
||
};
|
||
|
||
enum {
|
||
READY,
|
||
EXITED,
|
||
DIED,
|
||
LAST_SIGNAL
|
||
};
|
||
|
||
static guint signals [LAST_SIGNAL] = { 0, };
|
||
|
||
static void gdm_server_class_init (GdmServerClass *klass);
|
||
static void gdm_server_init (GdmServer *server);
|
||
static void gdm_server_finalize (GObject *object);
|
||
|
||
G_DEFINE_TYPE (GdmServer, gdm_server, G_TYPE_OBJECT)
|
||
|
||
char *
|
||
gdm_server_get_display_device (GdmServer *server)
|
||
{
|
||
/* systemd finds the display device out on its own based on the display */
|
||
return NULL;
|
||
}
|
||
|
||
static void
|
||
gdm_server_ready (GdmServer *server)
|
||
{
|
||
g_debug ("GdmServer: Got USR1 from X server - emitting READY");
|
||
|
||
gdm_run_script (GDMCONFDIR "/Init", GDM_USERNAME,
|
||
server->display_name,
|
||
NULL, /* hostname */
|
||
server->auth_file);
|
||
|
||
g_signal_emit (server, signals[READY], 0);
|
||
}
|
||
|
||
static GSList *active_servers;
|
||
static gboolean sigusr1_thread_running;
|
||
static GCond sigusr1_thread_cond;
|
||
static GMutex sigusr1_thread_mutex;
|
||
|
||
static gboolean
|
||
got_sigusr1 (gpointer user_data)
|
||
{
|
||
GPid pid = GPOINTER_TO_UINT (user_data);
|
||
GSList *l;
|
||
|
||
g_debug ("GdmServer: got SIGUSR1 from PID %d", pid);
|
||
|
||
for (l = active_servers; l; l = l->next) {
|
||
GdmServer *server = l->data;
|
||
|
||
if (server->pid == pid)
|
||
gdm_server_ready (server);
|
||
}
|
||
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
static gpointer
|
||
sigusr1_thread_main (gpointer user_data)
|
||
{
|
||
sigset_t sigusr1_mask;
|
||
|
||
/* Handle only SIGUSR1 */
|
||
sigemptyset (&sigusr1_mask);
|
||
sigaddset (&sigusr1_mask, SIGUSR1);
|
||
sigprocmask (SIG_SETMASK, &sigusr1_mask, NULL);
|
||
|
||
g_mutex_lock (&sigusr1_thread_mutex);
|
||
sigusr1_thread_running = TRUE;
|
||
g_cond_signal (&sigusr1_thread_cond);
|
||
g_mutex_unlock (&sigusr1_thread_mutex);
|
||
|
||
/* Spin waiting for a SIGUSR1 */
|
||
while (TRUE) {
|
||
siginfo_t info;
|
||
|
||
if (sigwaitinfo (&sigusr1_mask, &info) == -1)
|
||
continue;
|
||
|
||
g_idle_add (got_sigusr1, GUINT_TO_POINTER (info.si_pid));
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static void
|
||
gdm_server_launch_sigusr1_thread_if_needed (void)
|
||
{
|
||
static GThread *sigusr1_thread;
|
||
|
||
if (sigusr1_thread == NULL) {
|
||
sigusr1_thread = g_thread_new ("gdm SIGUSR1 catcher", sigusr1_thread_main, NULL);
|
||
|
||
g_mutex_lock (&sigusr1_thread_mutex);
|
||
while (!sigusr1_thread_running)
|
||
g_cond_wait (&sigusr1_thread_cond, &sigusr1_thread_mutex);
|
||
g_mutex_unlock (&sigusr1_thread_mutex);
|
||
}
|
||
}
|
||
|
||
static void
|
||
gdm_server_init_command (GdmServer *server)
|
||
{
|
||
gboolean debug = FALSE;
|
||
const char *debug_options;
|
||
const char *verbosity = "";
|
||
|
||
if (server->command != NULL) {
|
||
return;
|
||
}
|
||
|
||
gdm_settings_direct_get_boolean (GDM_KEY_DEBUG, &debug);
|
||
if (debug) {
|
||
debug_options = " -logverbose 7 -core ";
|
||
} else {
|
||
debug_options = "";
|
||
}
|
||
|
||
#define X_SERVER_ARG_FORMAT " -background none -noreset -verbose %s%s"
|
||
|
||
/* This is a temporary hack to work around the fact that XOrg
|
||
* currently lacks support for multi-seat hotplugging for
|
||
* display devices. This bit should be removed as soon as XOrg
|
||
* gains native support for automatically enumerating usb
|
||
* based graphics adapters at start-up via udev. */
|
||
|
||
/* systemd ships an X server wrapper tool which simply invokes
|
||
* the usual X but ensures it only uses the display devices of
|
||
* the seat. */
|
||
|
||
/* We do not rely on this wrapper server if, a) the machine
|
||
* wasn't booted using systemd, or b) the wrapper tool is
|
||
* missing, or c) we are running for the main seat 'seat0'. */
|
||
|
||
#ifdef ENABLE_SYSTEMD_JOURNAL
|
||
/* For systemd, we don't have a log file but instead log to stdout,
|
||
so set it to the xserver's built-in default verbosity */
|
||
if (debug)
|
||
verbosity = "7 -logfile /dev/null";
|
||
else
|
||
verbosity = "3 -logfile /dev/null";
|
||
#endif
|
||
|
||
if (g_access (SYSTEMD_X_SERVER, X_OK) < 0) {
|
||
goto fallback;
|
||
}
|
||
|
||
if (server->display_seat_id == NULL ||
|
||
strcmp (server->display_seat_id, "seat0") == 0) {
|
||
goto fallback;
|
||
}
|
||
|
||
server->command = g_strdup_printf (SYSTEMD_X_SERVER X_SERVER_ARG_FORMAT, verbosity, debug_options);
|
||
return;
|
||
|
||
fallback:
|
||
server->command = g_strdup_printf (X_SERVER X_SERVER_ARG_FORMAT, verbosity, debug_options);
|
||
|
||
}
|
||
|
||
static gboolean
|
||
gdm_server_resolve_command_line (GdmServer *server,
|
||
const char *vtarg,
|
||
int *argcp,
|
||
char ***argvp)
|
||
{
|
||
int argc;
|
||
char **argv;
|
||
int len;
|
||
int i;
|
||
gboolean gotvtarg = FALSE;
|
||
gboolean query_in_arglist = FALSE;
|
||
|
||
gdm_server_init_command (server);
|
||
|
||
if (!g_shell_parse_argv (server->command, &argc, &argv, NULL))
|
||
return FALSE;
|
||
|
||
for (len = 0; argv != NULL && argv[len] != NULL; len++) {
|
||
char *arg = argv[len];
|
||
|
||
/* HACK! Not to add vt argument to servers that already force
|
||
* allocation. Mostly for backwards compat only */
|
||
if (strncmp (arg, "vt", 2) == 0 &&
|
||
isdigit (arg[2]) &&
|
||
(arg[3] == '\0' ||
|
||
(isdigit (arg[3]) && arg[4] == '\0')))
|
||
gotvtarg = TRUE;
|
||
if (strcmp (arg, "-query") == 0 ||
|
||
strcmp (arg, "-indirect") == 0)
|
||
query_in_arglist = TRUE;
|
||
}
|
||
|
||
argv = g_renew (char *, argv, len + 12);
|
||
/* shift args down one */
|
||
for (i = len - 1; i >= 1; i--) {
|
||
argv[i+1] = argv[i];
|
||
}
|
||
|
||
/* server number is the FIRST argument, before any others */
|
||
argv[1] = g_strdup (server->display_name);
|
||
len++;
|
||
|
||
if (server->auth_file != NULL) {
|
||
argv[len++] = g_strdup ("-auth");
|
||
argv[len++] = g_strdup (server->auth_file);
|
||
}
|
||
|
||
if (server->display_seat_id != NULL) {
|
||
argv[len++] = g_strdup ("-seat");
|
||
argv[len++] = g_strdup (server->display_seat_id);
|
||
}
|
||
|
||
/* If we were compiled with Xserver >= 1.17 we need to specify
|
||
* '-listen tcp' as the X server dosen't listen on tcp sockets
|
||
* by default anymore. In older versions we need to pass
|
||
* -nolisten tcp to disable listening on tcp sockets.
|
||
*/
|
||
if (!query_in_arglist) {
|
||
if (server->disable_tcp) {
|
||
argv[len++] = g_strdup ("-nolisten");
|
||
argv[len++] = g_strdup ("tcp");
|
||
}
|
||
|
||
#ifdef HAVE_XSERVER_WITH_LISTEN
|
||
if (!server->disable_tcp) {
|
||
argv[len++] = g_strdup ("-listen");
|
||
argv[len++] = g_strdup ("tcp");
|
||
}
|
||
#endif
|
||
}
|
||
|
||
if (vtarg != NULL && ! gotvtarg) {
|
||
argv[len++] = g_strdup (vtarg);
|
||
}
|
||
|
||
argv[len++] = NULL;
|
||
|
||
*argvp = argv;
|
||
*argcp = len;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static void
|
||
rotate_logs (const char *path,
|
||
guint n_copies)
|
||
{
|
||
int i;
|
||
|
||
for (i = n_copies - 1; i > 0; i--) {
|
||
char *name_n;
|
||
char *name_n1;
|
||
|
||
name_n = g_strdup_printf ("%s.%d", path, i);
|
||
if (i > 1) {
|
||
name_n1 = g_strdup_printf ("%s.%d", path, i - 1);
|
||
} else {
|
||
name_n1 = g_strdup (path);
|
||
}
|
||
|
||
VE_IGNORE_EINTR (g_unlink (name_n));
|
||
VE_IGNORE_EINTR (g_rename (name_n1, name_n));
|
||
|
||
g_free (name_n1);
|
||
g_free (name_n);
|
||
}
|
||
|
||
VE_IGNORE_EINTR (g_unlink (path));
|
||
}
|
||
|
||
static void
|
||
change_user (GdmServer *server)
|
||
{
|
||
struct passwd *pwent;
|
||
|
||
if (server->user_name == NULL) {
|
||
return;
|
||
}
|
||
|
||
gdm_get_pwent_for_name (server->user_name, &pwent);
|
||
if (pwent == NULL) {
|
||
g_warning (_("Server was to be spawned by user %s but that user doesn’t exist"),
|
||
server->user_name);
|
||
_exit (EXIT_FAILURE);
|
||
}
|
||
|
||
g_debug ("GdmServer: Changing (uid:gid) for child process to (%d:%d)",
|
||
pwent->pw_uid,
|
||
pwent->pw_gid);
|
||
|
||
if (pwent->pw_uid != 0) {
|
||
if (setgid (pwent->pw_gid) < 0) {
|
||
g_warning (_("Couldn’t set groupid to %d"),
|
||
pwent->pw_gid);
|
||
_exit (EXIT_FAILURE);
|
||
}
|
||
|
||
if (initgroups (pwent->pw_name, pwent->pw_gid) < 0) {
|
||
g_warning (_("initgroups () failed for %s"),
|
||
pwent->pw_name);
|
||
_exit (EXIT_FAILURE);
|
||
}
|
||
|
||
if (setuid (pwent->pw_uid) < 0) {
|
||
g_warning (_("Couldn’t set userid to %d"),
|
||
(int)pwent->pw_uid);
|
||
_exit (EXIT_FAILURE);
|
||
}
|
||
} else {
|
||
gid_t groups[1] = { 0 };
|
||
|
||
if (setgid (0) < 0) {
|
||
g_warning (_("Couldn’t set groupid to %d"), 0);
|
||
/* Don't error out, it's not fatal, if it fails we'll
|
||
* just still be */
|
||
}
|
||
|
||
/* this will get rid of any suplementary groups etc... */
|
||
setgroups (1, groups);
|
||
}
|
||
}
|
||
|
||
static gboolean
|
||
gdm_server_setup_journal_fds (GdmServer *server)
|
||
{
|
||
#ifdef ENABLE_SYSTEMD_JOURNAL
|
||
if (sd_booted () > 0) {
|
||
int out, err;
|
||
g_autofree char *identifier = NULL;
|
||
|
||
identifier = g_strdup_printf("gdm-Xorg-%s", server->display_name);
|
||
|
||
out = sd_journal_stream_fd (identifier, LOG_INFO, FALSE);
|
||
if (out < 0)
|
||
return FALSE;
|
||
|
||
err = sd_journal_stream_fd (identifier, LOG_WARNING, FALSE);
|
||
if (err < 0) {
|
||
close (out);
|
||
return FALSE;
|
||
}
|
||
|
||
VE_IGNORE_EINTR (dup2 (out, 1));
|
||
VE_IGNORE_EINTR (dup2 (err, 2));
|
||
return TRUE;
|
||
}
|
||
#endif
|
||
return FALSE;
|
||
}
|
||
|
||
static void
|
||
gdm_server_setup_logfile (GdmServer *server)
|
||
{
|
||
int logfd;
|
||
char *log_file;
|
||
char *log_path;
|
||
|
||
log_file = g_strdup_printf ("%s.log", server->display_name);
|
||
log_path = g_build_filename (server->log_dir, log_file, NULL);
|
||
g_free (log_file);
|
||
|
||
/* Rotate the X server logs */
|
||
rotate_logs (log_path, MAX_LOGS);
|
||
|
||
g_debug ("GdmServer: Opening logfile for server %s", log_path);
|
||
|
||
VE_IGNORE_EINTR (g_unlink (log_path));
|
||
VE_IGNORE_EINTR (logfd = open (log_path, O_CREAT|O_APPEND|O_TRUNC|O_WRONLY|O_EXCL, 0644));
|
||
|
||
g_free (log_path);
|
||
|
||
if (logfd != -1) {
|
||
VE_IGNORE_EINTR (dup2 (logfd, 1));
|
||
VE_IGNORE_EINTR (dup2 (logfd, 2));
|
||
close (logfd);
|
||
} else {
|
||
g_warning (_("%s: Could not open log file for display %s!"),
|
||
"gdm_server_spawn",
|
||
server->display_name);
|
||
}
|
||
}
|
||
|
||
static void
|
||
server_child_setup (GdmServer *server)
|
||
{
|
||
struct sigaction ign_signal;
|
||
sigset_t mask;
|
||
|
||
if (!gdm_server_setup_journal_fds(server))
|
||
gdm_server_setup_logfile(server);
|
||
|
||
/* The X server expects USR1/TTIN/TTOU to be SIG_IGN */
|
||
ign_signal.sa_handler = SIG_IGN;
|
||
ign_signal.sa_flags = SA_RESTART;
|
||
sigemptyset (&ign_signal.sa_mask);
|
||
|
||
if (sigaction (SIGUSR1, &ign_signal, NULL) < 0) {
|
||
g_warning (_("%s: Error setting %s to %s"),
|
||
"gdm_server_spawn", "USR1", "SIG_IGN");
|
||
_exit (EXIT_FAILURE);
|
||
}
|
||
|
||
if (sigaction (SIGTTIN, &ign_signal, NULL) < 0) {
|
||
g_warning (_("%s: Error setting %s to %s"),
|
||
"gdm_server_spawn", "TTIN", "SIG_IGN");
|
||
_exit (EXIT_FAILURE);
|
||
}
|
||
|
||
if (sigaction (SIGTTOU, &ign_signal, NULL) < 0) {
|
||
g_warning (_("%s: Error setting %s to %s"),
|
||
"gdm_server_spawn", "TTOU", "SIG_IGN");
|
||
_exit (EXIT_FAILURE);
|
||
}
|
||
|
||
/* And HUP and TERM are at SIG_DFL from gdm_unset_signals,
|
||
we also have an empty mask and all that fun stuff */
|
||
|
||
/* unblock signals (especially HUP/TERM/USR1) so that we
|
||
* can control the X server */
|
||
sigemptyset (&mask);
|
||
sigprocmask (SIG_SETMASK, &mask, NULL);
|
||
|
||
/* Terminate the process when the parent dies */
|
||
#ifdef HAVE_SYS_PRCTL_H
|
||
prctl (PR_SET_PDEATHSIG, SIGTERM);
|
||
#endif
|
||
|
||
if (server->priority != 0) {
|
||
if (setpriority (PRIO_PROCESS, 0, server->priority)) {
|
||
g_warning (_("%s: Server priority couldn’t be set to %d: %s"),
|
||
"gdm_server_spawn",
|
||
server->priority,
|
||
g_strerror (errno));
|
||
}
|
||
}
|
||
|
||
setpgid (0, 0);
|
||
|
||
change_user (server);
|
||
}
|
||
|
||
static void
|
||
listify_hash (const char *key,
|
||
const char *value,
|
||
GPtrArray *env)
|
||
{
|
||
char *str;
|
||
str = g_strdup_printf ("%s=%s", key, value);
|
||
g_ptr_array_add (env, str);
|
||
}
|
||
|
||
static GPtrArray *
|
||
get_server_environment (GdmServer *server)
|
||
{
|
||
GPtrArray *env;
|
||
char **l;
|
||
GHashTable *hash;
|
||
|
||
env = g_ptr_array_new ();
|
||
|
||
/* create a hash table of current environment, then update keys has necessary */
|
||
hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
||
for (l = environ; *l != NULL; l++) {
|
||
char **str;
|
||
str = g_strsplit (*l, "=", 2);
|
||
g_hash_table_insert (hash, str[0], str[1]);
|
||
g_free (str);
|
||
}
|
||
|
||
/* modify environment here */
|
||
g_hash_table_insert (hash, g_strdup ("DISPLAY"), g_strdup (server->display_name));
|
||
|
||
if (server->user_name != NULL) {
|
||
struct passwd *pwent;
|
||
|
||
gdm_get_pwent_for_name (server->user_name, &pwent);
|
||
|
||
if (pwent->pw_dir != NULL
|
||
&& g_file_test (pwent->pw_dir, G_FILE_TEST_EXISTS)) {
|
||
g_hash_table_insert (hash, g_strdup ("HOME"), g_strdup (pwent->pw_dir));
|
||
} else {
|
||
/* Hack */
|
||
g_hash_table_insert (hash, g_strdup ("HOME"), g_strdup ("/"));
|
||
}
|
||
g_hash_table_insert (hash, g_strdup ("SHELL"), g_strdup (pwent->pw_shell));
|
||
g_hash_table_remove (hash, "MAIL");
|
||
}
|
||
|
||
g_hash_table_foreach (hash, (GHFunc)listify_hash, env);
|
||
g_hash_table_destroy (hash);
|
||
|
||
g_ptr_array_add (env, NULL);
|
||
|
||
return env;
|
||
}
|
||
|
||
static void
|
||
server_add_xserver_args (GdmServer *server,
|
||
int *argc,
|
||
char ***argv)
|
||
{
|
||
int count;
|
||
char **args;
|
||
int len;
|
||
int i;
|
||
|
||
len = *argc;
|
||
g_shell_parse_argv (server->session_args, &count, &args, NULL);
|
||
*argv = g_renew (char *, *argv, len + count + 1);
|
||
|
||
for (i=0; i < count;i++) {
|
||
*argv[len++] = g_strdup (args[i]);
|
||
}
|
||
|
||
*argc += count;
|
||
|
||
argv[len] = NULL;
|
||
g_strfreev (args);
|
||
}
|
||
|
||
static void
|
||
server_child_watch (GPid pid,
|
||
int status,
|
||
GdmServer *server)
|
||
{
|
||
g_debug ("GdmServer: child (pid:%d) done (%s:%d)",
|
||
(int) pid,
|
||
WIFEXITED (status) ? "status"
|
||
: WIFSIGNALED (status) ? "signal"
|
||
: "unknown",
|
||
WIFEXITED (status) ? WEXITSTATUS (status)
|
||
: WIFSIGNALED (status) ? WTERMSIG (status)
|
||
: -1);
|
||
|
||
g_object_ref (server);
|
||
|
||
if (WIFEXITED (status)) {
|
||
int code = WEXITSTATUS (status);
|
||
g_signal_emit (server, signals [EXITED], 0, code);
|
||
} else if (WIFSIGNALED (status)) {
|
||
int num = WTERMSIG (status);
|
||
g_signal_emit (server, signals [DIED], 0, num);
|
||
}
|
||
|
||
g_spawn_close_pid (server->pid);
|
||
server->pid = -1;
|
||
|
||
g_object_unref (server);
|
||
}
|
||
|
||
static void
|
||
prune_active_servers_list (GdmServer *server)
|
||
{
|
||
active_servers = g_slist_remove (active_servers, server);
|
||
}
|
||
|
||
static gboolean
|
||
gdm_server_spawn (GdmServer *server,
|
||
const char *vtarg,
|
||
GError **error)
|
||
{
|
||
int argc;
|
||
gchar **argv = NULL;
|
||
GPtrArray *env = NULL;
|
||
gboolean ret = FALSE;
|
||
char *freeme;
|
||
|
||
/* Figure out the server command */
|
||
argv = NULL;
|
||
argc = 0;
|
||
|
||
if (!gdm_server_resolve_command_line (server,
|
||
vtarg,
|
||
&argc,
|
||
&argv)) {
|
||
return FALSE;
|
||
}
|
||
|
||
if (server->session_args) {
|
||
server_add_xserver_args (server, &argc, &argv);
|
||
}
|
||
|
||
if (argv[0] == NULL) {
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||
_("%s: Empty server command for display %s"),
|
||
"gdm_server_spawn",
|
||
server->display_name);
|
||
goto out;
|
||
}
|
||
|
||
env = get_server_environment (server);
|
||
|
||
freeme = g_strjoinv (" ", argv);
|
||
g_debug ("GdmServer: Starting X server process: %s", freeme);
|
||
g_free (freeme);
|
||
|
||
active_servers = g_slist_append (active_servers, server);
|
||
|
||
g_object_weak_ref (G_OBJECT (server),
|
||
(GWeakNotify)
|
||
prune_active_servers_list,
|
||
server);
|
||
|
||
gdm_server_launch_sigusr1_thread_if_needed ();
|
||
|
||
if (!g_spawn_async_with_pipes (NULL,
|
||
argv,
|
||
(char **)env->pdata,
|
||
G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
|
||
(GSpawnChildSetupFunc)server_child_setup,
|
||
server,
|
||
&server->pid,
|
||
NULL,
|
||
NULL,
|
||
NULL,
|
||
error))
|
||
goto out;
|
||
|
||
g_debug ("GdmServer: Started X server process %d - waiting for READY", (int)server->pid);
|
||
|
||
server->child_watch_id = g_child_watch_add (server->pid,
|
||
(GChildWatchFunc)server_child_watch,
|
||
server);
|
||
|
||
ret = TRUE;
|
||
out:
|
||
g_strfreev (argv);
|
||
if (env) {
|
||
g_ptr_array_foreach (env, (GFunc)g_free, NULL);
|
||
g_ptr_array_free (env, TRUE);
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* gdm_server_start:
|
||
* @disp: Pointer to a GdmDisplay structure
|
||
*
|
||
* Starts a local X server. Handles retries and fatal errors properly.
|
||
*/
|
||
|
||
gboolean
|
||
gdm_server_start (GdmServer *server)
|
||
{
|
||
gboolean res = FALSE;
|
||
const char *vtarg = NULL;
|
||
GError *local_error = NULL;
|
||
GError **error = &local_error;
|
||
|
||
g_return_val_if_fail (GDM_IS_SERVER (server), FALSE);
|
||
|
||
/* Hardcode the VT for the initial X server, but nothing else */
|
||
if (server->is_initial) {
|
||
vtarg = "vt" G_STRINGIFY (GDM_INITIAL_VT);
|
||
}
|
||
|
||
/* fork X server process */
|
||
if (!gdm_server_spawn (server, vtarg, error)) {
|
||
goto out;
|
||
}
|
||
|
||
res = TRUE;
|
||
out:
|
||
if (local_error) {
|
||
g_printerr ("%s\n", local_error->message);
|
||
g_clear_error (&local_error);
|
||
}
|
||
return res;
|
||
}
|
||
|
||
static void
|
||
server_died (GdmServer *server)
|
||
{
|
||
int exit_status;
|
||
|
||
g_debug ("GdmServer: Waiting on process %d", server->pid);
|
||
exit_status = gdm_wait_on_pid (server->pid);
|
||
|
||
if (WIFEXITED (exit_status) && (WEXITSTATUS (exit_status) != 0)) {
|
||
g_debug ("GdmServer: Wait on child process failed");
|
||
} else {
|
||
/* exited normally */
|
||
}
|
||
|
||
g_spawn_close_pid (server->pid);
|
||
server->pid = -1;
|
||
|
||
if (server->display_device != NULL) {
|
||
g_free (server->display_device);
|
||
server->display_device = NULL;
|
||
g_object_notify (G_OBJECT (server), "display-device");
|
||
}
|
||
|
||
g_debug ("GdmServer: Server died");
|
||
}
|
||
|
||
gboolean
|
||
gdm_server_stop (GdmServer *server)
|
||
{
|
||
int res;
|
||
|
||
g_return_val_if_fail (GDM_IS_SERVER (server), FALSE);
|
||
|
||
if (server->pid <= 1) {
|
||
return TRUE;
|
||
}
|
||
|
||
/* remove watch source before we can wait on child */
|
||
g_clear_handle_id (&server->child_watch_id, g_source_remove);
|
||
|
||
g_debug ("GdmServer: Stopping server");
|
||
|
||
res = gdm_signal_pid (server->pid, SIGTERM);
|
||
if (res >= 0) {
|
||
server_died (server);
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
static void
|
||
_gdm_server_set_display_name (GdmServer *server,
|
||
const char *name)
|
||
{
|
||
g_free (server->display_name);
|
||
server->display_name = g_strdup (name);
|
||
}
|
||
|
||
static void
|
||
_gdm_server_set_display_seat_id (GdmServer *server,
|
||
const char *name)
|
||
{
|
||
g_free (server->display_seat_id);
|
||
server->display_seat_id = g_strdup (name);
|
||
}
|
||
|
||
static void
|
||
_gdm_server_set_auth_file (GdmServer *server,
|
||
const char *auth_file)
|
||
{
|
||
g_free (server->auth_file);
|
||
server->auth_file = g_strdup (auth_file);
|
||
}
|
||
|
||
static void
|
||
_gdm_server_set_user_name (GdmServer *server,
|
||
const char *name)
|
||
{
|
||
g_free (server->user_name);
|
||
server->user_name = g_strdup (name);
|
||
}
|
||
|
||
static void
|
||
_gdm_server_set_disable_tcp (GdmServer *server,
|
||
gboolean disabled)
|
||
{
|
||
server->disable_tcp = disabled;
|
||
}
|
||
|
||
static void
|
||
_gdm_server_set_is_initial (GdmServer *server,
|
||
gboolean initial)
|
||
{
|
||
server->is_initial = initial;
|
||
}
|
||
|
||
static void
|
||
gdm_server_set_property (GObject *object,
|
||
guint prop_id,
|
||
const GValue *value,
|
||
GParamSpec *pspec)
|
||
{
|
||
GdmServer *self;
|
||
|
||
self = GDM_SERVER (object);
|
||
|
||
switch (prop_id) {
|
||
case PROP_DISPLAY_NAME:
|
||
_gdm_server_set_display_name (self, g_value_get_string (value));
|
||
break;
|
||
case PROP_DISPLAY_SEAT_ID:
|
||
_gdm_server_set_display_seat_id (self, g_value_get_string (value));
|
||
break;
|
||
case PROP_AUTH_FILE:
|
||
_gdm_server_set_auth_file (self, g_value_get_string (value));
|
||
break;
|
||
case PROP_USER_NAME:
|
||
_gdm_server_set_user_name (self, g_value_get_string (value));
|
||
break;
|
||
case PROP_DISABLE_TCP:
|
||
_gdm_server_set_disable_tcp (self, g_value_get_boolean (value));
|
||
break;
|
||
case PROP_IS_INITIAL:
|
||
_gdm_server_set_is_initial (self, g_value_get_boolean (value));
|
||
break;
|
||
default:
|
||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void
|
||
gdm_server_get_property (GObject *object,
|
||
guint prop_id,
|
||
GValue *value,
|
||
GParamSpec *pspec)
|
||
{
|
||
GdmServer *self;
|
||
|
||
self = GDM_SERVER (object);
|
||
|
||
switch (prop_id) {
|
||
case PROP_DISPLAY_NAME:
|
||
g_value_set_string (value, self->display_name);
|
||
break;
|
||
case PROP_DISPLAY_SEAT_ID:
|
||
g_value_set_string (value, self->display_seat_id);
|
||
break;
|
||
case PROP_DISPLAY_DEVICE:
|
||
g_value_take_string (value,
|
||
gdm_server_get_display_device (self));
|
||
break;
|
||
case PROP_AUTH_FILE:
|
||
g_value_set_string (value, self->auth_file);
|
||
break;
|
||
case PROP_USER_NAME:
|
||
g_value_set_string (value, self->user_name);
|
||
break;
|
||
case PROP_DISABLE_TCP:
|
||
g_value_set_boolean (value, self->disable_tcp);
|
||
break;
|
||
case PROP_IS_INITIAL:
|
||
g_value_set_boolean (value, self->is_initial);
|
||
break;
|
||
default:
|
||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void
|
||
gdm_server_class_init (GdmServerClass *klass)
|
||
{
|
||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||
|
||
object_class->get_property = gdm_server_get_property;
|
||
object_class->set_property = gdm_server_set_property;
|
||
object_class->finalize = gdm_server_finalize;
|
||
|
||
signals [READY] =
|
||
g_signal_new ("ready",
|
||
G_TYPE_FROM_CLASS (object_class),
|
||
G_SIGNAL_RUN_LAST,
|
||
0,
|
||
NULL,
|
||
NULL,
|
||
g_cclosure_marshal_VOID__VOID,
|
||
G_TYPE_NONE,
|
||
0);
|
||
signals [EXITED] =
|
||
g_signal_new ("exited",
|
||
G_OBJECT_CLASS_TYPE (object_class),
|
||
G_SIGNAL_RUN_FIRST,
|
||
0,
|
||
NULL,
|
||
NULL,
|
||
g_cclosure_marshal_VOID__INT,
|
||
G_TYPE_NONE,
|
||
1,
|
||
G_TYPE_INT);
|
||
signals [DIED] =
|
||
g_signal_new ("died",
|
||
G_OBJECT_CLASS_TYPE (object_class),
|
||
G_SIGNAL_RUN_FIRST,
|
||
0,
|
||
NULL,
|
||
NULL,
|
||
g_cclosure_marshal_VOID__INT,
|
||
G_TYPE_NONE,
|
||
1,
|
||
G_TYPE_INT);
|
||
|
||
g_object_class_install_property (object_class,
|
||
PROP_DISPLAY_NAME,
|
||
g_param_spec_string ("display-name",
|
||
"name",
|
||
"name",
|
||
NULL,
|
||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||
g_object_class_install_property (object_class,
|
||
PROP_DISPLAY_SEAT_ID,
|
||
g_param_spec_string ("display-seat-id",
|
||
"Seat ID",
|
||
"ID of the seat this display is running on",
|
||
NULL,
|
||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||
g_object_class_install_property (object_class,
|
||
PROP_DISPLAY_DEVICE,
|
||
g_param_spec_string ("display-device",
|
||
"Display Device",
|
||
"Path to terminal display is running on",
|
||
NULL,
|
||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||
g_object_class_install_property (object_class,
|
||
PROP_AUTH_FILE,
|
||
g_param_spec_string ("auth-file",
|
||
"Authorization File",
|
||
"Path to X authorization file",
|
||
NULL,
|
||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||
|
||
g_object_class_install_property (object_class,
|
||
PROP_USER_NAME,
|
||
g_param_spec_string ("user-name",
|
||
"user name",
|
||
"user name",
|
||
NULL,
|
||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
|
||
g_object_class_install_property (object_class,
|
||
PROP_DISABLE_TCP,
|
||
g_param_spec_boolean ("disable-tcp",
|
||
NULL,
|
||
NULL,
|
||
TRUE,
|
||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
|
||
g_object_class_install_property (object_class,
|
||
PROP_IS_INITIAL,
|
||
g_param_spec_boolean ("is-initial",
|
||
NULL,
|
||
NULL,
|
||
FALSE,
|
||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
|
||
}
|
||
|
||
static void
|
||
gdm_server_init (GdmServer *server)
|
||
{
|
||
server->pid = -1;
|
||
|
||
server->log_dir = g_strdup (LOGDIR);
|
||
}
|
||
|
||
static void
|
||
gdm_server_finalize (GObject *object)
|
||
{
|
||
GdmServer *server;
|
||
|
||
g_return_if_fail (object != NULL);
|
||
g_return_if_fail (GDM_IS_SERVER (object));
|
||
|
||
server = GDM_SERVER (object);
|
||
|
||
gdm_server_stop (server);
|
||
|
||
g_free (server->command);
|
||
g_free (server->user_name);
|
||
g_free (server->session_args);
|
||
g_free (server->log_dir);
|
||
g_free (server->display_name);
|
||
g_free (server->display_seat_id);
|
||
g_free (server->display_device);
|
||
g_free (server->auth_file);
|
||
|
||
G_OBJECT_CLASS (gdm_server_parent_class)->finalize (object);
|
||
}
|
||
|
||
GdmServer *
|
||
gdm_server_new (const char *display_name,
|
||
const char *seat_id,
|
||
const char *auth_file,
|
||
gboolean initial)
|
||
{
|
||
GObject *object;
|
||
|
||
object = g_object_new (GDM_TYPE_SERVER,
|
||
"display-name", display_name,
|
||
"display-seat-id", seat_id,
|
||
"auth-file", auth_file,
|
||
"is-initial", initial,
|
||
NULL);
|
||
|
||
return GDM_SERVER (object);
|
||
}
|