http://www.tienza.es/crux/src/www.jwz.org/xscreensaver/xscreensaver-5.05.tar.gz
[xscreensaver] / driver / subprocs.c
index 1876f83290d06c624e085a134bb2fe009f5825e9..88270d6ca0ad448ba55f374d1110faa3fb02ed5c 100644 (file)
@@ -1,6 +1,5 @@
 /* subprocs.c --- choosing, spawning, and killing screenhacks.
 /* subprocs.c --- choosing, spawning, and killing screenhacks.
- * xscreensaver, Copyright (c) 1991, 1992, 1993, 1995, 1997, 1998
- *  Jamie Zawinski <jwz@netscape.com>
+ * xscreensaver, Copyright (c) 1991-2008 Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
 #include <X11/Xlib.h>          /* not used for much... */
 
 #ifndef ESRCH
 #include <X11/Xlib.h>          /* not used for much... */
 
 #ifndef ESRCH
-#include <errno.h>
+# include <errno.h>
 #endif
 
 #include <sys/time.h>          /* sys/resource.h needs this for timeval */
 #endif
 
 #include <sys/time.h>          /* sys/resource.h needs this for timeval */
+#include <sys/param.h>         /* for PATH_MAX */
 
 
-#ifndef VMS
-
-# include <sys/resource.h>     /* for setpriority() and PRIO_PROCESS */
+#ifdef HAVE_SYS_WAIT_H
 # include <sys/wait.h>         /* for waitpid() and associated macros */
 # include <sys/wait.h>         /* for waitpid() and associated macros */
+#endif
 
 
-#else  /* VMS */
-
-# if __DECC_VER >= 50200000
-#  include <sys/wait.h>
-# endif
+#ifdef HAVE_SETRLIMIT
+# include <sys/resource.h>     /* for setrlimit() and RLIMIT_AS */
+#endif
 
 
+#ifdef VMS
 # include <processes.h>
 # include <unixio.h>           /* for close */
 # include <unixlib.h>          /* for getpid */
 # include <processes.h>
 # include <unixio.h>           /* for close */
 # include <unixlib.h>          /* for getpid */
-# define pid_t    int
-# define fork     vfork
-
+# define pid_t int
+# define fork  vfork
 #endif /* VMS */
 
 #include <signal.h>            /* for the signal names */
 
 #endif /* VMS */
 
 #include <signal.h>            /* for the signal names */
 
-#ifndef NO_SETUID
-#include <pwd.h>               /* for getpwnam() and struct passwd */
-#include <grp.h>               /* for getgrgid() and struct group */
-#endif /* NO_SETUID */
-
 #if !defined(SIGCHLD) && defined(SIGCLD)
 #if !defined(SIGCHLD) && defined(SIGCLD)
-#define SIGCHLD SIGCLD
+# define SIGCHLD SIGCLD
 #endif
 
 #endif
 
+#if 0 /* putenv() is declared in stdlib.h on modern linux systems. */
 #ifdef HAVE_PUTENV
 extern int putenv (/* const char * */);        /* getenv() is in stdlib.h... */
 #endif
 #ifdef HAVE_PUTENV
 extern int putenv (/* const char * */);        /* getenv() is in stdlib.h... */
 #endif
+#endif
+
 extern int kill (pid_t, int);          /* signal() is in sys/signal.h... */
 
 /* This file doesn't need the Xt headers, so stub these types out... */
 extern int kill (pid_t, int);          /* signal() is in sys/signal.h... */
 
 /* This file doesn't need the Xt headers, so stub these types out... */
@@ -71,212 +66,133 @@ extern int kill (pid_t, int);             /* signal() is in sys/signal.h... */
 #define Widget       void*
 
 #include "xscreensaver.h"
 #define Widget       void*
 
 #include "xscreensaver.h"
+#include "exec.h"
 #include "yarandom.h"
 #include "yarandom.h"
-
+#include "visual.h"    /* for id_to_visual() */
 
 extern saver_info *global_si_kludge;   /* I hate C so much... */
 
 
 extern saver_info *global_si_kludge;   /* I hate C so much... */
 
-static void hack_subproc_environment (saver_screen_info *ssi);
-
 
 
-static void
-nice_subproc (int nice_level)
+/* Used when printing error/debugging messages from signal handlers.
+ */
+static const char *
+no_malloc_number_to_string (long num)
 {
 {
-  if (nice_level == 0)
-    return;
-
-#if defined(HAVE_NICE)
-  {
-    int old_nice = nice (0);
-    int n = nice_level - old_nice;
-    errno = 0;
-    if (nice (n) == -1 && errno != 0)
-      {
-       char buf [512];
-       sprintf (buf, "%s: nice(%d) failed", blurb(), n);
-       perror (buf);
-    }
-  }
-#elif defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
-  if (setpriority (PRIO_PROCESS, getpid(), nice_level) != 0)
-    {
-      char buf [512];
-      sprintf (buf, "%s: setpriority(PRIO_PROCESS, %lu, %d) failed",
-              blurb(), (unsigned long) getpid(), nice_level);
-      perror (buf);
-    }
-#else
-  fprintf (stderr,
-          "%s: don't know how to change process priority on this system.\n",
-          blurb());
+  static char string[128] = "";
+  int num_digits;
+  Bool negative_p = False;
 
 
-#endif
-}
+  num_digits = 0;
 
 
+  if (num == 0)
+    return "0";
 
 
-#ifndef VMS
-
-static void
-exec_simple_command (const char *command)
-{
-  char *av[1024];
-  int ac = 0;
-  char *token = strtok (strdup(command), " \t");
-  while (token)
+  if (num < 0)
     {
     {
-      av[ac++] = token;
-      token = strtok(0, " \t");
+      negative_p = True;
+      num = -num;
     }
     }
-  av[ac] = 0;
 
 
-  execvp (av[0], av);                  /* shouldn't return. */
+  while ((num > 0) && (num_digits < sizeof(string) - 1))
+    {
+      int digit;
+      digit = (int) num % 10;
+      num_digits++;
+      string[sizeof(string) - 1 - num_digits] = digit + '0';
+      num /= 10;
+    }
 
 
-  {
-    char buf [512];
-    sprintf (buf, "%s: could not execute \"%s\"", blurb(), av[0]);
-    perror (buf);
+  if (negative_p)
+    {
+      num_digits++;
+      string[sizeof(string) - 1 - num_digits] = '-';
+    }
 
 
-    if (errno == ENOENT &&
-       (token = getenv("PATH")))
-      {
-# ifndef PATH_MAX
-#  ifdef MAXPATHLEN
-#   define PATH_MAX MAXPATHLEN
-#  else
-#   define PATH_MAX 2048
-#  endif
-# endif
-       char path[PATH_MAX];
-       fprintf (stderr, "\n");
-       *path = 0;
-# if defined(HAVE_GETCWD)
-       getcwd (path, sizeof(path));
-# elif defined(HAVE_GETWD)
-       getwd (path);
-# endif
-       if (*path)
-         fprintf (stderr, "    Current directory is: %s\n", path);
-       fprintf (stderr, "    PATH is:\n");
-       token = strtok (strdup(token), ":");
-       while (token)
-         {
-           fprintf (stderr, "        %s\n", token);
-           token = strtok(0, ":");
-         }
-       fprintf (stderr, "\n");
-      }
-  }
-  fflush(stderr);
-  fflush(stdout);
-  exit (1);    /* Note that this only exits a child fork.  */
+  return string + sizeof(string) - 1 - num_digits;
 }
 
 }
 
+/* Like write(), but runs strlen() on the arg to get the length. */
+static int
+write_string (int fd, const char *str)
+{
+  return write (fd, str, strlen (str));
+}
 
 
-static void
-exec_complex_command (const char *shell, const char *command)
+static int
+write_long (int fd, long n)
 {
 {
-  char *av[5];
-  int ac = 0;
-  char *command2 = (char *) malloc (strlen (command) + 6);
-  memcpy (command2, "exec ", 5);
-  memcpy (command2 + 5, command, strlen (command) + 1);
+  const char *str = no_malloc_number_to_string (n);
+  return write_string (fd, str);
+}
 
 
-  /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
-  av [ac++] = (char *) shell;
-  av [ac++] = "-c";
-  av [ac++] = command2;
-  av [ac]   = 0;
 
 
-  execvp (av[0], av);                  /* shouldn't return. */
+/* RLIMIT_AS (called RLIMIT_VMEM on some systems) controls the maximum size
+   of a process's address space, i.e., the maximal brk(2) and mmap(2) values.
+   Setting this lets you put a cap on how much memory a process can allocate.
 
 
-  {
-    char buf [512];
-    sprintf (buf, "%s: execvp(\"%s\") failed", blurb(), av[0]);
-    perror (buf);
-    fflush(stderr);
-    fflush(stdout);
-    exit (1);  /* Note that this only exits a child fork.  */
-  }
-}
-
-#else  /* VMS */
+   Except the "and mmap()" part kinda makes this useless, since many GL
+   implementations end up using mmap() to pull the whole frame buffer into
+   memory (or something along those lines) making it appear processes are
+   using hundreds of megabytes when in fact they're using very little, and
+   we end up capping their mallocs prematurely.  YAY!
+ */
+#if defined(RLIMIT_VMEM) && !defined(RLIMIT_AS)
+# define RLIMIT_AS RLIMIT_VMEM
+#endif
 
 static void
 
 static void
-exec_vms_command (const char *command)
+limit_subproc_memory (int address_space_limit, Bool verbose_p)
 {
 {
-  system (command);
-  fflush (stderr);
-  fflush (stdout);
-  exit (1);    /* Note that this only exits a child fork.  */
-}
 
 
-#endif /* !VMS */
+/* This has caused way more problems than it has solved...
+   Let's just completely ignore the "memoryLimit" option now.
+ */
+#undef HAVE_SETRLIMIT
 
 
+#if defined(HAVE_SETRLIMIT) && defined(RLIMIT_AS)
+  struct rlimit r;
 
 
-static void
-exec_screenhack (saver_info *si, const char *command)
-{
-  /* I don't believe what a sorry excuse for an operating system UNIX is!
-
-     - I want to spawn a process.
-     - I want to know it's pid so that I can kill it.
-     - I would like to receive a message when it dies of natural causes.
-     - I want the spawned process to have user-specified arguments.
-
-     If shell metacharacters are present (wildcards, backquotes, etc), the
-     only way to parse those arguments is to run a shell to do the parsing
-     for you.
-
-     And the only way to know the pid of the process is to fork() and exec()
-     it in the spawned side of the fork.
-
-     But if you're running a shell to parse your arguments, this gives you
-     the pid of the *shell*, not the pid of the *process* that you're
-     actually interested in, which is an *inferior* of the shell.  This also
-     means that the SIGCHLD you get applies to the shell, not its inferior.
-     (Why isn't that sufficient?  I don't remember any more, but it turns
-     out that it isn't.)
-
-     So, the only solution, when metacharacters are present, is to force the
-     shell to exec() its inferior.  What a fucking hack!  We prepend "exec "
-     to the command string, and hope it doesn't contain unquoted semicolons
-     or ampersands (we don't search for them, because we don't want to
-     prohibit their use in quoted strings (messages, for example) and parsing
-     out the various quote characters is too much of a pain.)
-
-     (Actually, Clint Wong <clint@jts.com> points out that process groups
-     might be used to take care of this problem; this may be worth considering
-     some day, except that, 1: this code works now, so why fix it, and 2: from
-     what I've seen in Emacs, dealing with process groups isn't especially
-     portable.)
-   */
-  saver_preferences *p = &si->prefs;
+  if (address_space_limit < 10 * 1024)  /* let's not be crazy */
+    return;
 
 
-#ifndef VMS
-  Bool hairy_p = !!strpbrk (command, "*?$&!<>[];`'\\\"");
+  if (getrlimit (RLIMIT_AS, &r) != 0)
+    {
+      char buf [512];
+      sprintf (buf, "%s: getrlimit(RLIMIT_AS) failed", blurb());
+      perror (buf);
+      return;
+    }
 
 
-  if (p->verbose_p)
-    printf ("%s: spawning \"%s\" in pid %lu%s.\n",
-           blurb(), command, (unsigned long) getpid (),
-           (hairy_p ? " (via shell)" : ""));
-
-  if (hairy_p)
-    /* If it contains any shell metacharacters, do it the hard way,
-       and fork a shell to parse the arguments for us. */
-    exec_complex_command (p->shell, command);
-  else
-    /* Otherwise, we can just exec the program directly. */
-    exec_simple_command (command);
+  r.rlim_cur = address_space_limit;
 
 
-#else /* VMS */
-  if (p->verbose_p)
-    printf ("%s: spawning \"%s\" in pid %lu.\n", blurb(), command, getpid());
-  exec_vms_command (command);
-#endif /* VMS */
+  if (setrlimit (RLIMIT_AS, &r) != 0)
+    {
+      char buf [512];
+      sprintf (buf, "%s: setrlimit(RLIMIT_AS, {%lu, %lu}) failed",
+               blurb(), r.rlim_cur, r.rlim_max);
+      perror (buf);
+      return;
+    }
 
 
-  abort();     /* that shouldn't have returned. */
-}
+  if (verbose_p)
+    {
+      int i = address_space_limit;
+      char buf[100];
+      if      (i >= (1<<30) && i == ((i >> 30) << 30))
+        sprintf(buf, "%dG", i >> 30);
+      else if (i >= (1<<20) && i == ((i >> 20) << 20))
+        sprintf(buf, "%dM", i >> 20);
+      else if (i >= (1<<10) && i == ((i >> 10) << 10))
+        sprintf(buf, "%dK", i >> 10);
+      else
+        sprintf(buf, "%d bytes", i);
+
+      fprintf (stderr, "%s: limited pid %lu address space to %s.\n",
+               blurb(), (unsigned long) getpid (), buf);
+    }
 
 
+#endif /* HAVE_SETRLIMIT && RLIMIT_AS */
+}
 
 \f
 /* Management of child processes, and de-zombification.
 
 \f
 /* Management of child processes, and de-zombification.
@@ -295,21 +211,26 @@ enum job_status {
 struct screenhack_job {
   char *name;
   pid_t pid;
 struct screenhack_job {
   char *name;
   pid_t pid;
+  int screen;
   enum job_status status;
   struct screenhack_job *next;
 };
 
 static struct screenhack_job *jobs = 0;
 
   enum job_status status;
   struct screenhack_job *next;
 };
 
 static struct screenhack_job *jobs = 0;
 
-/* for debugging -- nothing calls this, but it's useful to invoke from gdb. */
+/* for debugging -- nothing calls this, but it's useful to invoke from gdb.
+ */
+void show_job_list (void);
+
 void
 show_job_list (void)
 {
   struct screenhack_job *job;
   fprintf(stderr, "%s: job list:\n", blurb());
   for (job = jobs; job; job = job->next)
 void
 show_job_list (void)
 {
   struct screenhack_job *job;
   fprintf(stderr, "%s: job list:\n", blurb());
   for (job = jobs; job; job = job->next)
-    fprintf (stderr, "  %5ld: (%s) %s\n",
+    fprintf (stderr, "  %5ld: %2d: (%s) %s\n",
             (long) job->pid,
             (long) job->pid,
+             job->screen,
             (job->status == job_running ? "running" :
              job->status == job_stopped ? "stopped" :
              job->status == job_killed  ? " killed" :
             (job->status == job_running ? "running" :
              job->status == job_stopped ? "stopped" :
              job->status == job_killed  ? " killed" :
@@ -322,31 +243,39 @@ show_job_list (void)
 static void clean_job_list (void);
 
 static struct screenhack_job *
 static void clean_job_list (void);
 
 static struct screenhack_job *
-make_job (pid_t pid, const char *cmd)
+make_job (pid_t pid, int screen, const char *cmd)
 {
   struct screenhack_job *job = (struct screenhack_job *) malloc (sizeof(*job));
 
   static char name [1024];
   const char *in = cmd;
   char *out = name;
 {
   struct screenhack_job *job = (struct screenhack_job *) malloc (sizeof(*job));
 
   static char name [1024];
   const char *in = cmd;
   char *out = name;
+  int got_eq = 0;
+  int first = 1;
 
   clean_job_list();
 
 
   clean_job_list();
 
+ AGAIN:
   while (isspace(*in)) in++;           /* skip whitespace */
   while (isspace(*in)) in++;           /* skip whitespace */
-  while (!isspace(*in) && *in != ':')
+  while (!isspace(*in) && *in != ':') {
+    if (*in == '=') got_eq = 1;
     *out++ = *in++;                    /* snarf first token */
     *out++ = *in++;                    /* snarf first token */
-  while (isspace(*in)) in++;           /* skip whitespace */
-  if (*in == ':')                      /* token was a visual name; skip it. */
-    {
-      in++;
+  }
+
+  if (got_eq)                          /* if the first token was FOO=bar */
+    {                                  /* then get the next token instead. */
+      got_eq = 0;
       out = name;
       out = name;
-      while (isspace(*in)) in++;               /* skip whitespace */
-      while (!isspace(*in)) *out++ = *in++;    /* snarf first token */
+      first = 0;
+      goto AGAIN;
     }
     }
+
+  while (isspace(*in)) in++;           /* skip whitespace */
   *out = 0;
 
   job->name = strdup(name);
   job->pid = pid;
   *out = 0;
 
   job->name = strdup(name);
   job->pid = pid;
+  job->screen = screen;
   job->status = job_running;
   job->next = jobs;
   jobs = job;
   job->status = job_running;
   job->next = jobs;
   jobs = job;
@@ -423,28 +352,56 @@ static void describe_dead_child (saver_info *, pid_t, int wait_status);
 static int block_sigchld_handler = 0;
 
 
 static int block_sigchld_handler = 0;
 
 
-static void
+#ifdef HAVE_SIGACTION
+ sigset_t
+#else  /* !HAVE_SIGACTION */
+ int
+#endif /* !HAVE_SIGACTION */
 block_sigchld (void)
 {
 #ifdef HAVE_SIGACTION
 block_sigchld (void)
 {
 #ifdef HAVE_SIGACTION
+  struct sigaction sa;
   sigset_t child_set;
   sigset_t child_set;
+
+  memset (&sa, 0, sizeof (sa));
+  sa.sa_handler = SIG_IGN;
+  sigaction (SIGPIPE, &sa, NULL);
+
   sigemptyset (&child_set);
   sigaddset (&child_set, SIGCHLD);
   sigprocmask (SIG_BLOCK, &child_set, 0);
   sigemptyset (&child_set);
   sigaddset (&child_set, SIGCHLD);
   sigprocmask (SIG_BLOCK, &child_set, 0);
-#endif /* HAVE_SIGACTION */
+
+#else  /* !HAVE_SIGACTION */
+  signal (SIGPIPE, SIG_IGN);
+#endif /* !HAVE_SIGACTION */
 
   block_sigchld_handler++;
 
   block_sigchld_handler++;
+
+#ifdef HAVE_SIGACTION
+  return child_set;
+#else  /* !HAVE_SIGACTION */
+  return 0;
+#endif /* !HAVE_SIGACTION */
 }
 
 }
 
-static void
+void
 unblock_sigchld (void)
 {
 #ifdef HAVE_SIGACTION
 unblock_sigchld (void)
 {
 #ifdef HAVE_SIGACTION
+  struct sigaction sa;
   sigset_t child_set;
   sigset_t child_set;
+
+  memset(&sa, 0, sizeof (sa));
+  sa.sa_handler = SIG_DFL;
+  sigaction(SIGPIPE, &sa, NULL);
+
   sigemptyset(&child_set);
   sigaddset(&child_set, SIGCHLD);
   sigprocmask(SIG_UNBLOCK, &child_set, 0);
   sigemptyset(&child_set);
   sigaddset(&child_set, SIGCHLD);
   sigprocmask(SIG_UNBLOCK, &child_set, 0);
-#endif /* HAVE_SIGACTION */
+
+#else /* !HAVE_SIGACTION */
+  signal(SIGPIPE, SIG_DFL);
+#endif /* !HAVE_SIGACTION */
 
   block_sigchld_handler--;
 }
 
   block_sigchld_handler--;
 }
@@ -485,31 +442,27 @@ kill_job (saver_info *si, pid_t pid, int signal)
   default: abort();
   }
 
   default: abort();
   }
 
-#ifdef SIGSTOP
-  if (p->verbose_p)
-    fprintf (stderr, "%s: %s pid %lu.\n", blurb(),
-            (signal == SIGTERM ? "killing" :
-             signal == SIGSTOP ? "suspending" :
-             signal == SIGCONT ? "resuming" : "signalling"),
-            (unsigned long) job->pid);
-#else  /* !SIGSTOP */
   if (p->verbose_p)
   if (p->verbose_p)
-    fprintf (stderr, "%s: %s pid %lu.\n", blurb(), "killing",
-            (unsigned long) job->pid);
-#endif /* !SIGSTOP */
+    fprintf (stderr, "%s: %d: %s pid %lu (%s)\n",
+             blurb(), job->screen,
+             (job->status == job_killed  ? "killing" :
+              job->status == job_stopped ? "suspending" : "resuming"),
+             (unsigned long) job->pid,
+             job->name);
 
   status = kill (job->pid, signal);
 
   if (p->verbose_p && status < 0)
     {
       if (errno == ESRCH)
 
   status = kill (job->pid, signal);
 
   if (p->verbose_p && status < 0)
     {
       if (errno == ESRCH)
-       fprintf (stderr, "%s: child process %lu (%s) was already dead.\n",
-                blurb(), job->pid, job->name);
+       fprintf (stderr,
+                 "%s: %d: child process %lu (%s) was already dead.\n",
+                blurb(), job->screen, (unsigned long) job->pid, job->name);
       else
        {
          char buf [1024];
       else
        {
          char buf [1024];
-         sprintf (buf, "%s: couldn't kill child process %lu (%s)",
-                  blurb(), job->pid, job->name);
+         sprintf (buf, "%s: %d: couldn't kill child process %lu (%s)",
+                  blurb(), job->screen, (unsigned long) job->pid, job->name);
          perror (buf);
        }
     }
          perror (buf);
        }
     }
@@ -533,8 +486,19 @@ sigchld_handler (int sig)
   saver_info *si = global_si_kludge;   /* I hate C so much... */
 
   if (si->prefs.debug_p)
   saver_info *si = global_si_kludge;   /* I hate C so much... */
 
   if (si->prefs.debug_p)
-    fprintf(stderr, "%s: got SIGCHLD%s\n", blurb(),
+    {
+      /* Don't call fprintf() from signal handlers, as it might malloc.
+      fprintf(stderr, "%s: got SIGCHLD%s\n", blurb(),
            (block_sigchld_handler ? " (blocked)" : ""));
            (block_sigchld_handler ? " (blocked)" : ""));
+      */
+      write_string (STDERR_FILENO, blurb());
+      write_string (STDERR_FILENO, ": got SIGCHLD");
+
+      if (block_sigchld_handler)
+        write_string (STDERR_FILENO, " (blocked)\n");
+      else
+        write_string (STDERR_FILENO, "\n");
+    }
 
   if (block_sigchld_handler < 0)
     abort();
 
   if (block_sigchld_handler < 0)
     abort();
@@ -551,6 +515,7 @@ sigchld_handler (int sig)
 
 
 #ifndef VMS
 
 
 #ifndef VMS
+
 static void
 await_dying_children (saver_info *si)
 {
 static void
 await_dying_children (saver_info *si)
 {
@@ -565,11 +530,29 @@ await_dying_children (saver_info *si)
       if (si->prefs.debug_p)
        {
          if (kid < 0 && errno)
       if (si->prefs.debug_p)
        {
          if (kid < 0 && errno)
-           fprintf (stderr, "%s: waitpid(-1) ==> %ld (%d)\n", blurb(),
-                    (long) kid, errno);
-         else
-           fprintf (stderr, "%s: waitpid(-1) ==> %ld\n", blurb(),
-                    (long) kid);
+            {
+              /* Don't call fprintf() from signal handlers, as it might malloc.
+             fprintf (stderr, "%s: waitpid(-1) ==> %ld (%d)\n", blurb(),
+                      (long) kid, errno);
+               */
+              write_string (STDERR_FILENO, blurb());
+              write_string (STDERR_FILENO, ": waitpid(-1) ==> ");
+              write_long   (STDERR_FILENO, (long) kid);
+              write_string (STDERR_FILENO, " (");
+              write_long   (STDERR_FILENO, (long) errno);
+              write_string (STDERR_FILENO, ")\n");
+            }
+          else
+            {
+              /* Don't call fprintf() from signal handlers, as it might malloc.
+              fprintf (stderr, "%s: waitpid(-1) ==> %ld\n", blurb(),
+                       (long) kid);
+               */
+              write_string (STDERR_FILENO, blurb());
+              write_string (STDERR_FILENO, ": waitpid(-1) ==> ");
+              write_long   (STDERR_FILENO, (long) kid);
+              write_string (STDERR_FILENO, "\n");
+            }
        }
 
       /* 0 means no more children to reap.
        }
 
       /* 0 means no more children to reap.
@@ -591,6 +574,7 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status)
   saver_preferences *p = &si->prefs;
   struct screenhack_job *job = find_job (kid);
   const char *name = job ? job->name : "<unknown>";
   saver_preferences *p = &si->prefs;
   struct screenhack_job *job = find_job (kid);
   const char *name = job ? job->name : "<unknown>";
+  int screen_no = job ? job->screen : 0;
 
   if (WIFEXITED (wait_status))
     {
 
   if (WIFEXITED (wait_status))
     {
@@ -608,12 +592,38 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status)
       if (!job ||
          (exit_status != 0 &&
           (p->verbose_p || job->status != job_killed)))
       if (!job ||
          (exit_status != 0 &&
           (p->verbose_p || job->status != job_killed)))
-       fprintf (stderr,
-                "%s: child pid %lu (%s) exited abnormally (code %d).\n",
-                blurb(), (unsigned long) kid, name, exit_status);
+        {
+          /* Don't call fprintf() from signal handlers, as it might malloc.
+         fprintf (stderr,
+                  "%s: %d: child pid %lu (%s) exited abnormally (code %d).\n",
+                  blurb(), screen_no, (unsigned long) kid, name, exit_status);
+           */
+          write_string (STDERR_FILENO, blurb());
+          write_string (STDERR_FILENO, ": ");
+          write_long   (STDERR_FILENO, (long) screen_no);
+          write_string (STDERR_FILENO, ": child pid ");
+          write_long   (STDERR_FILENO, (long) kid);
+          write_string (STDERR_FILENO, " (");
+          write_string (STDERR_FILENO, name);
+          write_string (STDERR_FILENO, ") exited abnormally (code ");
+          write_long   (STDERR_FILENO, (long) exit_status);
+          write_string (STDERR_FILENO, ").\n"); 
+        }
       else if (p->verbose_p)
       else if (p->verbose_p)
-       printf ("%s: child pid %lu (%s) exited normally.\n",
-               blurb(), (unsigned long) kid, name);
+        {
+          /* Don't call fprintf() from signal handlers, as it might malloc.
+         fprintf (stderr, "%s: %d: child pid %lu (%s) exited normally.\n",
+                  blurb(), screen_no, (unsigned long) kid, name);
+           */
+          write_string (STDERR_FILENO, blurb());
+          write_string (STDERR_FILENO, ": ");
+          write_long   (STDERR_FILENO, (long) screen_no);
+          write_string (STDERR_FILENO, ": child pid ");
+          write_long   (STDERR_FILENO, (long) kid);
+          write_string (STDERR_FILENO, " (");
+          write_string (STDERR_FILENO, name);
+          write_string (STDERR_FILENO, ") exited normally.\n");
+        }
 
       if (job)
        job->status = job_dead;
 
       if (job)
        job->status = job_dead;
@@ -624,9 +634,23 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status)
          !job ||
          job->status != job_killed ||
          WTERMSIG (wait_status) != SIGTERM)
          !job ||
          job->status != job_killed ||
          WTERMSIG (wait_status) != SIGTERM)
-       fprintf (stderr, "%s: child pid %lu (%s) terminated with %s.\n",
-                blurb(), (unsigned long) kid, name,
-                signal_name (WTERMSIG(wait_status)));
+        {
+          /* Don't call fprintf() from signal handlers, as it might malloc.
+         fprintf (stderr, "%s: %d: child pid %lu (%s) terminated with %s.\n",
+                  blurb(), screen_no, (unsigned long) kid, name,
+                  signal_name (WTERMSIG(wait_status)));
+           */
+          write_string (STDERR_FILENO, blurb());
+          write_string (STDERR_FILENO, ": ");
+          write_long   (STDERR_FILENO, (long) screen_no);
+          write_string (STDERR_FILENO, ": child pid ");
+          write_long   (STDERR_FILENO, (long) kid);
+          write_string (STDERR_FILENO, " (");
+          write_string (STDERR_FILENO, name);
+          write_string (STDERR_FILENO, ") terminated with signal ");
+          write_long   (STDERR_FILENO, WTERMSIG(wait_status));
+          write_string (STDERR_FILENO, ".\n");
+        }
 
       if (job)
        job->status = job_dead;
 
       if (job)
        job->status = job_dead;
@@ -634,17 +658,41 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status)
   else if (WIFSTOPPED (wait_status))
     {
       if (p->verbose_p)
   else if (WIFSTOPPED (wait_status))
     {
       if (p->verbose_p)
-       fprintf (stderr, "%s: child pid %lu (%s) stopped with %s.\n",
-                blurb(), (unsigned long) kid, name,
-                signal_name (WSTOPSIG (wait_status)));
+        {
+          /* Don't call fprintf() from signal handlers, as it might malloc.
+         fprintf (stderr, "%s: child pid %lu (%s) stopped with %s.\n",
+                  blurb(), (unsigned long) kid, name,
+                  signal_name (WSTOPSIG (wait_status)));
+           */
+          write_string (STDERR_FILENO, blurb());
+          write_string (STDERR_FILENO, ": ");
+          write_long   (STDERR_FILENO, (long) screen_no);
+          write_string (STDERR_FILENO, ": child pid ");
+          write_long   (STDERR_FILENO, (long) kid);
+          write_string (STDERR_FILENO, " (");
+          write_string (STDERR_FILENO, name);
+          write_string (STDERR_FILENO, ") stopped with signal ");
+          write_long   (STDERR_FILENO, WSTOPSIG(wait_status));
+          write_string (STDERR_FILENO, ".\n");
+        }
 
       if (job)
        job->status = job_stopped;
     }
   else
     {
 
       if (job)
        job->status = job_stopped;
     }
   else
     {
+      /* Don't call fprintf() from signal handlers, as it might malloc.
       fprintf (stderr, "%s: child pid %lu (%s) died in a mysterious way!",
               blurb(), (unsigned long) kid, name);
       fprintf (stderr, "%s: child pid %lu (%s) died in a mysterious way!",
               blurb(), (unsigned long) kid, name);
+       */
+      write_string (STDERR_FILENO, blurb());
+      write_string (STDERR_FILENO, ": ");
+      write_long   (STDERR_FILENO, (long) screen_no);
+      write_string (STDERR_FILENO, ": child pid ");
+      write_long   (STDERR_FILENO, (long) kid);
+      write_string (STDERR_FILENO, " (");
+      write_string (STDERR_FILENO, name);
+      write_string (STDERR_FILENO, ") died in a mysterious way!");
       if (job)
        job->status = job_dead;
     }
       if (job)
        job->status = job_dead;
     }
@@ -707,37 +755,126 @@ init_sigchld (void)
 \f
 
 static Bool
 \f
 
 static Bool
-select_visual_of_hack (saver_screen_info *ssi, const char *hack)
+select_visual_of_hack (saver_screen_info *ssi, screenhack *hack)
 {
   saver_info *si = ssi->global;
   saver_preferences *p = &si->prefs;
   Bool selected;
 {
   saver_info *si = ssi->global;
   saver_preferences *p = &si->prefs;
   Bool selected;
-  static char vis [1024];
-  const char *in = hack;
-  char *out = vis;
-  while (isspace(*in)) in++;           /* skip whitespace */
-  while (!isspace(*in) && *in != ':')
-    *out++ = *in++;                    /* snarf first token */
-  while (isspace(*in)) in++;           /* skip whitespace */
-  *out = 0;
 
 
-  if (*in == ':')
-    selected = select_visual(ssi, vis);
+  if (hack->visual && *hack->visual)
+    selected = select_visual(ssi, hack->visual);
   else
     selected = select_visual(ssi, 0);
 
   else
     selected = select_visual(ssi, 0);
 
-  if (!selected && (p->verbose_p || si->demo_mode_p))
+  if (!selected && (p->verbose_p || si->demoing_p))
+    fprintf (stderr,
+             (si->demoing_p
+              ? "%s: warning, no \"%s\" visual for \"%s\".\n"
+              : "%s: no \"%s\" visual; skipping \"%s\".\n"),
+             blurb(),
+             (hack->visual && *hack->visual ? hack->visual : "???"),
+             hack->command);
+
+  return selected;
+}
+
+
+static void
+print_path_error (const char *program)
+{
+  char buf [512];
+  char *cmd = strdup (program);
+  char *token = strchr (cmd, ' ');
+
+  if (token) *token = 0;
+  sprintf (buf, "%s: could not execute \"%.100s\"", blurb(), cmd);
+  free (cmd);
+  perror (buf);
+
+  if (errno == ENOENT &&
+      (token = getenv("PATH")))
     {
     {
-      if (*in == ':') in++;
-      while (isspace(*in)) in++;
-      fprintf (stderr,
-              (si->demo_mode_p
-               ? "%s: warning, no \"%s\" visual for \"%s\".\n"
-               : "%s: no \"%s\" visual; skipping \"%s\".\n"),
-              blurb(), (vis ? vis : "???"), in);
+# ifndef PATH_MAX
+#  ifdef MAXPATHLEN
+#   define PATH_MAX MAXPATHLEN
+#  else
+#   define PATH_MAX 2048
+#  endif
+# endif
+      char path[PATH_MAX];
+      fprintf (stderr, "\n");
+      *path = 0;
+# if defined(HAVE_GETCWD)
+      if (! getcwd (path, sizeof(path)))
+        *path = 0;
+# elif defined(HAVE_GETWD)
+      getwd (path);
+# endif
+      if (*path)
+        fprintf (stderr, "    Current directory is: %s\n", path);
+      fprintf (stderr, "    PATH is:\n");
+      token = strtok (strdup(token), ":");
+      while (token)
+        {
+          fprintf (stderr, "        %s\n", token);
+          token = strtok(0, ":");
+        }
+      fprintf (stderr, "\n");
     }
     }
+}
 
 
-  return selected;
+
+/* Executes the command in another process.
+   Command may be any single command acceptable to /bin/sh.
+   It may include wildcards, but no semicolons.
+   If successful, the pid of the other process is returned.
+   Otherwise, -1 is returned and an error may have been
+   printed to stderr.
+ */
+pid_t
+fork_and_exec (saver_screen_info *ssi, const char *command)
+{
+  saver_info *si = ssi->global;
+  saver_preferences *p = &si->prefs;
+  pid_t forked;
+
+  switch ((int) (forked = fork ()))
+    {
+    case -1:
+      {
+        char buf [255];
+        sprintf (buf, "%s: couldn't fork", blurb());
+        perror (buf);
+        break;
+      }
+
+    case 0:
+      close (ConnectionNumber (si->dpy));      /* close display fd */
+      limit_subproc_memory (p->inferior_memory_limit, p->verbose_p);
+      hack_subproc_environment (ssi);          /* set $DISPLAY */
+
+      if (p->verbose_p)
+        fprintf (stderr, "%s: %d: spawning \"%s\" in pid %lu.\n",
+                 blurb(), ssi->number, command,
+                 (unsigned long) getpid ());
+
+      exec_command (p->shell, command, p->nice_inferior);
+
+      /* If that returned, we were unable to exec the subprocess.
+         Print an error message, if desired.
+       */
+      if (! p->ignore_uninstalled_p)
+        print_path_error (command);
+
+      exit (1);  /* exits child fork */
+      break;
+
+    default:   /* parent */
+      (void) make_job (forked, ssi->number, command);
+      break;
+    }
+
+  return forked;
 }
 
 
 }
 
 
@@ -749,96 +886,134 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
   raise_window (si, first_time_p, True, False);
   XFlush (si->dpy);
 
   raise_window (si, first_time_p, True, False);
   XFlush (si->dpy);
 
-  if (p->screenhacks_count || si->demo_mode_p)
+  if (p->screenhacks_count)
     {
     {
-      char *hack;
+      screenhack *hack;
       pid_t forked;
       char buf [255];
       pid_t forked;
       char buf [255];
-      int new_hack;
-
-      if (si->demo_mode_p)
+      int new_hack = -1;
+      int retry_count = 0;
+      Bool force = False;
+
+    AGAIN:
+
+      if (p->screenhacks_count < 1)
+        {
+          /* No hacks at all */
+          new_hack = -1;
+        }
+      else if (p->screenhacks_count == 1)
+        {
+          /* Exactly one hack in the list */
+          new_hack = 0;
+        }
+      else if (si->selection_mode == -1)
+        {
+          /* Select the next hack, wrapping. */
+          new_hack = (ssi->current_hack + 1) % p->screenhacks_count;
+        }
+      else if (si->selection_mode == -2)
+        {
+          /* Select the previous hack, wrapping. */
+          if (ssi->current_hack < 0)
+            new_hack = p->screenhacks_count - 1;
+          else
+            new_hack = ((ssi->current_hack + p->screenhacks_count - 1)
+                        % p->screenhacks_count);
+        }
+      else if (si->selection_mode > 0)
        {
        {
-         hack = si->demo_hack;
-
-         /* Ignore visual-selection failure if in demo mode. */
-         (void) select_visual_of_hack (ssi, hack);
+          /* Select a specific hack, by number (via the ACTIVATE command.) */
+         new_hack = ((si->selection_mode - 1) % p->screenhacks_count);
+         force = True;
        }
        }
-      else
+      else if (p->mode == ONE_HACK &&
+               p->selected_hack >= 0)
        {
        {
-         int retry_count = 0;
-
-       AGAIN:
-         if (p->screenhacks_count == 1)
-           new_hack = 0;
-         else if (si->next_mode_p == 1)
-           new_hack = (ssi->current_hack + 1) % p->screenhacks_count;
-         else if (si->next_mode_p == 2)
-           new_hack = ((ssi->current_hack + p->screenhacks_count - 1)
-                       % p->screenhacks_count);
-         else
-           while ((new_hack = random () % p->screenhacks_count)
-                  == ssi->current_hack)
-             ;
-         ssi->current_hack = new_hack;
-         hack = p->screenhacks[ssi->current_hack];
+          /* Select a specific hack, by number (via "One Saver" mode.) */
+          new_hack = p->selected_hack;
+         force = True;
+       }
+      else if (p->mode == BLANK_ONLY || p->mode == DONT_BLANK)
+        {
+          new_hack = -1;
+        }
+      else if (p->mode == RANDOM_HACKS_SAME &&
+               ssi->number != 0)
+       {
+         /* Use the same hack that's running on screen 0.
+             (Assumes this function was called on screen 0 first.)
+           */
+          new_hack = si->screens[0].current_hack;
+       }
+      else  /* (p->mode == RANDOM_HACKS) */
+       {
+         /* Select a random hack (but not the one we just ran.) */
+         while ((new_hack = random () % p->screenhacks_count)
+                == ssi->current_hack)
+           ;
+       }
 
 
-         if (!select_visual_of_hack (ssi, hack))
+      if (new_hack < 0)   /* don't run a hack */
+        {
+          ssi->current_hack = -1;
+          if (si->selection_mode < 0)
+            si->selection_mode = 0;
+          return;
+        }
+
+      ssi->current_hack = new_hack;
+      hack = p->screenhacks[ssi->current_hack];
+
+      /* If the hack is disabled, or there is no visual for this hack,
+        then try again (move forward, or backward, or re-randomize.)
+        Unless this hack was specified explicitly, in which case,
+        use it regardless.
+       */
+      if (force)
+        select_visual_of_hack (ssi, hack);
+        
+      if (!force &&
+         (!hack->enabled_p ||
+          !on_path_p (hack->command) ||
+          !select_visual_of_hack (ssi, hack)))
+       {
+         if (++retry_count > (p->screenhacks_count*4))
            {
            {
-             if (++retry_count > (p->screenhacks_count*4))
-               {
-                 /* Uh, oops.  Odds are, there are no suitable visuals,
-                    and we're looping.  Give up.  (This is totally lame,
-                    what we should do is make a list of suitable hacks at
-                    the beginning, then only loop over them.)
-                 */
-                 if (p->verbose_p)
-                   fprintf(stderr,
-                           "%s: no suitable visuals for these programs.\n",
-                           blurb());
-                 return;
-               }
-             else
-               goto AGAIN;
+             /* Uh, oops.  Odds are, there are no suitable visuals,
+                and we're looping.  Give up.  (This is totally lame,
+                what we should do is make a list of suitable hacks at
+                the beginning, then only loop over them.)
+             */
+             if (p->verbose_p)
+               fprintf(stderr,
+                     "%s: %d: no programs enabled, or no suitable visuals.\n",
+                       blurb(), ssi->number);
+             return;
            }
            }
+         else
+           goto AGAIN;
        }
        }
-      si->next_mode_p = 0;
 
 
-
-      /* If there's a visual description on the front of the command, nuke it.
+      /* Turn off "next" and "prev" modes now, but "demo" mode is only
+        turned off by explicit action.
        */
        */
-      {
-       char *in = hack;
-       while (isspace(*in)) in++;                      /* skip whitespace */
-       hack = in;
-       while (!isspace(*in) && *in != ':') in++;       /* snarf first token */
-       while (isspace(*in)) in++;                      /* skip whitespace */
-       if (*in == ':')
-         {
-           in++;
-           while (isspace(*in)) in++;
-           hack = in;
-         }
-      }
+      if (si->selection_mode < 0)
+       si->selection_mode = 0;
 
 
-      switch ((int) (forked = fork ()))
+      forked = fork_and_exec (ssi, hack->command);
+      switch ((int) forked)
        {
        {
-       case -1:
+       case -1: /* fork failed */
+       case 0:  /* child fork (can't happen) */
          sprintf (buf, "%s: couldn't fork", blurb());
          perror (buf);
          restore_real_vroot (si);
          sprintf (buf, "%s: couldn't fork", blurb());
          perror (buf);
          restore_real_vroot (si);
-         saver_exit (si, 1);
-
-       case 0:
-         close (ConnectionNumber (si->dpy));   /* close display fd */
-         nice_subproc (p->nice_inferior);      /* change process priority */
-         hack_subproc_environment (ssi);       /* set $DISPLAY */
-         exec_screenhack (si, hack);           /* this does not return */
-         abort();
+         saver_exit (si, 1, "couldn't fork");
          break;
 
        default:
          ssi->pid = forked;
          break;
 
        default:
          ssi->pid = forked;
-         (void) make_job (forked, hack);
          break;
        }
     }
          break;
        }
     }
@@ -848,21 +1023,21 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
 void
 spawn_screenhack (saver_info *si, Bool first_time_p)
 {
 void
 spawn_screenhack (saver_info *si, Bool first_time_p)
 {
-  int i;
-
-  if (!monitor_powered_on_p (si))
+  if (monitor_powered_on_p (si))
     {
     {
-      if (si->prefs.verbose_p)
-       printf ("%s: server reports that monitor has powered down; "
-               "not launching a new hack.\n", blurb());
-      return;
+      int i;
+      for (i = 0; i < si->nscreens; i++)
+        {
+          saver_screen_info *ssi = &si->screens[i];
+          spawn_screenhack_1 (ssi, first_time_p);
+        }
     }
     }
+  else if (si->prefs.verbose_p)
+    fprintf (stderr,
+             "%s: X says monitor has powered down; "
+             "not launching a hack.\n", blurb());
 
 
-  for (i = 0; i < si->nscreens; i++)
-    {
-      saver_screen_info *ssi = &si->screens[i];
-      spawn_screenhack_1 (ssi, first_time_p);
-    }
+  store_saver_status (si);  /* store current hack numbers */
 }
 
 
 }
 
 
@@ -918,65 +1093,50 @@ emergency_kill_subproc (saver_info *si)
 Bool
 screenhack_running_p (saver_info *si)
 {
 Bool
 screenhack_running_p (saver_info *si)
 {
-  Bool result = True;
+  Bool any_running_p = False;
   int i;
   for (i = 0; i < si->nscreens; i++)
     {
       saver_screen_info *ssi = &si->screens[i];
   int i;
   for (i = 0; i < si->nscreens; i++)
     {
       saver_screen_info *ssi = &si->screens[i];
-      if (!ssi->pid)
-       result = False;
+      if (ssi->pid) any_running_p = True;
     }
     }
-  return result;
+  return any_running_p;
 }
 
 \f
 }
 
 \f
-/* Restarting the xscreensaver process from scratch. */
+/* Environment variables. */
 
 
-static char **saved_argv;
 
 
+/* Modifies $PATH in the current environment, so that if DEFAULT_PATH_PREFIX
+   is defined, the xscreensaver daemon will search that directory for hacks.
+ */
 void
 void
-save_argv (int argc, char **argv)
+hack_environment (saver_info *si)
 {
 {
-  saved_argv = (char **) malloc ((argc + 2) * sizeof (char *));
-  saved_argv [argc] = 0;
-  while (argc--)
+#if defined(HAVE_PUTENV) && defined(DEFAULT_PATH_PREFIX)
+  static const char *def_path = DEFAULT_PATH_PREFIX;
+  if (def_path && *def_path)
     {
     {
-      int i = strlen (argv [argc]) + 1;
-      saved_argv [argc] = (char *) malloc (i);
-      memcpy (saved_argv [argc], argv [argc], i);
+      const char *opath = getenv("PATH");
+      char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
+      strcpy (npath, "PATH=");
+      strcat (npath, def_path);
+      strcat (npath, ":");
+      strcat (npath, opath);
+
+      if (putenv (npath))
+       abort ();
+
+      /* don't free (npath) -- some implementations of putenv (BSD 4.4,
+         glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2)
+         do not.  So we must leak it (and/or the previous setting). Yay.
+       */
     }
     }
+#endif /* HAVE_PUTENV && DEFAULT_PATH_PREFIX */
 }
 
 }
 
-void
-restart_process (saver_info *si)
-{
-  fflush (real_stdout);
-  fflush (real_stderr);
-  execvp (saved_argv [0], saved_argv); /* shouldn't return */
-  {
-    char buf [512];
-    sprintf (buf, "%s: could not restart process", blurb());
-    perror(buf);
-    fflush(stderr);
-  }
-}
 
 
-/* Like restart_process(), but ensures that when it restarts,
-   it comes up in demo-mode. */
 void
 void
-demo_mode_restart_process (saver_info *si)
-{
-  int i;
-  for (i = 0; saved_argv [i]; i++);
-  /* add the -initial-demo-mode switch; save_argv() left room for this. */
-  saved_argv [i] = "-initial-demo-mode";
-  saved_argv [i+1] = 0;
-  restart_process (si);                /* shouldn't return */
-  saved_argv [i] = 0;
-  XBell(si->dpy, 0);
-}
-
-static void
 hack_subproc_environment (saver_screen_info *ssi)
 {
   /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
 hack_subproc_environment (saver_screen_info *ssi)
 {
   /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
@@ -987,183 +1147,211 @@ hack_subproc_environment (saver_screen_info *ssi)
      be the screen on which this particular hack is running -- not the display
      specification which the driver itself is using, since the driver ignores
      its screen number and manages all existing screens.
      be the screen on which this particular hack is running -- not the display
      specification which the driver itself is using, since the driver ignores
      its screen number and manages all existing screens.
+
+     Likewise, store a window ID in $XSCREENSAVER_WINDOW -- this will allow
+     us to (eventually) run multiple hacks in Xinerama mode, where each hack
+     has the same $DISPLAY but a different piece of glass.
    */
   saver_info *si = ssi->global;
   const char *odpy = DisplayString (si->dpy);
    */
   saver_info *si = ssi->global;
   const char *odpy = DisplayString (si->dpy);
-  char *ndpy = (char *) malloc(strlen(odpy) + 20);
-  int screen_number;
-  char *s;
-
-  for (screen_number = 0; screen_number < si->nscreens; screen_number++)
-    if (ssi == &si->screens[screen_number])
-      break;
-  if (screen_number >= si->nscreens) abort();
+  char *ndpy = (char *) malloc (strlen(odpy) + 20);
+  char *nssw = (char *) malloc (40);
+  char *s, *c;
 
   strcpy (ndpy, "DISPLAY=");
   s = ndpy + strlen(ndpy);
   strcpy (s, odpy);
 
 
   strcpy (ndpy, "DISPLAY=");
   s = ndpy + strlen(ndpy);
   strcpy (s, odpy);
 
-  while (*s && *s != ':') s++;                 /* skip to colon */
-  while (*s == ':') s++;                       /* skip over colons */
+  /* We have to find the last colon since it is the boundary between
+     hostname & screen - IPv6 numeric format addresses may have many
+     colons before that point, and DECnet addresses always have two colons */
+  c = strrchr(s,':');                          /* skip to last colon */
+  if (c != NULL) s = c+1;
   while (isdigit(*s)) s++;                     /* skip over dpy number */
   while (*s == '.') s++;                       /* skip over dot */
   if (s[-1] != '.') *s++ = '.';                        /* put on a dot */
   while (isdigit(*s)) s++;                     /* skip over dpy number */
   while (*s == '.') s++;                       /* skip over dot */
   if (s[-1] != '.') *s++ = '.';                        /* put on a dot */
-  sprintf(s, "%d", screen_number);             /* put on screen number */
+  sprintf(s, "%d", ssi->real_screen_number);   /* put on screen number */
+
+  sprintf (nssw, "XSCREENSAVER_WINDOW=0x%lX",
+           (unsigned long) ssi->screensaver_window);
 
   /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
      any more, right?  It's not Posix, but everyone seems to have it. */
 #ifdef HAVE_PUTENV
   if (putenv (ndpy))
     abort ();
 
   /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
      any more, right?  It's not Posix, but everyone seems to have it. */
 #ifdef HAVE_PUTENV
   if (putenv (ndpy))
     abort ();
+  if (putenv (nssw))
+    abort ();
+
+  /* don't free ndpy/nssw -- some implementations of putenv (BSD 4.4,
+     glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2)
+     do not.  So we must leak it (and/or the previous setting). Yay.
+   */
 #endif /* HAVE_PUTENV */
 }
 
 #endif /* HAVE_PUTENV */
 }
 
+\f
+/* GL crap */
 
 
-void
-hack_environment (saver_info *si)
+Visual *
+get_best_gl_visual (saver_screen_info *ssi)
 {
 {
-#if defined(HAVE_PUTENV) && defined(DEFAULT_PATH_PREFIX)
-  static const char *def_path = DEFAULT_PATH_PREFIX;
-  if (def_path && *def_path)
+  saver_info *si = ssi->global;
+  pid_t forked;
+  int fds [2];
+  int in, out;
+  char buf[1024];
+
+  char *av[10];
+  int ac = 0;
+
+  av[ac++] = "xscreensaver-gl-helper";
+  av[ac] = 0;
+
+  if (pipe (fds))
     {
     {
-      const char *opath = getenv("PATH");
-      char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
-      strcpy (npath, "PATH=");
-      strcat (npath, def_path);
-      strcat (npath, ":");
-      strcat (npath, opath);
+      perror ("error creating pipe:");
+      return 0;
+    }
 
 
-      if (putenv (npath))
-       abort ();
+  in = fds [0];
+  out = fds [1];
+
+  switch ((int) (forked = fork ()))
+    {
+    case -1:
+      {
+        sprintf (buf, "%s: couldn't fork", blurb());
+        perror (buf);
+        saver_exit (si, 1, 0);
+      }
+    case 0:
+      {
+        int stdout_fd = 1;
+
+        close (in);  /* don't need this one */
+        close (ConnectionNumber (si->dpy));    /* close display fd */
+
+        if (dup2 (out, stdout_fd) < 0)         /* pipe stdout */
+          {
+            perror ("could not dup() a new stdout:");
+            return 0;
+          }
+        hack_subproc_environment (ssi);                /* set $DISPLAY */
+
+        execvp (av[0], av);                    /* shouldn't return. */
+
+        if (errno != ENOENT /* || si->prefs.verbose_p */ )
+          {
+            /* Ignore "no such file or directory" errors.
+               Issue all other exec errors, though. */
+            sprintf (buf, "%s: running %s", blurb(), av[0]);
+            perror (buf);
+          }
+        exit (1);                               /* exits fork */
+        break;
+      }
+    default:
+      {
+        int result = 0;
+        int wait_status = 0;
+
+        FILE *f = fdopen (in, "r");
+        unsigned long v = 0;
+        char c;
+
+        close (out);  /* don't need this one */
+
+        *buf = 0;
+        if (! fgets (buf, sizeof(buf)-1, f))
+          *buf = 0;
+        fclose (f);
+
+        /* Wait for the child to die. */
+        waitpid (-1, &wait_status, 0);
+
+        if (1 == sscanf (buf, "0x%lx %c", &v, &c))
+          result = (int) v;
+
+        if (result == 0)
+          {
+            if (si->prefs.verbose_p)
+              {
+                int L = strlen(buf);
+                fprintf (stderr, "%s: %s did not report a GL visual!\n",
+                         blurb(), av[0]);
+
+                if (L && buf[L-1] == '\n')
+                  buf[--L] = 0;
+                if (*buf)
+                  fprintf (stderr, "%s: %s said: \"%s\"\n",
+                           blurb(), av[0], buf);
+              }
+            return 0;
+          }
+        else
+          {
+            Visual *v = id_to_visual (ssi->screen, result);
+            if (si->prefs.verbose_p)
+              fprintf (stderr, "%s: %d: %s: GL visual is 0x%X%s.\n",
+                       blurb(), ssi->number,
+                       av[0], result,
+                       (v == ssi->default_visual ? " (default)" : ""));
+            return v;
+          }
+      }
     }
     }
-#endif /* HAVE_PUTENV && DEFAULT_PATH_PREFIX */
+
+  abort();
 }
 
 
 \f
 }
 
 
 \f
-/* Change the uid/gid of the screensaver process, so that it is safe for it
-   to run setuid root (which it needs to do on some systems to read the 
-   encrypted passwords from the passwd file.)
-
-   hack_uid() is run before opening the X connection, so that XAuth works.
-   hack_uid_warn() is called after the connection is opened and the command
-   line arguments are parsed, so that the messages from hack_uid() get 
-   printed after we know whether we're in `verbose' mode.
- */
-
-#ifndef NO_SETUID
+/* Restarting the xscreensaver process from scratch. */
 
 
-static int hack_uid_errno;
-static char hack_uid_buf [255], *hack_uid_error;
+static char **saved_argv;
 
 void
 
 void
-hack_uid (saver_info *si)
+save_argv (int argc, char **argv)
 {
 {
-
-  /* If we've been run as setuid or setgid to someone else (most likely root)
-     turn off the extra permissions so that random user-specified programs
-     don't get special privileges.  (On some systems it might be necessary
-     to install this as setuid root in order to read the passwd file to
-     implement lock-mode...)
-  */
-  setgid (getgid ());
-  setuid (getuid ());
-
-  hack_uid_errno = 0;
-  hack_uid_error = 0;
-
-  /* If we're being run as root (as from xdm) then switch the user id
-     to something safe. */
-  if (getuid () == 0)
+  saved_argv = (char **) calloc (argc+2, sizeof (char *));
+  saved_argv [argc] = 0;
+  while (argc--)
     {
     {
-      struct passwd *p;
-      /* Locking can't work when running as root, because we have no way of
-        knowing what the user id of the logged in user is (so we don't know
-        whose password to prompt for.)
-       */
-      si->locking_disabled_p = True;
-      si->nolock_reason = "running as root";
-      p = getpwnam ("nobody");
-      if (! p) p = getpwnam ("noaccess");
-      if (! p) p = getpwnam ("daemon");
-      if (! p) p = getpwnam ("bin");
-      if (! p) p = getpwnam ("sys");
-      if (! p)
-       {
-         hack_uid_error = "couldn't find safe uid; running as root.";
-         hack_uid_errno = -1;
-       }
-      else
-       {
-         struct group *g = getgrgid (p->pw_gid);
-         hack_uid_error = hack_uid_buf;
-         sprintf (hack_uid_error, "changing uid/gid to %s/%s (%ld/%ld).",
-                  p->pw_name, (g ? g->gr_name : "???"),
-                  (long) p->pw_uid, (long) p->pw_gid);
-
-         /* Change the gid to be a safe one.  If we can't do that, then
-            print a warning.  We change the gid before the uid so that we
-            change the gid while still root. */
-         if (setgid (p->pw_gid) != 0)
-           {
-             hack_uid_errno = errno;
-             sprintf (hack_uid_error, "couldn't set gid to %s (%ld)",
-                      (g ? g->gr_name : "???"), (long) p->pw_gid);
-           }
-
-         /* Now change the uid to be a safe one. */
-         if (setuid (p->pw_uid) != 0)
-           {
-             hack_uid_errno = errno;
-             sprintf (hack_uid_error, "couldn't set uid to %s (%ld)",
-                      p->pw_name, (long) p->pw_uid);
-           }
-       }
+      int i = strlen (argv [argc]) + 1;
+      saved_argv [argc] = (char *) malloc (i);
+      memcpy (saved_argv [argc], argv [argc], i);
     }
     }
-# ifndef NO_LOCKING
- else  /* disable locking if already being run as "someone else" */
-   {
-     struct passwd *p = getpwuid (getuid ());
-     if (!p ||
-        !strcmp (p->pw_name, "root") ||
-        !strcmp (p->pw_name, "nobody") ||
-        !strcmp (p->pw_name, "noaccess") ||
-        !strcmp (p->pw_name, "daemon") ||
-        !strcmp (p->pw_name, "bin") ||
-        !strcmp (p->pw_name, "sys"))
-       {
-        si->locking_disabled_p = True;
-        si->nolock_reason = hack_uid_buf;
-        sprintf (si->nolock_reason, "running as %s", p->pw_name);
-       }
-   }
-# endif /* !NO_LOCKING */
 }
 
 }
 
+
+/* Re-execs the process with the arguments in saved_argv.  Does not return.
+ */
 void
 void
-hack_uid_warn (saver_info *si)
+restart_process (saver_info *si)
 {
 {
-  saver_preferences *p = &si->prefs;
-
-  if (! hack_uid_error)
-    ;
-  else if (hack_uid_errno == 0)
-    {
-      if (p->verbose_p)
-       printf ("%s: %s\n", blurb(), hack_uid_error);
-    }
-  else
+  fflush (stdout);
+  fflush (stderr);
+  shutdown_stderr (si);
+  if (si->prefs.verbose_p)
     {
     {
-      char buf [255];
-      sprintf (buf, "%s: %s", blurb(), hack_uid_error);
-      if (hack_uid_errno == -1)
-       fprintf (stderr, "%s\n", buf);
-      else
-       {
-         errno = hack_uid_errno;
-         perror (buf);
-       }
+      int i;
+      fprintf (stderr, "%s: re-executing", blurb());
+      for (i = 0; saved_argv[i]; i++)
+       fprintf (stderr, " %s", saved_argv[i]);
+      fprintf (stderr, "\n");
     }
     }
-}
+  describe_uids (si, stderr);
+  fprintf (stderr, "\n");
 
 
-#endif /* !NO_SETUID */
+  fflush (stdout);
+  fflush (stderr);
+  execvp (saved_argv [0], saved_argv); /* shouldn't return */
+  {
+    char buf [512];
+    sprintf (buf, "%s: could not restart process", blurb());
+    perror(buf);
+    fflush(stderr);
+    abort();
+  }
+}