summaryrefslogtreecommitdiff
path: root/dm.c
diff options
context:
space:
mode:
authorKaleb Keithley <kaleb@freedesktop.org>2003-11-14 15:54:53 +0000
committerKaleb Keithley <kaleb@freedesktop.org>2003-11-14 15:54:53 +0000
commitdf3e1beed5bbb631975127133464b7e24fc38497 (patch)
tree6291fb1c51fb790c2f236249b5f44eb9bedfdc7b /dm.c
R6.6 is the Xorg base-lineXORG-MAIN
Diffstat (limited to 'dm.c')
-rw-r--r--dm.c855
1 files changed, 855 insertions, 0 deletions
diff --git a/dm.c b/dm.c
new file mode 100644
index 0000000..4cdbbc0
--- /dev/null
+++ b/dm.c
@@ -0,0 +1,855 @@
+/* $Xorg: dm.c,v 1.5 2001/02/09 02:05:40 xorgcvs Exp $ */
+/*
+
+Copyright 1988, 1998 The Open Group
+
+Permission to use, copy, modify, distribute, and sell this software and its
+documentation for any purpose is hereby granted without fee, provided that
+the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of The Open Group shall
+not be used in advertising or otherwise to promote the sale, use or
+other dealings in this Software without prior written authorization
+from The Open Group.
+
+*/
+
+/*
+ * xdm - display manager daemon
+ * Author: Keith Packard, MIT X Consortium
+ *
+ * display manager
+ */
+
+# include "dm.h"
+
+# include <stdio.h>
+#ifdef X_POSIX_C_SOURCE
+#define _POSIX_C_SOURCE X_POSIX_C_SOURCE
+#include <signal.h>
+#undef _POSIX_C_SOURCE
+#else
+#if defined(X_NOT_POSIX) || defined(_POSIX_SOURCE)
+#include <signal.h>
+#else
+#define _POSIX_SOURCE
+#include <signal.h>
+#undef _POSIX_SOURCE
+#endif
+#endif
+
+#ifndef sigmask
+#define sigmask(m) (1 << ((m - 1)))
+#endif
+
+# include <sys/stat.h>
+# include <errno.h>
+# include <X11/Xfuncproto.h>
+#if NeedVarargsPrototypes
+# include <stdarg.h>
+# define Va_start(a,b) va_start(a,b)
+#else
+# include <varargs.h>
+# define Va_start(a,b) va_start(a)
+#endif
+
+#ifndef F_TLOCK
+#ifndef X_NOT_POSIX
+# include <unistd.h>
+#endif
+#endif
+
+#ifdef X_NOT_STDC_ENV
+extern int errno;
+#endif
+
+
+#ifdef SVR4
+extern FILE *fdopen();
+#endif
+
+static void RescanServers ();
+static void ScanServers ();
+int Rescan;
+static long ServersModTime, ConfigModTime, AccessFileModTime;
+static SIGVAL StopAll (), RescanNotify ();
+void StopDisplay ();
+static void RestartDisplay ();
+static void StartDisplays ();
+
+int nofork_session = 0;
+
+#ifndef NOXDMTITLE
+static char *Title;
+static int TitleLen;
+#endif
+
+#ifndef UNRELIABLE_SIGNALS
+static SIGVAL ChildNotify ();
+#endif
+
+static int parent_pid = -1; /* PID of parent xdm process */
+
+main (argc, argv)
+int argc;
+char **argv;
+{
+ int oldpid, oldumask;
+ char cmdbuf[1024];
+
+ /* make sure at least world write access is disabled */
+ if (((oldumask = umask(022)) & 002) == 002)
+ (void) umask (oldumask);
+#ifndef NOXDMTITLE
+ Title = argv[0];
+ TitleLen = (argv[argc - 1] + strlen(argv[argc - 1])) - Title;
+#endif
+
+ /*
+ * Step 1 - load configuration parameters
+ */
+ InitResources (argc, argv);
+ SetConfigFileTime ();
+ LoadDMResources ();
+ /*
+ * Only allow root to run in non-debug mode to avoid problems
+ */
+ if (debugLevel == 0 && getuid() != 0)
+ {
+ fprintf (stderr, "Only root wants to run %s\n", argv[0]);
+ exit (1);
+ }
+ if (debugLevel == 0 && daemonMode)
+ BecomeOrphan ();
+ if (debugLevel >= 10)
+ nofork_session = 1;
+ if (debugLevel == 0 && daemonMode)
+ BecomeDaemon ();
+ /* SUPPRESS 560 */
+ if ((oldpid = StorePid ()))
+ {
+ if (oldpid == -1)
+ LogError ("Can't create/lock pid file %s\n", pidFile);
+ else
+ LogError ("Can't lock pid file %s, another xdm is running (pid %d)\n",
+ pidFile, oldpid);
+ exit (1);
+ }
+ if (debugLevel == 0)
+ InitErrorLog ();
+
+ if (nofork_session == 0) {
+ /* Clean up any old Authorization files */
+ sprintf(cmdbuf, "/bin/rm -f %s/authdir/authfiles/A*", authDir);
+ system(cmdbuf);
+ }
+
+#ifdef XDMCP
+ init_session_id ();
+ CreateWellKnownSockets ();
+#else
+ Debug ("xdm: not compiled for XDMCP\n");
+#endif
+ parent_pid = getpid ();
+ (void) Signal (SIGTERM, StopAll);
+ (void) Signal (SIGINT, StopAll);
+ /*
+ * Step 2 - Read /etc/Xservers and set up
+ * the socket.
+ *
+ * Keep a sub-daemon running
+ * for each entry
+ */
+ SetAccessFileTime ();
+#ifdef XDMCP
+ ScanAccessDatabase ();
+#endif
+ ScanServers ();
+ StartDisplays ();
+ (void) Signal (SIGHUP, RescanNotify);
+#ifndef UNRELIABLE_SIGNALS
+ (void) Signal (SIGCHLD, ChildNotify);
+#endif
+ while (
+#ifdef XDMCP
+ AnyWellKnownSockets() ||
+#endif
+ AnyDisplaysLeft ())
+ {
+ if (Rescan)
+ {
+ RescanServers ();
+ Rescan = 0;
+ }
+#if defined(UNRELIABLE_SIGNALS) || !defined(XDMCP)
+ WaitForChild ();
+#else
+ WaitForSomething ();
+#endif
+ }
+ Debug ("Nothing left to do, exiting\n");
+ exit(0);
+ /*NOTREACHED*/
+}
+
+/* ARGSUSED */
+static SIGVAL
+RescanNotify (n)
+ int n;
+{
+ Debug ("Caught SIGHUP\n");
+ Rescan = 1;
+#ifdef SIGNALS_RESET_WHEN_CAUGHT
+ (void) Signal (SIGHUP, RescanNotify);
+#endif
+}
+
+static void
+ScanServers ()
+{
+ char lineBuf[10240];
+ int len;
+ FILE *serversFile;
+ struct stat statb;
+ static DisplayType acceptableTypes[] =
+ { { Local, Permanent, FromFile },
+ { Foreign, Permanent, FromFile },
+ };
+
+#define NumTypes (sizeof (acceptableTypes) / sizeof (acceptableTypes[0]))
+
+ if (servers[0] == '/')
+ {
+ serversFile = fopen (servers, "r");
+ if (serversFile == NULL)
+ {
+ LogError ("cannot access servers file %s\n", servers);
+ return;
+ }
+ if (ServersModTime == 0)
+ {
+ fstat (fileno (serversFile), &statb);
+ ServersModTime = statb.st_mtime;
+ }
+ while (fgets (lineBuf, sizeof (lineBuf)-1, serversFile))
+ {
+ len = strlen (lineBuf);
+ if (lineBuf[len-1] == '\n')
+ lineBuf[len-1] = '\0';
+ ParseDisplay (lineBuf, acceptableTypes, NumTypes);
+ }
+ fclose (serversFile);
+ }
+ else
+ {
+ ParseDisplay (servers, acceptableTypes, NumTypes);
+ }
+}
+
+static void
+MarkDisplay (d)
+struct display *d;
+{
+ d->state = MissingEntry;
+}
+
+static void
+RescanServers ()
+{
+ Debug ("rescanning servers\n");
+ LogInfo ("Rescanning both config and servers files\n");
+ ForEachDisplay (MarkDisplay);
+ SetConfigFileTime ();
+ ReinitResources ();
+ LoadDMResources ();
+ ScanServers ();
+ SetAccessFileTime ();
+#ifdef XDMCP
+ ScanAccessDatabase ();
+#endif
+ StartDisplays ();
+}
+
+SetConfigFileTime ()
+{
+ struct stat statb;
+
+ if (stat (config, &statb) != -1)
+ ConfigModTime = statb.st_mtime;
+}
+
+SetAccessFileTime ()
+{
+ struct stat statb;
+
+ if (stat (accessFile, &statb) != -1)
+ AccessFileModTime = statb.st_mtime;
+}
+
+static
+RescanIfMod ()
+{
+ struct stat statb;
+
+ if (config && stat (config, &statb) != -1)
+ {
+ if (statb.st_mtime != ConfigModTime)
+ {
+ Debug ("Config file %s has changed, rereading\n", config);
+ LogInfo ("Rereading configuration file %s\n", config);
+ ConfigModTime = statb.st_mtime;
+ ReinitResources ();
+ LoadDMResources ();
+ }
+ }
+ if (servers[0] == '/' && stat(servers, &statb) != -1)
+ {
+ if (statb.st_mtime != ServersModTime)
+ {
+ Debug ("Servers file %s has changed, rescanning\n", servers);
+ LogInfo ("Rereading servers file %s\n", servers);
+ ServersModTime = statb.st_mtime;
+ ForEachDisplay (MarkDisplay);
+ ScanServers ();
+ }
+ }
+#ifdef XDMCP
+ if (accessFile && accessFile[0] && stat (accessFile, &statb) != -1)
+ {
+ if (statb.st_mtime != AccessFileModTime)
+ {
+ Debug ("Access file %s has changed, rereading\n", accessFile);
+ LogInfo ("Rereading access file %s\n", accessFile);
+ AccessFileModTime = statb.st_mtime;
+ ScanAccessDatabase ();
+ }
+ }
+#endif
+}
+
+/*
+ * catch a SIGTERM, kill all displays and exit
+ */
+
+/* ARGSUSED */
+static SIGVAL
+StopAll (n)
+ int n;
+{
+ if (parent_pid != getpid())
+ {
+ /*
+ * We are a child xdm process that was killed by the
+ * master xdm before we were able to return from fork()
+ * and remove this signal handler.
+ *
+ * See defect XWSog08655 for more information.
+ */
+ Debug ("Child xdm caught SIGTERM before it remove that signal.\n");
+ (void) Signal (n, SIG_DFL);
+ TerminateProcess (getpid(), SIGTERM);
+ return;
+ }
+ Debug ("Shutting down entire manager\n");
+#ifdef XDMCP
+ DestroyWellKnownSockets ();
+#endif
+ ForEachDisplay (StopDisplay);
+#ifdef SIGNALS_RESET_WHEN_CAUGHT
+ /* to avoid another one from killing us unceremoniously */
+ (void) Signal (SIGTERM, StopAll);
+ (void) Signal (SIGINT, StopAll);
+#endif
+}
+
+/*
+ * notice that a child has died and may need another
+ * sub-daemon started
+ */
+
+int ChildReady;
+
+#ifndef UNRELIABLE_SIGNALS
+/* ARGSUSED */
+static SIGVAL
+ChildNotify (n)
+ int n;
+{
+ ChildReady = 1;
+}
+#endif
+
+WaitForChild ()
+{
+ int pid;
+ struct display *d;
+ waitType status;
+#ifndef X_NOT_POSIX
+ sigset_t mask, omask;
+#else
+ int omask;
+#endif
+
+#ifdef UNRELIABLE_SIGNALS
+ /* XXX classic System V signal race condition here with RescanNotify */
+ if ((pid = wait (&status)) != -1)
+#else
+#ifndef X_NOT_POSIX
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGCHLD);
+ sigaddset(&mask, SIGHUP);
+ sigprocmask(SIG_BLOCK, &mask, &omask);
+#else
+ omask = sigblock (sigmask (SIGCHLD) | sigmask (SIGHUP));
+#endif
+ Debug ("signals blocked, mask was 0x%x\n", omask);
+ if (!ChildReady && !Rescan)
+#ifndef X_NOT_POSIX
+ sigsuspend(&omask);
+#else
+ sigpause (omask);
+#endif
+ ChildReady = 0;
+#ifndef X_NOT_POSIX
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *)NULL);
+#else
+ sigsetmask (omask);
+#endif
+#ifndef X_NOT_POSIX
+ while ((pid = waitpid (-1, &status, WNOHANG)) > 0)
+#else
+ while ((pid = wait3 (&status, WNOHANG, (struct rusage *) 0)) > 0)
+#endif
+#endif
+ {
+ Debug ("Manager wait returns pid: %d sig %d core %d code %d\n",
+ pid, waitSig(status), waitCore(status), waitCode(status));
+ if (autoRescan)
+ RescanIfMod ();
+ /* SUPPRESS 560 */
+ if ((d = FindDisplayByPid (pid))) {
+ d->pid = -1;
+ switch (waitVal (status)) {
+ case UNMANAGE_DISPLAY:
+ Debug ("Display exited with UNMANAGE_DISPLAY\n");
+ StopDisplay (d);
+ break;
+ case OBEYSESS_DISPLAY:
+ d->startTries = 0;
+ Debug ("Display exited with OBEYSESS_DISPLAY\n");
+ if (d->displayType.lifetime != Permanent ||
+ d->status == zombie)
+ StopDisplay (d);
+ else
+ RestartDisplay (d, FALSE);
+ break;
+ default:
+ Debug ("Display exited with unknown status %d\n", waitVal(status));
+ LogError ("Unknown session exit code %d from process %d\n",
+ waitVal (status), pid);
+ StopDisplay (d);
+ break;
+ case OPENFAILED_DISPLAY:
+ Debug ("Display exited with OPENFAILED_DISPLAY, try %d of %d\n",
+ d->startTries, d->startAttempts);
+ LogError ("Display %s cannot be opened\n", d->name);
+ /*
+ * no display connection was ever made, tell the
+ * terminal that the open attempt failed
+ */
+#ifdef XDMCP
+ if (d->displayType.origin == FromXDMCP)
+ SendFailed (d, "Cannot open display");
+#endif
+ if (d->displayType.origin == FromXDMCP ||
+ d->status == zombie ||
+ ++d->startTries >= d->startAttempts)
+ {
+ LogError ("Display %s is being disabled\n", d->name);
+ StopDisplay (d);
+ }
+ else
+ {
+ RestartDisplay (d, TRUE);
+ }
+ break;
+ case RESERVER_DISPLAY:
+ d->startTries = 0;
+ Debug ("Display exited with RESERVER_DISPLAY\n");
+ if (d->displayType.origin == FromXDMCP || d->status == zombie)
+ StopDisplay(d);
+ else
+ RestartDisplay (d, TRUE);
+ break;
+ case waitCompose (SIGTERM,0,0):
+ d->startTries = 0;
+ Debug ("Display exited on SIGTERM\n");
+ if (d->displayType.origin == FromXDMCP || d->status == zombie)
+ StopDisplay(d);
+ else
+ RestartDisplay (d, TRUE);
+ break;
+ case REMANAGE_DISPLAY:
+ d->startTries = 0;
+ Debug ("Display exited with REMANAGE_DISPLAY\n");
+ /*
+ * XDMCP will restart the session if the display
+ * requests it
+ */
+ if (d->displayType.origin == FromXDMCP || d->status == zombie)
+ StopDisplay(d);
+ else
+ RestartDisplay (d, FALSE);
+ break;
+ }
+ }
+ /* SUPPRESS 560 */
+ else if ((d = FindDisplayByServerPid (pid)))
+ {
+ d->serverPid = -1;
+ switch (d->status)
+ {
+ case zombie:
+ Debug ("Zombie server reaped, removing display %s\n", d->name);
+ RemoveDisplay (d);
+ break;
+ case phoenix:
+ Debug ("Phoenix server arises, restarting display %s\n", d->name);
+ d->status = notRunning;
+ break;
+ case running:
+ Debug ("Server for display %s terminated unexpectedly, status %d\n", d->name, waitVal (status));
+ LogError ("Server for display %s terminated unexpectedly: %d\n", d->name, waitVal (status));
+ if (d->pid != -1)
+ {
+ Debug ("Terminating session pid %d\n", d->pid);
+ TerminateProcess (d->pid, SIGTERM);
+ }
+ break;
+ case notRunning:
+ Debug ("Server exited for notRunning session on display %s\n", d->name);
+ break;
+ }
+ }
+ else
+ {
+ Debug ("Unknown child termination, status %d\n", waitVal (status));
+ }
+ }
+ StartDisplays ();
+}
+
+static void
+CheckDisplayStatus (d)
+struct display *d;
+{
+ if (d->displayType.origin == FromFile)
+ {
+ switch (d->state) {
+ case MissingEntry:
+ StopDisplay (d);
+ break;
+ case NewEntry:
+ d->state = OldEntry;
+ case OldEntry:
+ if (d->status == notRunning)
+ StartDisplay (d);
+ break;
+ }
+ }
+}
+
+static void
+StartDisplays ()
+{
+ ForEachDisplay (CheckDisplayStatus);
+}
+
+void
+StartDisplay (d)
+struct display *d;
+{
+ int pid;
+
+ Debug ("StartDisplay %s\n", d->name);
+ LoadServerResources (d);
+ if (d->displayType.location == Local)
+ {
+ /* don't bother pinging local displays; we'll
+ * certainly notice when they exit
+ */
+ d->pingInterval = 0;
+ if (d->authorize)
+ {
+ Debug ("SetLocalAuthorization %s, auth %s\n",
+ d->name, d->authNames[0]);
+ SetLocalAuthorization (d);
+ /*
+ * reset the server after writing the authorization information
+ * to make it read the file (for compatibility with old
+ * servers which read auth file only on reset instead of
+ * at first connection)
+ */
+ if (d->serverPid != -1 && d->resetForAuth && d->resetSignal)
+ kill (d->serverPid, d->resetSignal);
+ }
+ if (d->serverPid == -1 && !StartServer (d))
+ {
+ LogError ("Server for display %s can't be started, session disabled\n", d->name);
+ RemoveDisplay (d);
+ return;
+ }
+ }
+ else
+ {
+ /* this will only happen when using XDMCP */
+ if (d->authorizations)
+ SaveServerAuthorizations (d, d->authorizations, d->authNum);
+ }
+ if (!nofork_session)
+ pid = fork ();
+ else
+ pid = 0;
+ switch (pid)
+ {
+ case 0:
+ if (!nofork_session) {
+ CleanUpChild ();
+ (void) Signal (SIGPIPE, SIG_IGN);
+ }
+ LoadSessionResources (d);
+ SetAuthorization (d);
+ if (!WaitForServer (d))
+ exit (OPENFAILED_DISPLAY);
+#ifdef XDMCP
+ if (d->useChooser)
+ RunChooser (d);
+ else
+#endif
+ ManageSession (d);
+ exit (REMANAGE_DISPLAY);
+ case -1:
+ break;
+ default:
+ Debug ("pid: %d\n", pid);
+ d->pid = pid;
+ d->status = running;
+ break;
+ }
+}
+
+TerminateProcess (pid, signal)
+{
+ kill (pid, signal);
+#ifdef SIGCONT
+ kill (pid, SIGCONT);
+#endif
+}
+
+/*
+ * transition from running to zombie or deleted
+ */
+
+void
+StopDisplay (d)
+ struct display *d;
+{
+ if (d->serverPid != -1)
+ d->status = zombie; /* be careful about race conditions */
+ if (d->pid != -1)
+ TerminateProcess (d->pid, SIGTERM);
+ if (d->serverPid != -1)
+ TerminateProcess (d->serverPid, d->termSignal);
+ else
+ RemoveDisplay (d);
+}
+
+/*
+ * transition from running to phoenix or notRunning
+ */
+
+static void
+RestartDisplay (d, forceReserver)
+ struct display *d;
+ int forceReserver;
+{
+ if (d->serverPid != -1 && (forceReserver || d->terminateServer))
+ {
+ TerminateProcess (d->serverPid, d->termSignal);
+ d->status = phoenix;
+ }
+ else
+ {
+ d->status = notRunning;
+ }
+}
+
+static FD_TYPE CloseMask;
+static int max;
+
+RegisterCloseOnFork (fd)
+int fd;
+{
+ FD_SET (fd, &CloseMask);
+ if (fd > max)
+ max = fd;
+}
+
+ClearCloseOnFork (fd)
+int fd;
+{
+ FD_CLR (fd, &CloseMask);
+ if (fd == max) {
+ while (--fd >= 0)
+ if (FD_ISSET (fd, &CloseMask))
+ break;
+ max = fd;
+ }
+}
+
+CloseOnFork ()
+{
+ int fd;
+
+ for (fd = 0; fd <= max; fd++)
+ if (FD_ISSET (fd, &CloseMask))
+ close (fd);
+ FD_ZERO (&CloseMask);
+ max = 0;
+}
+
+static int pidFd;
+static FILE *pidFilePtr;
+
+StorePid ()
+{
+ int oldpid;
+
+ if (pidFile[0] != '\0') {
+ pidFd = open (pidFile, 2);
+ if (pidFd == -1 && errno == ENOENT)
+ pidFd = open (pidFile, O_RDWR|O_CREAT, 0666);
+ if (pidFd == -1 || !(pidFilePtr = fdopen (pidFd, "r+")))
+ {
+ LogError ("process-id file %s cannot be opened\n",
+ pidFile);
+ return -1;
+ }
+ if (fscanf (pidFilePtr, "%d\n", &oldpid) != 1)
+ oldpid = -1;
+ fseek (pidFilePtr, 0l, 0);
+ if (lockPidFile)
+ {
+#ifdef F_SETLK
+#ifndef SEEK_SET
+#define SEEK_SET 0
+#endif
+ struct flock lock_data;
+ lock_data.l_type = F_WRLCK;
+ lock_data.l_whence = SEEK_SET;
+ lock_data.l_start = lock_data.l_len = 0;
+ if (fcntl(pidFd, F_SETLK, &lock_data) == -1)
+ {
+ if (errno == EAGAIN)
+ return oldpid;
+ else
+ return -1;
+ }
+#else
+#ifdef LOCK_EX
+ if (flock (pidFd, LOCK_EX|LOCK_NB) == -1)
+ {
+ if (errno == EWOULDBLOCK)
+ return oldpid;
+ else
+ return -1;
+ }
+#else
+ if (lockf (pidFd, F_TLOCK, 0) == -1)
+ {
+ if (errno == EACCES)
+ return oldpid;
+ else
+ return -1;
+ }
+#endif
+#endif
+ }
+ fprintf (pidFilePtr, "%5d\n", getpid ());
+ (void) fflush (pidFilePtr);
+ RegisterCloseOnFork (pidFd);
+ }
+ return 0;
+}
+
+UnlockPidFile ()
+{
+ if (lockPidFile)
+#ifdef F_SETLK
+ {
+ struct flock lock_data;
+ lock_data.l_type = F_UNLCK;
+ lock_data.l_whence = SEEK_SET;
+ lock_data.l_start = lock_data.l_len = 0;
+ (void) fcntl(pidFd, F_SETLK, &lock_data);
+ }
+#else
+#ifdef F_ULOCK
+ lockf (pidFd, F_ULOCK, 0);
+#else
+ flock (pidFd, LOCK_UN);
+#endif
+#endif
+ close (pidFd);
+ fclose (pidFilePtr);
+}
+
+#if NeedVarargsPrototypes
+SetTitle (char *name, ...)
+#else
+/*VARARGS*/
+SetTitle (name, va_alist)
+char *name;
+va_dcl
+#endif
+{
+#ifndef NOXDMTITLE
+ char *p = Title;
+ int left = TitleLen;
+ char *s;
+ va_list args;
+
+ Va_start(args,name);
+ *p++ = '-';
+ --left;
+ s = name;
+ while (s)
+ {
+ while (*s && left > 0)
+ {
+ *p++ = *s++;
+ left--;
+ }
+ s = va_arg (args, char *);
+ }
+ while (left > 0)
+ {
+ *p++ = ' ';
+ --left;
+ }
+ va_end(args);
+#endif
+}