/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
 * GDM - The GNOME Display Manager
 * Copyright (C) 1999, 2000 Martin K. Petersen <mkp@mkp.net>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/* This file contains functions for controlling local X servers */

#include "config.h"

#include <glib/gi18n.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <strings.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>
#include <X11/Xlib.h>

#include "gdm.h"
#include "server.h"
#include "misc.h"
#include "xdmcp.h"
#include "display.h"
#include "auth.h"
#include "slave.h"
#include "getvt.h"

#include "gdm-common.h"
#include "gdm-log.h"
#include "gdm-daemon-config.h"

#include "gdm-socket-protocol.h"

/* Local prototypes */
static void gdm_server_spawn (GdmDisplay *d, const char *vtarg);
static void gdm_server_usr1_handler (gint);
static void gdm_server_child_handler (gint);
static char * get_font_path (const char *display);

/* Global vars */
static int server_signal_pipe[2];
static GdmDisplay *d                   = NULL;
static gboolean server_signal_notified = FALSE;
static int gdm_in_signal               = 0;

static void do_server_wait (GdmDisplay *d);
static gboolean setup_server_wait (GdmDisplay *d);

void
gdm_server_whack_lockfile (GdmDisplay *disp)
{
	    char buf[256];

	    /* X seems to be sometimes broken with its lock files and
	       doesn't seem to remove them always, and if you manage
	       to get into the weird state where the old pid now
	       corresponds to some new pid, X will just die with
	       a stupid error. */

	    /* Yes there could be a race here if another X server starts
	       at this exact instant.  Oh well such is life.  Very unlikely
	       to happen though as we should really be the only ones
	       trying to start X servers, and we aren't starting an
	       X server on this display yet. */

	    /* if lock file exists and it is our process, whack it! */
	    g_snprintf (buf, sizeof (buf), "/tmp/.X%d-lock", disp->dispnum);
	    VE_IGNORE_EINTR (g_unlink (buf));

	    /* whack the unix socket as well */
	    g_snprintf (buf, sizeof (buf),
			"/tmp/.X11-unix/X%d", disp->dispnum);
	    VE_IGNORE_EINTR (g_unlink (buf));
}


/* Wipe cookie files */
void
gdm_server_wipe_cookies (GdmDisplay *disp)
{
	if ( ! ve_string_empty (disp->authfile)) {
		VE_IGNORE_EINTR (g_unlink (disp->authfile));
	}
	g_free (disp->authfile);
	disp->authfile = NULL;
	if ( ! ve_string_empty (disp->authfile_gdm)) {
		VE_IGNORE_EINTR (g_unlink (disp->authfile_gdm));
	}
	g_free (disp->authfile_gdm);
	disp->authfile_gdm = NULL;
}

static Jmp_buf reinitjmp;

/* ignore handlers */
static int
ignore_xerror_handler (Display *disp, XErrorEvent *evt)
{
	return 0;
}

static int
jumpback_xioerror_handler (Display *disp)
{
	Longjmp (reinitjmp, 1);
}

#ifdef HAVE_FBCONSOLE
#define FBCONSOLE "/usr/openwin/bin/fbconsole"

static void
gdm_exec_fbconsole (GdmDisplay *disp)
{
        pid_t pid;
        char *argv[6];

        argv[0] = FBCONSOLE;
        argv[1] = "-d";
        argv[2] = disp->name;
        argv[3] = NULL;

	g_debug ("Forking fbconsole");

        pid = fork ();
        if (pid == 0) {
                gdm_close_all_descriptors (0 /* from */, -1 /* except */, -1 /* except2 */)
;
                VE_IGNORE_EINTR (execv (argv[0], argv));
        }
        if (pid == -1) {
                gdm_error (_("Can not start fallback console"));
        }
}
#endif

/**
 * gdm_server_reinit:
 * @disp: Pointer to a GdmDisplay structure
 *
 * Reinit the display, basically sends a HUP signal
 * but only if the display exists
 */

gboolean
gdm_server_reinit (GdmDisplay *disp)
{
	if (disp == NULL)
		return FALSE;

	if (disp->servpid <= 0) {
		/* Kill our connection if one existed, likely to result
		 * in some bizzaro error right now */
		if (disp->dsp != NULL) {
			XCloseDisplay (disp->dsp);
			disp->dsp = NULL;
		}
		return FALSE;
	}

	gdm_debug ("gdm_server_reinit: Server for %s is about to be reinitialized!", disp->name);

	if ( ! setup_server_wait (disp))
		return FALSE;

	d->servstat = SERVER_PENDING;

	if (disp->dsp != NULL) {
		/* static because of the Setjmp */
		static int (*old_xerror_handler)(Display *, XErrorEvent *) = NULL;
		static int (*old_xioerror_handler)(Display *) = NULL;

		old_xerror_handler = NULL;
		old_xioerror_handler = NULL;

		/* Do note the interaction of this Setjmp and the signal
	   	   handlers and the Setjmp in slave.c */

		/* Long live Setjmp, DIE DIE DIE XSetIOErrorHandler */

		if (Setjmp (reinitjmp) == 0)  {
			/* come here and we'll whack the server and wait to get
			   an xio error */
			old_xerror_handler = XSetErrorHandler (ignore_xerror_handler);
			old_xioerror_handler = XSetIOErrorHandler (jumpback_xioerror_handler);

			/* Now whack the server with a SIGHUP */
			gdm_sigchld_block_push ();
			if (disp->servpid > 1)
				kill (disp->servpid, SIGHUP);
			else
				d->servstat = SERVER_DEAD;
			gdm_sigchld_block_pop ();

			/* the server is dead, weird */
			if (disp->dsp != NULL) {
				XCloseDisplay (disp->dsp);
				disp->dsp = NULL;
			}
		}
		/* no more display */
		disp->dsp = NULL;
		XSetErrorHandler (old_xerror_handler);
		XSetIOErrorHandler (old_xioerror_handler);
	} else {
		/* Now whack the server with a SIGHUP */
		gdm_sigchld_block_push ();
		if (disp->servpid > 1)
			kill (disp->servpid, SIGHUP);
		else
			d->servstat = SERVER_DEAD;
		gdm_sigchld_block_pop ();
	}

	/* Wait for the SIGUSR1 */
	do_server_wait (d);

	if (d->servstat == SERVER_RUNNING) {
#ifdef HAVE_FBCONSOLE
		gdm_exec_fbconsole (d);
#endif
		return TRUE;
        } else {
		/* if something really REALLY screwed up, then whack the
		   lockfiles for safety */
		gdm_server_whack_lockfile (d);
		return FALSE;
	}
}

/**
 * gdm_server_stop:
 * @disp: Pointer to a GdmDisplay structure
 *
 * Stops a local X server, but only if it exists
 */

void
gdm_server_stop (GdmDisplay *disp)
{
    static gboolean waiting_for_server = FALSE;
    int old_servstat;

    if (disp == NULL)
	return;

    /* Kill our connection if one existed */
    if (disp->dsp != NULL) {
	    /* on XDMCP servers first kill everything in sight */
	    if (disp->type == TYPE_XDMCP)
		    gdm_server_whack_clients (disp->dsp);
	    XCloseDisplay (disp->dsp);
	    disp->dsp = NULL;
    }

    /* Kill our parent connection if one existed */
    if (disp->parent_dsp != NULL) {
	    /* on XDMCP servers first kill everything in sight */
	    if (disp->type == TYPE_XDMCP_PROXY)
		    gdm_server_whack_clients (disp->parent_dsp);
	    XCloseDisplay (disp->parent_dsp);
	    disp->parent_dsp = NULL;
    }

    if (disp->servpid <= 0)
	    return;

    gdm_debug ("gdm_server_stop: Server for %s going down!", disp->name);

    old_servstat = disp->servstat;
    disp->servstat = SERVER_DEAD;

    if (disp->servpid > 0) {
	    pid_t servpid;

	    gdm_debug ("gdm_server_stop: Killing server pid %d",
		       (int)disp->servpid);

	    /* avoid SIGCHLD race */
	    gdm_sigchld_block_push ();
	    servpid = disp->servpid;

	    if (waiting_for_server) {
		    gdm_error ("gdm_server_stop: Some problem killing server, whacking with SIGKILL");
		    if (disp->servpid > 1)
			    kill (disp->servpid, SIGKILL);

	    } else {
		    if (disp->servpid > 1 &&
			kill (disp->servpid, SIGTERM) == 0) {
			    waiting_for_server = TRUE;
			    ve_waitpid_no_signal (disp->servpid, NULL, 0);
			    waiting_for_server = FALSE;
		    }
	    }
	    disp->servpid = 0;

	    gdm_sigchld_block_pop ();

	    if (old_servstat == SERVER_RUNNING)
		    gdm_server_whack_lockfile (disp);

	    gdm_debug ("gdm_server_stop: Server pid %d dead", (int)servpid);

	    /* just in case we restart again wait at least
	       one sec to avoid races */
	    if (d->sleep_before_run < 1)
		    d->sleep_before_run = 1;
    }

    gdm_server_wipe_cookies (disp);

    gdm_slave_whack_temp_auth_file ();
}

static gboolean
busy_ask_user (GdmDisplay *disp)
{
    /* if we have "open" we can talk to the user */
    if (g_access (LIBEXECDIR "/gdmopen", X_OK) == 0) {
	    char *error = g_strdup_printf
		    (C_(N_("There already appears to be an X server "
			   "running on display %s.  Should another "
			   "display number by tried?  Answering no will "
			   "cause GDM to attempt starting the server "
			   "on %s again.%s")),
		     disp->name,
		     disp->name,
#ifdef __linux__
		     C_(N_("  (You can change consoles by pressing Ctrl-Alt "
			   "plus a function key, such as Ctrl-Alt-F7 to go "
			   "to console 7.  X servers usually run on consoles "
			   "7 and higher.)"))
#else /* ! __linux__ */
		     /* no info for non linux users */
		     ""
#endif /* __linux__ */
		     );
	    gboolean ret = TRUE;
	    /* default ret to TRUE */
	    if ( ! gdm_text_yesno_dialog (error, &ret))
		    ret = TRUE;
	    g_free (error);
	    return ret;
    } else {
	    /* Well we'll just try another display number */
	    return TRUE;
    }
}

/* Checks only output, no XFree86 v4 logfile */
static gboolean
display_parent_no_connect (GdmDisplay *disp)
{
	char *logname = gdm_make_filename (gdm_daemon_config_get_value_string (GDM_KEY_LOG_DIR), d->name, ".log");
	FILE *fp;
	char buf[256];
	char *getsret;

	VE_IGNORE_EINTR (fp = fopen (logname, "r"));
	g_free (logname);

	if (fp == NULL)
		return FALSE;

	for (;;) {
		VE_IGNORE_EINTR (getsret = fgets (buf, sizeof (buf), fp));
		if (getsret == NULL) {
			VE_IGNORE_EINTR (fclose (fp));
			return FALSE;
		}
		/* Note: this is probably XFree86 specific, and perhaps even
		 * version 3 specific (I don't have xfree v4 to test this),
		 * of course additions are welcome to make this more robust */
		if (strstr (buf, "Unable to open display \"") == buf) {
			gdm_error (_("Display '%s' cannot be opened by nested display"),
				   ve_sure_string (disp->parent_disp));
			VE_IGNORE_EINTR (fclose (fp));
			return TRUE;
		}
	}
}

static gboolean
display_busy (GdmDisplay *disp)
{
	char *logname = gdm_make_filename (gdm_daemon_config_get_value_string (GDM_KEY_LOG_DIR), d->name, ".log");
	FILE *fp;
	char buf[256];
	char *getsret;

	VE_IGNORE_EINTR (fp = fopen (logname, "r"));
	g_free (logname);

	if (fp == NULL)
		return FALSE;

	for (;;) {
		VE_IGNORE_EINTR (getsret = fgets (buf, sizeof (buf), fp));
		if (getsret == NULL) {
			VE_IGNORE_EINTR (fclose (fp));
			return FALSE;
		}
		/* Note: this is probably XFree86 specific */
		if (strstr (buf, "Server is already active for display")
		    == buf) {
			gdm_error (_("Display %s is busy. There is another "
				     "X server running already."),
				   disp->name);
			VE_IGNORE_EINTR (fclose (fp));
			return TRUE;
		}
	}
}

/* if we find 'Log file: "foo"' switch fp to foo and
   return TRUE */
/* Note: assumes buf is of size 256 and is writable */ 
static gboolean
open_another_logfile (char buf[256], FILE **fp)
{
	if (strncmp (&buf[5], "Log file: \"", strlen ("Log file: \"")) == 0) {
		FILE *ffp;
		char *fname = &buf[5+strlen ("Log file: \"")];
		char *p = strchr (fname, '"');
		if (p == NULL)
			return FALSE;
		*p = '\0';
		VE_IGNORE_EINTR (ffp = fopen (fname, "r"));
		if (ffp == NULL)
			return FALSE;
		VE_IGNORE_EINTR (fclose (*fp));
		*fp = ffp;
		return TRUE;
	}
	return FALSE;
}

static int
display_vt (GdmDisplay *disp)
{
	char *logname = gdm_make_filename (gdm_daemon_config_get_value_string (GDM_KEY_LOG_DIR), d->name, ".log");
	FILE *fp;
	char buf[256];
	gboolean switched = FALSE;
	char *getsret;

	VE_IGNORE_EINTR (fp = fopen (logname, "r"));
	g_free (logname);

	if (fp == NULL)
		return FALSE;

	for (;;) {
		int vt;
		char *p;

		VE_IGNORE_EINTR (getsret = fgets (buf, sizeof (buf), fp));
		if (getsret == NULL) {
			VE_IGNORE_EINTR (fclose (fp));
			return -1;
		}

		if ( ! switched &&
		     /* this is XFree v4 specific */
		    open_another_logfile (buf, &fp)) {
			switched = TRUE;
			continue;
		} 
		/* Note: this is probably XFree86 specific (works with
		 * both v3 and v4 though */
		p = strstr (buf, "using VT number ");
		if (p != NULL &&
		    sscanf (p, "using VT number %d", &vt) == 1) {
			VE_IGNORE_EINTR (fclose (fp));
			return vt;
		}
	}
}

static struct sigaction old_svr_wait_chld;
static sigset_t old_svr_wait_mask;

static gboolean
setup_server_wait (GdmDisplay *d)
{
    struct sigaction usr1, chld;
    sigset_t mask;

    if (pipe (server_signal_pipe) != 0) {
	    gdm_error (_("%s: Error opening a pipe: %s"),
		       "setup_server_wait", strerror (errno));
	    return FALSE; 
    }
    server_signal_notified = FALSE;

    /* Catch USR1 from X server */
    usr1.sa_handler = gdm_server_usr1_handler;
    usr1.sa_flags = SA_RESTART;
    sigemptyset (&usr1.sa_mask);

    if (sigaction (SIGUSR1, &usr1, NULL) < 0) {
	    gdm_error (_("%s: Error setting up %s signal handler: %s"),
		       "gdm_server_start", "USR1", strerror (errno));
	    VE_IGNORE_EINTR (close (server_signal_pipe[0]));
	    VE_IGNORE_EINTR (close (server_signal_pipe[1]));
	    return FALSE;
    }

    /* Catch CHLD from X server */
    chld.sa_handler = gdm_server_child_handler;
    chld.sa_flags = SA_RESTART|SA_NOCLDSTOP;
    sigemptyset (&chld.sa_mask);

    if (sigaction (SIGCHLD, &chld, &old_svr_wait_chld) < 0) {
	    gdm_error (_("%s: Error setting up %s signal handler: %s"),
		       "gdm_server_start", "CHLD", strerror (errno));
	    gdm_signal_ignore (SIGUSR1);
	    VE_IGNORE_EINTR (close (server_signal_pipe[0]));
	    VE_IGNORE_EINTR (close (server_signal_pipe[1]));
	    return FALSE;
    }

    /* Set signal mask */
    sigemptyset (&mask);
    sigaddset (&mask, SIGUSR1);
    sigaddset (&mask, SIGCHLD);
    sigprocmask (SIG_UNBLOCK, &mask, &old_svr_wait_mask);

    return TRUE;
}

static void
do_server_wait (GdmDisplay *d)
{
    /* Wait for X server to send ready signal */
    if (d->servstat == SERVER_PENDING) {
	    if (d->server_uid != 0 && ! d->handled && ! d->chosen_hostname) {
		    /* FIXME: If not handled, we just don't know, so
		     * just wait a few seconds and hope things just work,
		     * fortunately there is no such case yet and probably
		     * never will, but just for code anality's sake */
		    gdm_sleep_no_signal (gdm_daemon_config_get_value_int(GDM_KEY_XSERVER_TIMEOUT));
	    } else if (d->server_uid != 0) {
		    int i;

		    /* FIXME: This is not likely to work in reinit,
		       but we never reinit Nested servers nowdays,
		       so that's fine */

		    /* if we're running the server as a non-root, we can't
		     * use USR1 of course, so try openning the display 
		     * as a test, but the */

		    /* just in case it's set */
		    g_unsetenv ("XAUTHORITY");

		    gdm_auth_set_local_auth (d);

		    for (i = 0;
			 d->dsp == NULL &&
			 d->servstat == SERVER_PENDING &&
			 i < gdm_daemon_config_get_value_int(GDM_KEY_XSERVER_TIMEOUT);
			 i++) {
			    d->dsp = XOpenDisplay (d->name);
			    if (d->dsp == NULL)
				    gdm_sleep_no_signal (1);
			    else
				    d->servstat = SERVER_RUNNING;
		    }
		    if (d->dsp == NULL &&
			/* Note: we could have still gotten a SIGCHLD */
			d->servstat == SERVER_PENDING) {
			    d->servstat = SERVER_TIMEOUT;
		    }
	    } else {
		    time_t t = time (NULL);

		    gdm_debug ("do_server_wait: Before mainloop waiting for server");

		    do {
			    fd_set rfds;
			    struct timeval tv;

			    /* Wait up to GDM_KEY_XSERVER_TIMEOUT seconds. */
			    tv.tv_sec = MAX (1, gdm_daemon_config_get_value_int(GDM_KEY_XSERVER_TIMEOUT) 
			    	- (time (NULL) - t));
			    tv.tv_usec = 0;

			    FD_ZERO (&rfds);
			    FD_SET (server_signal_pipe[0], &rfds);

			    if (select (server_signal_pipe[0]+1, &rfds, NULL, NULL, &tv) > 0) {
				    char buf[4];
				    /* read the Yay! */
				    VE_IGNORE_EINTR (read (server_signal_pipe[0], buf, 4));
			    }
			    if ( ! server_signal_notified &&
				t + gdm_daemon_config_get_value_int(GDM_KEY_XSERVER_TIMEOUT) < time (NULL)) {
				    gdm_debug ("do_server_wait: Server timeout");
				    d->servstat = SERVER_TIMEOUT;
				    server_signal_notified = TRUE;
			    }
			    if (d->servpid <= 1) {
				    d->servstat = SERVER_ABORT;
				    server_signal_notified = TRUE;
			    }
		    } while ( ! server_signal_notified);

		    gdm_debug ("gdm_server_start: After mainloop waiting for server");
	    }
    }

    /* restore default handlers */
    gdm_signal_ignore (SIGUSR1);
    sigaction (SIGCHLD, &old_svr_wait_chld, NULL);
    sigprocmask (SIG_SETMASK, &old_svr_wait_mask, NULL);

    VE_IGNORE_EINTR (close (server_signal_pipe[0]));
    VE_IGNORE_EINTR (close (server_signal_pipe[1]));

    if (d->servpid <= 1) {
	    d->servstat = SERVER_ABORT;
    }

    if (d->servstat != SERVER_RUNNING) {
	    /* bad things are happening */
	    if (d->servpid > 0) {
		    pid_t pid;

		    d->dsp = NULL;

		    gdm_sigchld_block_push ();
		    pid = d->servpid;
		    d->servpid = 0;
		    if (pid > 1 &&
			kill (pid, SIGTERM) == 0)
			    ve_waitpid_no_signal (pid, NULL, 0);
		    gdm_sigchld_block_pop ();
	    }

	    /* We will rebake cookies anyway, so wipe these */
	    gdm_server_wipe_cookies (d);
    }
}

/* We keep a connection (parent_dsp) open with the parent X server
 * before running a proxy on it to prevent the X server resetting
 * as we open and close other connections.
 * Note that XDMCP servers, by default, reset when the seed X
 * connection closes whereas usually the X server only quits when
 * all X connections have closed.
 */
static gboolean
connect_to_parent (GdmDisplay *d)
{
	int maxtries;
	int openretries;

	gdm_debug ("gdm_server_start: Connecting to parent display \'%s\'",
		   d->parent_disp);

	d->parent_dsp = NULL;

	maxtries = SERVER_IS_XDMCP (d) ? 10 : 2;

	openretries = 0;
	while (openretries < maxtries &&
	       d->parent_dsp == NULL) {
		d->parent_dsp = XOpenDisplay (d->parent_disp);

		if G_UNLIKELY (d->parent_dsp == NULL) {
			gdm_debug ("gdm_server_start: Sleeping %d on a retry", 1+openretries*2);
			gdm_sleep_no_signal (1+openretries*2);
			openretries++;
		}
	}

	if (d->parent_dsp == NULL)
		gdm_error (_("%s: failed to connect to parent display \'%s\'"),
			   "gdm_server_start", d->parent_disp);

	return d->parent_dsp != NULL;
}

/**
 * gdm_server_start:
 * @disp: Pointer to a GdmDisplay structure
 *
 * Starts a local X server. Handles retries and fatal errors properly.
 */

gboolean
gdm_server_start (GdmDisplay *disp,
		  gboolean try_again_if_busy /* only affects non-flexi servers */,
		  gboolean treat_as_flexi,
		  int min_flexi_disp,
		  int flexi_retries)
{
    int flexi_disp = 20;
    char *vtarg = NULL;
    int vtfd = -1, vt = -1;
    
    if (disp == NULL)
	    return FALSE;

    d = disp;

    /* if an X server exists, wipe it */
    gdm_server_stop (d);

    /* First clear the VT number */
    if (d->type == TYPE_STATIC ||
	d->type == TYPE_FLEXI) {
	    d->vt = -1;
	    gdm_slave_send_num (GDM_SOP_VT_NUM, -1);
    }

    if (SERVER_IS_FLEXI (d) ||
	treat_as_flexi) {
	    flexi_disp = gdm_get_free_display
		    (MAX (gdm_daemon_config_get_high_display_num () + 1, min_flexi_disp) /* start */,
		     d->server_uid /* server uid */);

	    g_free (d->name);
	    d->name = g_strdup_printf (":%d", flexi_disp);
	    d->dispnum = flexi_disp;

	    gdm_slave_send_num (GDM_SOP_DISP_NUM, flexi_disp);
    }

    if (d->type == TYPE_XDMCP_PROXY &&
	! connect_to_parent (d))
	    return FALSE;

    gdm_debug ("gdm_server_start: %s", d->name);

    /* Create new cookie */
    if ( ! gdm_auth_secure_display (d)) 
	    return FALSE;
    gdm_slave_send_string (GDM_SOP_COOKIE, d->cookie);
    gdm_slave_send_string (GDM_SOP_AUTHFILE, d->authfile);
    g_setenv ("DISPLAY", d->name, TRUE);

    if ( ! setup_server_wait (d))
	    return FALSE;

    d->servstat = SERVER_DEAD;

    if (d->type == TYPE_STATIC ||
	d->type == TYPE_FLEXI) {
	    vtarg = gdm_get_empty_vt_argument (&vtfd, &vt);
    }

    /* fork X server process */
    gdm_server_spawn (d, vtarg);

    /* we can now use d->handled since that's set up above */
    do_server_wait (d);

    /* If we were holding a vt open for the server, close it now as it has
     * already taken the bait. */
    if (vtfd > 0) {
	    VE_IGNORE_EINTR (close (vtfd));
    }

    switch (d->servstat) {

    case SERVER_TIMEOUT:
	    gdm_debug ("gdm_server_start: Temporary server failure (%s)", d->name);
	    break;

    case SERVER_ABORT:
	    gdm_debug ("gdm_server_start: Server %s died during startup!", d->name);
	    break;

    case SERVER_RUNNING:
	    gdm_debug ("gdm_server_start: Completed %s!", d->name);

	    if (SERVER_IS_FLEXI (d))
		    gdm_slave_send_num (GDM_SOP_FLEXI_OK, 0 /* bogus */);
	    if (d->type == TYPE_STATIC ||
		d->type == TYPE_FLEXI) {
		    if (vt >= 0)
			    d->vt = vt;

		    if (d->vt < 0)
			    d->vt = display_vt (d);
		    if (d->vt >= 0)
			    gdm_slave_send_num (GDM_SOP_VT_NUM, d->vt);
	    }

#ifdef HAVE_FBCONSOLE
            gdm_exec_fbconsole (d);
#endif

	    return TRUE;
    default:
	    break;
    }

    if (SERVER_IS_PROXY (disp) &&
	display_parent_no_connect (disp)) {
	    gdm_slave_send_num (GDM_SOP_FLEXI_ERR,
				5 /* proxy can't connect */);
	    _exit (DISPLAY_REMANAGE);
    }

    /* if this was a busy fail, that is, there is already
     * a server on that display, we'll display an error and after
     * this we'll exit with DISPLAY_REMANAGE to try again if the
     * user wants to, or abort this display */
    if (display_busy (disp)) {
	    if (SERVER_IS_FLEXI (disp) ||
		treat_as_flexi) {
		    /* for flexi displays, try again a few times with different
		     * display numbers */
		    if (flexi_retries <= 0) {
			    /* Send X too busy */
			    gdm_error (_("%s: Cannot find a free "
					 "display number"),
				       "gdm_server_start");
			    if (SERVER_IS_FLEXI (disp)) {
				    gdm_slave_send_num (GDM_SOP_FLEXI_ERR,
							4 /* X too busy */);
			    }
			    /* eki eki */
			    _exit (DISPLAY_REMANAGE);
		    }
		    return gdm_server_start (d, FALSE /*try_again_if_busy */,
					     treat_as_flexi,
					     flexi_disp + 1,
					     flexi_retries - 1);
	    } else {
		    if (try_again_if_busy) {
			    gdm_debug ("%s: Display %s busy.  Trying once again "
				       "(after 2 sec delay)",
				       "gdm_server_start", d->name);
			    gdm_sleep_no_signal (2);
			    return gdm_server_start (d,
						     FALSE /* try_again_if_busy */,
						     treat_as_flexi,
						     flexi_disp,
						     flexi_retries);
		    }
		    if (busy_ask_user (disp)) {
			    gdm_error (_("%s: Display %s busy.  Trying "
					 "another display number."),
				       "gdm_server_start",
				       d->name);
			    d->busy_display = TRUE;
			    return gdm_server_start (d,
						     FALSE /*try_again_if_busy */,
						     TRUE /* treat as flexi */,
						     gdm_daemon_config_get_high_display_num () + 1,
						     flexi_retries - 1);
		    }
		    _exit (DISPLAY_REMANAGE);
	    }
    }

    _exit (DISPLAY_XFAILED);

    return FALSE;
}

/* Do things that require checking the log,
 * we really do need to get called a bit later, after all init is done
 * as things aren't written to disk before that */
void
gdm_server_checklog (GdmDisplay *disp)
{
	if (d->vt < 0 &&
	    (d->type == TYPE_STATIC ||
	     d->type == TYPE_FLEXI)) {
		d->vt = display_vt (d);
		if (d->vt >= 0)
			gdm_slave_send_num (GDM_SOP_VT_NUM, d->vt);
	}
}

/* somewhat safer rename (safer if the log dir is unsafe), may in fact
   lose the file though, it guarantees that a is gone, but not that
   b exists */
static void
safer_rename (const char *a, const char *b)
{
	errno = 0;
	if (link (a, b) < 0) {
		if (errno == EEXIST) {
			VE_IGNORE_EINTR (g_unlink (a));
			return;
		} 
		VE_IGNORE_EINTR (g_unlink (b));
		/* likely this system doesn't support hard links */
		g_rename (a, b);
		VE_IGNORE_EINTR (g_unlink (a));
		return;
	}
	VE_IGNORE_EINTR (g_unlink (a));
}

static void
rotate_logs (const char *dname)
{
	const gchar *logdir = gdm_daemon_config_get_value_string (GDM_KEY_LOG_DIR);

	/* I'm too lazy to write a loop */
	char *fname4 = gdm_make_filename (logdir, dname, ".log.4");
	char *fname3 = gdm_make_filename (logdir, dname, ".log.3");
	char *fname2 = gdm_make_filename (logdir, dname, ".log.2");
	char *fname1 = gdm_make_filename (logdir, dname, ".log.1");
	char *fname = gdm_make_filename (logdir, dname, ".log");

	/* Rotate the logs (keep 4 last) */
	VE_IGNORE_EINTR (g_unlink (fname4));
	safer_rename (fname3, fname4);
	safer_rename (fname2, fname3);
	safer_rename (fname1, fname2);
	safer_rename (fname, fname1);

	g_free (fname4);
	g_free (fname3);
	g_free (fname2);
	g_free (fname1);
	g_free (fname);
}

static void
gdm_server_add_xserver_args (GdmDisplay *d, char **argv)
{
	int count;
	char **args;
	int len;
	int i;

	len = gdm_vector_len (argv);
	g_shell_parse_argv (d->xserver_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]);
	}

	argv[len] = NULL;
	g_strfreev (args);
}

GdmXserver *
gdm_server_resolve (GdmDisplay *disp)
{
	char *bin;
	GdmXserver *svr = NULL;

	bin = ve_first_word (disp->command);
	if (bin != NULL && bin[0] != '/') {
		svr = gdm_daemon_config_find_xserver (bin);
	}
	g_free (bin);
	return svr;
}


static char **
vector_merge (char * const *v1,
	      int           len1,
	      char * const *v2,
	      int           len2)
{
	int argc, i;
	char **argv;

	if (v1 == NULL && v2 == NULL)
		return NULL;

	argc = len1 + len2;

	argv = g_new (char *, argc + 1);
	for (i = 0; i < len1; i++)
		argv[i] = g_strdup (v1[i]);
	for (; i < argc; i++)
		argv[i] = g_strdup (v2[i - len1]);
	argv[i] = NULL;

	return argv;
}

gboolean
gdm_server_resolve_command_line (GdmDisplay *disp,
				 gboolean    resolve_flags,
				 const char *vtarg,
				 int        *argcp,
				 char     ***argvp)
{
	char *bin;
	int    argc;
	char **argv;
	int len;
	int i;
	gboolean gotvtarg = FALSE;
	gboolean query_in_arglist = FALSE;

        argv = NULL;

	bin = ve_first_word (disp->command);
	if (bin == NULL) {
		const char *str;

		gdm_error (_("Invalid server command '%s'"), disp->command);
		str = gdm_daemon_config_get_value_string (GDM_KEY_STANDARD_XSERVER);
       		g_shell_parse_argv (str, &argc, &argv, NULL);
	} else if (bin[0] != '/') {
		GdmXserver *svr = gdm_daemon_config_find_xserver (bin);
		if (svr == NULL) {
			const char *str;

			gdm_error (_("Server name '%s' not found; "
				     "using standard server"), bin);
			str = gdm_daemon_config_get_value_string (GDM_KEY_STANDARD_XSERVER);
			g_shell_parse_argv (str, &argc, &argv, NULL);

		} else {
			char **svr_command;
			const char *str;
			int svr_argc;

			str = ve_sure_string (svr->command);
			svr_command = NULL;
			g_shell_parse_argv (str, &svr_argc, &svr_command, NULL);

			g_shell_parse_argv (disp->command, &argc, &argv, NULL);

			if (argv[0] == NULL || argv[1] == NULL) {
				g_strfreev (argv);
				argv = svr_command;
				argc = svr_argc;
			} else {
				char **old_argv = argv;
				argv = vector_merge (svr_command,
						     svr_argc,
						     &old_argv[1],
						     argc);
				g_strfreev (svr_command);
				g_strfreev (old_argv);

				argc += svr_argc;
			}

			if (resolve_flags) {
				/* Setup the handled function */
				disp->handled = svr->handled;
				/* never make use_chooser FALSE,
				   it may have been set temporarily for
				   us by the master */
				if (svr->chooser)
					disp->use_chooser = TRUE;
				disp->priority = svr->priority;
			}
		}
	} else {
		g_shell_parse_argv (disp->command, &argc, &argv, NULL);
	}

	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 + 10);
	/* 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 (disp->name);
	len++;

	if (disp->authfile != NULL) {
		argv[len++] = g_strdup ("-auth");
		argv[len++] = g_strdup (disp->authfile);
	}

	if (resolve_flags && disp->chosen_hostname) {
		/* this display is NOT handled */
		disp->handled = FALSE;
		/* never ever ever use chooser here */
		disp->use_chooser = FALSE;
		disp->priority = 0;
		/* run just one session */
		argv[len++] = g_strdup ("-terminate");
		argv[len++] = g_strdup ("-query");
		argv[len++] = g_strdup (disp->chosen_hostname);
		query_in_arglist = TRUE;
	}

	if (resolve_flags && gdm_daemon_config_get_value_bool (GDM_KEY_DISALLOW_TCP) && ! query_in_arglist) {
		argv[len++] = g_strdup ("-nolisten");
		argv[len++] = g_strdup ("tcp");
		d->tcp_disallowed = TRUE;
	}

	if (vtarg != NULL &&
	    ! gotvtarg) {
		argv[len++] = g_strdup (vtarg);
	}

	argv[len++] = NULL;

	*argvp = argv;
	*argcp = len;

	g_free (bin);

	return TRUE;
}

/**
 * gdm_server_spawn:
 * @disp: Pointer to a GdmDisplay structure
 *
 * forks an actual X server process
 *
 * Note that we can only use d->handled once we call this function
 * since otherwise the server might not yet be looked up yet.
 */

static void
gdm_server_spawn (GdmDisplay *d, const char *vtarg)
{
    struct sigaction ign_signal;
    sigset_t mask;
    int argc;
    gchar **argv = NULL;
    char *logfile;
    int logfd;
    char *command;
    pid_t pid;

    if (d == NULL ||
	ve_string_empty (d->command)) {
	    return;
    }

    d->servstat = SERVER_PENDING;

    gdm_sigchld_block_push ();

    /* eek, some previous copy, just wipe it */
    if (d->servpid > 0) {
	    pid_t pid = d->servpid;
	    d->servpid = 0;
	    if (pid > 1 &&
		kill (pid, SIGTERM) == 0)
		    ve_waitpid_no_signal (pid, NULL, 0);
    }

    /* Figure out the server command */
    argv = NULL;
    argc = 0;
    gdm_server_resolve_command_line (d,
				     TRUE /* resolve flags */,
				     vtarg,
				     &argc,
				     &argv);

    /* Do not support additional session arguments with Xnest. */
    if (d->type != TYPE_FLEXI_XNEST) {
	    if (d->xserver_session_args)
		    gdm_server_add_xserver_args (d, argv);
    }

    command = g_strjoinv (" ", argv);

    /* Fork into two processes. Parent remains the gdm process. Child
     * becomes the X server. */

    g_debug ("Forking X server process");

    gdm_sigterm_block_push ();
    pid = d->servpid = fork ();
    if (pid == 0)
	    gdm_unset_signals ();
    gdm_sigterm_block_pop ();
    gdm_sigchld_block_pop ();
    
    switch (pid) {
	
    case 0:
	/* the pops whacked mask again */
        gdm_unset_signals ();

	gdm_log_shutdown ();

	/* close things */
	gdm_close_all_descriptors (0 /* from */, -1 /* except */, -1 /* except2 */);

	/* No error checking here - if it's messed the best response
         * is to ignore & try to continue */
	gdm_open_dev_null (O_RDONLY); /* open stdin - fd 0 */
	gdm_open_dev_null (O_RDWR); /* open stdout - fd 1 */
	gdm_open_dev_null (O_RDWR); /* open stderr - fd 2 */

	gdm_log_init ();

	/* Rotate the X server logs */
	rotate_logs (d->name);

        /* Log all output from spawned programs to a file */
	logfile = gdm_make_filename (gdm_daemon_config_get_value_string (GDM_KEY_LOG_DIR), d->name, ".log");
	VE_IGNORE_EINTR (g_unlink (logfile));
	VE_IGNORE_EINTR (logfd = open (logfile, O_CREAT|O_TRUNC|O_WRONLY|O_EXCL, 0644));

	if (logfd != -1) {
		VE_IGNORE_EINTR (dup2 (logfd, 1));
		VE_IGNORE_EINTR (dup2 (logfd, 2));
		close (logfd);
        } else {
		gdm_error (_("%s: Could not open logfile for display %s!"),
			   "gdm_server_spawn", d->name);
	}

	/* 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 (d->server_uid == 0) {
		/* only set this if we can actually listen */
		if (sigaction (SIGUSR1, &ign_signal, NULL) < 0) {
			gdm_error (_("%s: Error setting %s to %s"),
				   "gdm_server_spawn", "USR1", "SIG_IGN");
			_exit (SERVER_ABORT);
		}
	}
	if (sigaction (SIGTTIN, &ign_signal, NULL) < 0) {
		gdm_error (_("%s: Error setting %s to %s"),
			   "gdm_server_spawn", "TTIN", "SIG_IGN");
		_exit (SERVER_ABORT);
	}
	if (sigaction (SIGTTOU, &ign_signal, NULL) < 0) {
		gdm_error (_("%s: Error setting %s to %s"),
			   "gdm_server_spawn", "TTOU", "SIG_IGN");
		_exit (SERVER_ABORT);
	}

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

	if (SERVER_IS_PROXY (d)) {
		gboolean add_display = TRUE;

		g_unsetenv ("DISPLAY");
		if (d->parent_auth_file != NULL)
			g_setenv ("XAUTHORITY", d->parent_auth_file, TRUE);
		else
			g_unsetenv ("XAUTHORITY");

		if (d->type == TYPE_FLEXI_XNEST) {
			char *font_path = NULL;
			/* Add -fp with the current font path, but only if not
			 * already among the arguments */
			if (strstr (command, "-fp") == NULL)
				font_path = get_font_path (d->parent_disp);
			if (font_path != NULL) {
				argv = g_renew (char *, argv, argc + 2);
				argv[argc++] = "-fp";
				argv[argc++] = font_path;
				command = g_strconcat (command, " -fp ",
						       font_path, NULL);
			}
			add_display = FALSE;
		}

		/*
		 * Set the DISPLAY environment variable when calling
		 * nested server since some Xnest commands like Xephyr 
		 * do not support the -display argument.
		 */
		if (add_display == TRUE) {
			argv = g_renew (char *, argv, argc + 3);
			argv[argc++] = "-display";
			argv[argc++] = d->parent_disp;
			argv[argc++] = NULL;
			command = g_strconcat (command, " -display ",
				       d->parent_disp, NULL);
		} else {
			argv = g_renew (char *, argv, argc + 1);
			argv[argc++] = NULL;
			g_setenv ("DISPLAY", d->parent_disp, TRUE);
		}
	}

	if (argv[0] == NULL) {
		gdm_error (_("%s: Empty server command for display %s"),
			   "gdm_server_spawn",
			   d->name);
		_exit (SERVER_ABORT);
	}

	gdm_debug ("gdm_server_spawn: '%s'", command);
	
	if (d->priority != 0) {
		if (setpriority (PRIO_PROCESS, 0, d->priority)) {
			gdm_error (_("%s: Server priority couldn't be set to %d: %s"),
				   "gdm_server_spawn", d->priority,
				   strerror (errno));
		}
	}

	setpgid (0, 0);

	if (d->server_uid != 0) {
		struct passwd *pwent;
		pwent = getpwuid (d->server_uid);
		if (pwent == NULL) {
			gdm_error (_("%s: Server was to be spawned by uid %d but "
				     "that user doesn't exist"),
				   "gdm_server_spawn",
				   (int)d->server_uid);
			_exit (SERVER_ABORT);
		}
		if (pwent->pw_dir != NULL &&
		    g_file_test (pwent->pw_dir, G_FILE_TEST_EXISTS))
			g_setenv ("HOME", pwent->pw_dir, TRUE);
		else
			g_setenv ("HOME", "/", TRUE); /* Hack */
		g_setenv ("SHELL", pwent->pw_shell, TRUE);
		g_unsetenv ("MAIL");

		if (setgid (pwent->pw_gid) < 0)  {
			gdm_error (_("%s: Couldn't set groupid to %d"), 
				   "gdm_server_spawn", (int)pwent->pw_gid);
			_exit (SERVER_ABORT);
		}

		if (initgroups (pwent->pw_name, pwent->pw_gid) < 0) {
			gdm_error (_("%s: initgroups () failed for %s"),
				   "gdm_server_spawn", pwent->pw_name);
			_exit (SERVER_ABORT);
		}

		if (setuid (d->server_uid) < 0)  {
			gdm_error (_("%s: Couldn't set userid to %d"),
				   "gdm_server_spawn", (int)d->server_uid);
			_exit (SERVER_ABORT);
		}
	} else {
		gid_t groups[1] = { 0 };
		if (setgid (0) < 0)  {
			gdm_error (_("%s: Couldn't set groupid to 0"), 
				   "gdm_server_spawn");
			/* 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);
	}

#if sun
    {
        /* Remove old communication pipe, if present */
        char old_pipe[MAXPATHLEN];

        sprintf (old_pipe, "%s/%d", SDTLOGIN_DIR, d->name);
        g_unlink (old_pipe);
    }
#endif

	VE_IGNORE_EINTR (execv (argv[0], argv));

	gdm_fdprintf (2, "GDM: Xserver not found: %s\n"
		      "Error: Command could not be executed!\n"
		      "Please install the X server or correct "
		      "GDM configuration and restart GDM.",
		      command);

	gdm_error (_("%s: Xserver not found: %s"), 
		   "gdm_server_spawn", command);
	
	_exit (SERVER_ABORT);
	
    case -1:
	g_strfreev (argv);
	g_free (command);
	gdm_error (_("%s: Can't fork Xserver process!"),
		   "gdm_server_spawn");
	d->servpid = 0;
	d->servstat = SERVER_DEAD;

	break;
	
    default:
	g_strfreev (argv);
	g_free (command);
	gdm_debug ("%s: Forked server on pid %d", 
		   "gdm_server_spawn", (int)pid);
	break;
    }
}

/**
 * gdm_server_usr1_handler:
 * @sig: Signal value
 *
 * Received when the server is ready to accept connections
 */

static void
gdm_server_usr1_handler (gint sig)
{
    gdm_in_signal++;

    d->servstat = SERVER_RUNNING; /* Server ready to accept connections */
    d->starttime = time (NULL);

    server_signal_notified = TRUE;
    /* this will quit the select */
    VE_IGNORE_EINTR (write (server_signal_pipe[1], "Yay!", 4));

    gdm_in_signal--;
}


/**
 * gdm_server_child_handler:
 * @sig: Signal value
 *
 * Received when server died during startup
 */

static void 
gdm_server_child_handler (int signal)
{
	gdm_in_signal++;

	/* go to the main child handler */
	gdm_slave_child_handler (signal);

	/* this will quit the select */
	VE_IGNORE_EINTR (write (server_signal_pipe[1], "Yay!", 4));

	gdm_in_signal--;
}


void
gdm_server_whack_clients (Display *dsp)
{
	int i, screen_count;
	int (* old_xerror_handler) (Display *, XErrorEvent *);

	if (dsp == NULL)
		return;

	old_xerror_handler = XSetErrorHandler (ignore_xerror_handler);

	XGrabServer (dsp);

	screen_count = ScreenCount (dsp);

	for (i = 0; i < screen_count; i++) {
		Window root_ret, parent_ret;
		Window *childs = NULL;
		unsigned int childs_count = 0;
		Window root = RootWindow (dsp, i);

		while (XQueryTree (dsp, root, &root_ret, &parent_ret,
				   &childs, &childs_count) &&
		       childs_count > 0) {
			int ii;

			for (ii = 0; ii < childs_count; ii++) {
				XKillClient (dsp, childs[ii]);
			}

			XFree (childs);
		}
	}

	XUngrabServer (dsp);

	XSync (dsp, False);
	XSetErrorHandler (old_xerror_handler);
}

static char *
get_font_path (const char *display)
{
	Display *disp;
	char **font_path;
	int n_fonts;
	int i;
	GString *gs;

	disp = XOpenDisplay (display);
	if (disp == NULL)
		return NULL;

	font_path = XGetFontPath (disp, &n_fonts);
	if (font_path == NULL) {
		XCloseDisplay (disp);
		return NULL;
	}

	gs = g_string_new (NULL);
	for (i = 0; i < n_fonts; i++) {
		if (i != 0)
			g_string_append_c (gs, ',');

	        if (gdm_daemon_config_get_value_bool (GDM_KEY_XNEST_UNSCALED_FONT_PATH) == TRUE)
			g_string_append (gs, font_path[i]);
		else {
			gchar *unscaled_ptr = NULL;

			/*
			 * When using Xsun Xnest, it doesn't support the
			 * ":unscaled" suffix in fontpath entries, so strip it.
			 */
			unscaled_ptr = g_strrstr (font_path[i], ":unscaled");
			if (unscaled_ptr != NULL) {
				gchar *temp_string;

				temp_string = g_strndup (font_path[i],
					strlen (font_path[i]) -
					strlen (":unscaled"));

gdm_debug ("font_path[i] is %s, temp_string is %s", font_path[i], temp_string);
				g_string_append (gs, temp_string);
				g_free (temp_string);
			} else {
gdm_debug ("font_path[i] is %s", font_path[i]);
				g_string_append (gs, font_path[i]);
			}
		}
	}

	XFreeFontPath (font_path);

	XCloseDisplay (disp);

	return g_string_free (gs, FALSE);
}

/* EOF */
