ftp://ftp.krokus.ru/pub/OpenBSD/distfiles/xscreensaver-5.01.tar.gz
[xscreensaver] / driver / subprocs.c
index e6a281270b58bfd2a42cf7fcf3c198d028e9ce22..0bbe4a87609c836239b693642957276d33f64a89 100644 (file)
@@ -1,4 +1,5 @@
-/* xscreensaver, Copyright (c) 1991-1993 Jamie Zawinski <jwz@lucid.com>
+/* subprocs.c --- choosing, spawning, and killing screenhacks.
+ * xscreensaver, Copyright (c) 1991-2006 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
  * implied warranty.
  */
 
-#if __STDC__
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
+#ifdef HAVE_CONFIG_H
+# include "config.h"
 #endif
 
+#include <ctype.h>
 #include <stdio.h>
+#include <string.h>
 
 #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 */
-#include <sys/resource.h>      /* for setpriority() and PRIO_PROCESS */
-#include <sys/wait.h>          /* for waitpid() and associated macros */
-#include <signal.h>            /* for the signal names */
+#include <sys/param.h>         /* for PATH_MAX */
 
-extern char **environ;         /* why isn't this in some header file? */
+#ifdef HAVE_SYS_WAIT_H
+# include <sys/wait.h>         /* for waitpid() and associated macros */
+#endif
 
-#ifndef NO_SETUID
-#include <pwd.h>               /* for getpwnam() and struct passwd */
-#include <grp.h>               /* for getgrgid() and struct group */
-#endif /* NO_SETUID */
+#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 */
+# define pid_t int
+# define fork  vfork
+#endif /* VMS */
+
+#include <signal.h>            /* for the signal names */
 
 #if !defined(SIGCHLD) && defined(SIGCLD)
-#define SIGCHLD SIGCLD
+# define SIGCHLD SIGCLD
 #endif
 
-#if __STDC__
-extern int putenv (const char *);      /* getenv() is in stdlib.h... */
-extern int kill (pid_t, int);          /* signal() is in sys/signal.h... */
+#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
 #endif
 
-# if defined(SVR4) || defined(SYSV)
-#  define random() rand()
-# else /* !totally-losing-SYSV */
-extern long random();                  /* rand() is in stdlib.h... */
-# endif /* !totally-losing-SYSV */
+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... */
+#undef XtPointer
+#define XtAppContext void*
+#define XrmDatabase  void*
+#define XtIntervalId void*
+#define XtPointer    void*
+#define Widget       void*
 
 #include "xscreensaver.h"
+#include "exec.h"
+#include "yarandom.h"
+#include "visual.h"    /* for id_to_visual() */
 
-/* this must be `sh', not whatever $SHELL happens to be. */
-char *shell;
-static pid_t pid = 0;
-char **screenhacks;
-int screenhacks_count;
-int current_hack = -1;
-char *demo_hack;
-int next_mode_p = 0;
-Bool locking_disabled_p = False;
-int nice_inferior = 0;
+extern saver_info *global_si_kludge;   /* I hate C so much... */
 
-extern Bool demo_mode_p;
 
-static void
-exec_screenhack (command)
-     char *command;
+/* Used when printing error/debugging messages from signal handlers.
+ */
+static const char *
+no_malloc_number_to_string (long num)
 {
-  char *tmp;
-  char buf [512];
-  char *av [5];
-  int ac = 0;
+  static char string[128] = "";
+  int num_digits;
+  Bool negative_p = False;
 
-  /* Close this fork's version of the display's fd.  It will open its own. */
-  close (ConnectionNumber (dpy));
-  
-  /* I don't believe what a sorry excuse for an operating system UNIX is!
+  num_digits = 0;
 
-     - 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 (num == 0)
+    return "0";
 
-     The *only way* to parse arguments the way the shells do is to run a
-     shell (or duplicate what they do, which would be a *lot* of code.)
+  if (num < 0)
+    {
+      negative_p = True;
+      num = -num;
+    }
 
-     The *only way* to know the pid of the process is to fork() and exec()
-     it in the spawned side of the fork.
+  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;
+    }
 
-     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.
+  if (negative_p)
+    {
+      num_digits++;
+      string[sizeof(string) - 1 - num_digits] = '-';
+    }
 
-     So, the only solution other than implementing an argument parser here
-     is to force the shell to exec() its inferior.  What a fucking hack!
-     We prepend "exec " to the command string.
-   */
-  tmp = command;
-  command = (char *) malloc (strlen (tmp) + 6);
-  memcpy (command, "exec ", 5);
-  memcpy (command + 5, tmp, strlen (tmp) + 1);
-
-  /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
-  av [ac++] = shell;
-  av [ac++] = "-c";
-  av [ac++] = command;
-  av [ac++] = 0;
-  
-  if (verbose_p)
-    printf ("%s: spawning \"%s\" in pid %d.\n", progname, command, getpid ());
+  return string + sizeof(string) - 1 - num_digits;
+}
 
-#if defined(SYSV) || defined(__hpux)
-  {
-    int old_nice = nice (0);
-    int n = nice_inferior - old_nice;
-    errno = 0;
-    if (nice (n) == -1 && errno != 0)
-      {
-       sprintf (buf, "%s: %snice(%d) failed", progname,
-                (verbose_p ? "## " : ""), n);
-       perror (buf);
-      }
-  }
-#else /* !SYSV */
-#ifdef PRIO_PROCESS
-  if (setpriority (PRIO_PROCESS, getpid(), nice_inferior) != 0)
+/* 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 int
+write_long (int fd, long n)
+{
+  const char *str = no_malloc_number_to_string (n);
+  return write_string (fd, str);
+}
+
+
+/* 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.
+
+   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
+limit_subproc_memory (int address_space_limit, Bool verbose_p)
+{
+
+/* 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;
+
+  if (address_space_limit < 10 * 1024)  /* let's not be crazy */
+    return;
+
+  if (getrlimit (RLIMIT_AS, &r) != 0)
     {
-      sprintf (buf, "%s: %ssetpriority(PRIO_PROCESS, %d, %d) failed",
-              progname, (verbose_p ? "## " : ""), getpid(), nice_inferior);
+      char buf [512];
+      sprintf (buf, "%s: getrlimit(RLIMIT_AS) failed", blurb());
       perror (buf);
+      return;
     }
-#else /* !PRIO_PROCESS */
-  if (nice_inferior != 0)
-    fprintf (stderr,
-          "%s: %sdon't know how to change process priority on this system.\n",
-            progname, (verbose_p ? "## " : ""));
-#endif /* !PRIO_PROCESS */
-#endif /* !SYSV */
 
-  /* Now overlay the current process with /bin/sh running the command.
-     If this returns, it's an error.
-   */
-  execve (av [0], av, environ);
+  r.rlim_cur = address_space_limit;
 
-  sprintf (buf, "%s: %sexecve() failed", progname, (verbose_p ? "## " : ""));
-  perror (buf);
-  exit (1);    /* Note this this only exits a child fork.  */
+  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;
+    }
+
+  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.
+ */
+
+enum job_status {
+  job_running, /* the process is still alive */
+  job_stopped, /* we have sent it a STOP signal */
+  job_killed,  /* we have sent it a TERM signal */
+  job_dead     /* we have wait()ed for it, and it's dead -- this state only
+                  occurs so that we can avoid calling free() from a signal
+                  handler.  Shortly after going into this state, the list
+                  element will be removed. */
+};
+
+struct screenhack_job {
+  char *name;
+  pid_t pid;
+  int screen;
+  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.
+ */
+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)
+    fprintf (stderr, "  %5ld: %2d: (%s) %s\n",
+            (long) job->pid,
+             job->screen,
+            (job->status == job_running ? "running" :
+             job->status == job_stopped ? "stopped" :
+             job->status == job_killed  ? " killed" :
+             job->status == job_dead    ? "   dead" : "    ???"),
+            job->name);
+  fprintf (stderr, "\n");
 }
 
-/* to avoid a race between the main thread and the SIGCHLD handler */
-static int killing = 0;
-static Bool suspending = False;
 
-static char *current_hack_name P((void));
+static void clean_job_list (void);
+
+static struct screenhack_job *
+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;
+  int got_eq = 0;
+  int first = 1;
+
+  clean_job_list();
+
+ AGAIN:
+  while (isspace(*in)) in++;           /* skip whitespace */
+  while (!isspace(*in) && *in != ':') {
+    if (*in == '=') got_eq = 1;
+    *out++ = *in++;                    /* snarf first token */
+  }
+
+  if (got_eq)                          /* if the first token was FOO=bar */
+    {                                  /* then get the next token instead. */
+      got_eq = 0;
+      out = name;
+      first = 0;
+      goto AGAIN;
+    }
+
+  while (isspace(*in)) in++;           /* skip whitespace */
+  *out = 0;
+
+  job->name = strdup(name);
+  job->pid = pid;
+  job->screen = screen;
+  job->status = job_running;
+  job->next = jobs;
+  jobs = job;
+
+  return jobs;
+}
+
 
 static void
-await_child_death (killed)
-     Bool killed;
+free_job (struct screenhack_job *job)
 {
-  Bool suspended_p = False;
-  int status;
-  pid_t kid;
-  killing = 1;
-  if (! pid)
+  if (!job)
     return;
-
-  do
+  else if (job == jobs)
+    jobs = jobs->next;
+  else
     {
-      kid = waitpid (pid, &status, WUNTRACED);
+      struct screenhack_job *job2, *prev;
+      for (prev = 0, job2 = jobs;
+          job2;
+          prev = job2, job2 = job2->next)
+       if (job2 == job)
+         {
+           prev->next = job->next;
+           break;
+         }
     }
-  while (kid == -1 && errno == EINTR);
+  free(job->name);
+  free(job);
+}
 
-  if (kid == pid)
+
+/* Cleans out dead jobs from the jobs list -- this must only be called
+   from the main thread, not from a signal handler. 
+ */
+static void
+clean_job_list (void)
+{
+  struct screenhack_job *job, *prev, *next;
+  for (prev = 0, job = jobs, next = (job ? job->next : 0);
+       job;
+       prev = job, job = next, next = (job ? job->next : 0))
     {
-      if (WIFEXITED (status))
-       {
-         int exit_status = WEXITSTATUS (status);
-         if (exit_status & 0x80)
-           exit_status |= ~0xFF;
-         if (exit_status != 0 && verbose_p)
-           printf ("%s: child pid %d (%s) exited abnormally (code %d).\n",
-                   progname, pid, current_hack_name (), exit_status);
-         else if (verbose_p)
-           printf ("%s: child pid %d (%s) exited normally.\n",
-                   progname, pid, current_hack_name ());
-       }
-      else if (WIFSIGNALED (status))
+      if (job->status == job_dead)
        {
-         if (!killed || WTERMSIG (status) != SIGTERM)
-           fprintf (stderr,
-                    "%s: %schild pid %d (%s) terminated with signal %d!\n",
-                    progname, (verbose_p ? "## " : ""),
-                    pid, current_hack_name (), WTERMSIG (status));
-         else if (verbose_p)
-           printf ("%s: child pid %d (%s) terminated with SIGTERM.\n",
-                   progname, pid, current_hack_name ());
+         if (prev)
+           prev->next = next;
+         free_job (job);
+         job = prev;
        }
-      else if (suspending)
-       {
-         suspended_p = True;
-         suspending = False; /* complain if it happens twice */
-       }
-      else if (WIFSTOPPED (status))
+    }
+}
+
+
+static struct screenhack_job *
+find_job (pid_t pid)
+{
+  struct screenhack_job *job;
+  for (job = jobs; job; job = job->next)
+    if (job->pid == pid)
+      return job;
+  return 0;
+}
+
+static void await_dying_children (saver_info *si);
+#ifndef VMS
+static void describe_dead_child (saver_info *, pid_t, int wait_status);
+#endif
+
+
+/* Semaphore to temporarily turn the SIGCHLD handler into a no-op.
+   Don't alter this directly -- use block_sigchld() / unblock_sigchld().
+ */
+static int block_sigchld_handler = 0;
+
+
+#ifdef HAVE_SIGACTION
+ sigset_t
+#else  /* !HAVE_SIGACTION */
+ int
+#endif /* !HAVE_SIGACTION */
+block_sigchld (void)
+{
+#ifdef HAVE_SIGACTION
+  sigset_t child_set;
+  sigemptyset (&child_set);
+  sigaddset (&child_set, SIGCHLD);
+  sigaddset (&child_set, SIGPIPE);
+  sigprocmask (SIG_BLOCK, &child_set, 0);
+#endif /* HAVE_SIGACTION */
+
+  block_sigchld_handler++;
+
+#ifdef HAVE_SIGACTION
+  return child_set;
+#else  /* !HAVE_SIGACTION */
+  return 0;
+#endif /* !HAVE_SIGACTION */
+}
+
+void
+unblock_sigchld (void)
+{
+#ifdef HAVE_SIGACTION
+  sigset_t child_set;
+  sigemptyset(&child_set);
+  sigaddset(&child_set, SIGCHLD);
+  sigaddset(&child_set, SIGPIPE);
+  sigprocmask(SIG_UNBLOCK, &child_set, 0);
+#endif /* HAVE_SIGACTION */
+
+  block_sigchld_handler--;
+}
+
+static int
+kill_job (saver_info *si, pid_t pid, int signal)
+{
+  saver_preferences *p = &si->prefs;
+  struct screenhack_job *job;
+  int status = -1;
+
+  clean_job_list();
+
+  if (block_sigchld_handler)
+    /* This function should not be called from the signal handler. */
+    abort();
+
+  block_sigchld();                     /* we control the horizontal... */
+
+  job = find_job (pid);
+  if (!job ||
+      !job->pid ||
+      job->status == job_killed)
+    {
+      if (p->verbose_p)
+       fprintf (stderr, "%s: no child %ld to signal!\n",
+                blurb(), (long) pid);
+      goto DONE;
+    }
+
+  switch (signal) {
+  case SIGTERM: job->status = job_killed;  break;
+#ifdef SIGSTOP
+    /* #### there must be a way to do this on VMS... */
+  case SIGSTOP: job->status = job_stopped; break;
+  case SIGCONT: job->status = job_running; break;
+#endif /* SIGSTOP */
+  default: abort();
+  }
+
+  if (p->verbose_p)
+    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)
+       fprintf (stderr,
+                 "%s: %d: child process %lu (%s) was already dead.\n",
+                blurb(), job->screen, (unsigned long) job->pid, job->name);
+      else
        {
-         suspended_p = True;
-         fprintf (stderr, "%s: %schild pid %d (%s) stopped with signal %d!\n",
-                  progname, (verbose_p ? "## " : ""), pid,
-                  current_hack_name (), WSTOPSIG (status));
+         char buf [1024];
+         sprintf (buf, "%s: %d: couldn't kill child process %lu (%s)",
+                  blurb(), job->screen, (unsigned long) job->pid, job->name);
+         perror (buf);
        }
+    }
+
+  await_dying_children (si);
+
+ DONE:
+  unblock_sigchld();
+  if (block_sigchld_handler < 0)
+    abort();
+
+  clean_job_list();
+  return status;
+}
+
+
+#ifdef SIGCHLD
+static RETSIGTYPE
+sigchld_handler (int sig)
+{
+  saver_info *si = global_si_kludge;   /* I hate C so much... */
+
+  if (si->prefs.debug_p)
+    {
+      /* Don't call fprintf() from signal handlers, as it might malloc.
+      fprintf(stderr, "%s: got SIGCHLD%s\n", blurb(),
+           (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
-       fprintf (stderr, "%s: %schild pid %d (%s) died in a mysterious way!",
-                progname, (verbose_p ? "## " : ""), pid, current_hack_name());
+        write_string (STDERR_FILENO, "\n");
     }
-  else if (kid <= 0)
-    fprintf (stderr, "%s: %swaitpid(%d, ...) says there are no kids?  (%d)\n",
-            progname, (verbose_p ? "## " : ""), pid, kid);
-  else
-    fprintf (stderr, "%s: %swaitpid(%d, ...) says proc %d died, not %d?\n",
-            progname, (verbose_p ? "## " : ""), pid, kid, pid);
-  killing = 0;
-  if (suspended_p != True)
-    pid = 0;
+
+  if (block_sigchld_handler < 0)
+    abort();
+  else if (block_sigchld_handler == 0)
+    {
+      block_sigchld();
+      await_dying_children (si);
+      unblock_sigchld();
+    }
+
+  init_sigchld();
 }
+#endif /* SIGCHLD */
 
-static char *
-current_hack_name ()
+
+#ifndef VMS
+
+static void
+await_dying_children (saver_info *si)
 {
-  static char chn [1024];
-  char *hack = (demo_mode_p ? demo_hack : screenhacks [current_hack]);
-  int i;
-  for (i = 0; hack [i] != 0 && hack [i] != ' ' && hack [i] != '\t'; i++)
-    chn [i] = hack [i];
-  chn [i] = 0;
-  return chn;
+  while (1)
+    {
+      int wait_status = 0;
+      pid_t kid;
+
+      errno = 0;
+      kid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED);
+
+      if (si->prefs.debug_p)
+       {
+         if (kid < 0 && errno)
+            {
+              /* 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.
+        -1 means error -- except "interrupted system call" isn't a "real"
+        error, so if we get that, we should just try again. */
+      if (kid == 0 ||
+         (kid < 0 && errno != EINTR))
+       break;
+
+      describe_dead_child (si, kid, wait_status);
+    }
 }
 
-#ifdef SIGCHLD
+
 static void
-sigchld_handler (sig)
-     int sig;
+describe_dead_child (saver_info *si, pid_t kid, int wait_status)
 {
-  if (killing)
-    return;
-  if (! pid)
-    abort ();
-  await_child_death (False);
+  int i;
+  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))
+    {
+      int exit_status = WEXITSTATUS (wait_status);
+
+      /* Treat exit code as a signed 8-bit quantity. */
+      if (exit_status & 0x80) exit_status |= ~0xFF;
+
+      /* One might assume that exiting with non-0 means something went wrong.
+        But that loser xswarm exits with the code that it was killed with, so
+        it *always* exits abnormally.  Treat abnormal exits as "normal" (don't
+        mention them) if we've just killed the subprocess.  But mention them
+        if they happen on their own.
+       */
+      if (!job ||
+         (exit_status != 0 &&
+          (p->verbose_p || job->status != job_killed)))
+        {
+          /* 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)
+        {
+          /* 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;
+    }
+  else if (WIFSIGNALED (wait_status))
+    {
+      if (p->verbose_p ||
+         !job ||
+         job->status != job_killed ||
+         WTERMSIG (wait_status) != SIGTERM)
+        {
+          /* 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;
+    }
+  else if (WIFSTOPPED (wait_status))
+    {
+      if (p->verbose_p)
+        {
+          /* 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
+    {
+      /* 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);
+       */
+      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;
+    }
+
+  /* Clear out the pid so that screenhack_running_p() knows it's dead.
+   */
+  if (!job || job->status == job_dead)
+    for (i = 0; i < si->nscreens; i++)
+      {
+       saver_screen_info *ssi = &si->screens[i];
+       if (kid == ssi->pid)
+         ssi->pid = 0;
+      }
 }
-#endif
+
+#else  /* VMS */
+static void await_dying_children (saver_info *si) { return; }
+#endif /* VMS */
 
 
 void
-init_sigchld ()
+init_sigchld (void)
 {
 #ifdef SIGCHLD
-  if (((int) signal (SIGCHLD, sigchld_handler)) == -1)
+
+# ifdef HAVE_SIGACTION /* Thanks to Tom Kelly <tom@ancilla.toronto.on.ca> */
+
+  static Bool sigchld_initialized_p = 0;
+  if (!sigchld_initialized_p)
+    {
+      struct sigaction action, old;
+
+      action.sa_handler = sigchld_handler;
+      sigemptyset(&action.sa_mask);
+      action.sa_flags = 0;
+
+      if (sigaction(SIGCHLD, &action, &old) < 0)
+       {
+         char buf [255];
+         sprintf (buf, "%s: couldn't catch SIGCHLD", blurb());
+         perror (buf);
+       }
+      sigchld_initialized_p = True;
+    }
+
+# else  /* !HAVE_SIGACTION */
+
+  if (((long) signal (SIGCHLD, sigchld_handler)) == -1L)
     {
       char buf [255];
-      sprintf (buf, "%s: %scouldn't catch SIGCHLD", progname,
-              (verbose_p ? "## " : ""));
+      sprintf (buf, "%s: couldn't catch SIGCHLD", blurb());
       perror (buf);
     }
-#endif
+# endif /* !HAVE_SIGACTION */
+#endif /* SIGCHLD */
 }
 
 
-extern void raise_window P((Bool inhibit_fade, Bool between_hacks_p));
 
-void
-spawn_screenhack (first_time_p)
-     Bool first_time_p;
+\f
+
+static Bool
+select_visual_of_hack (saver_screen_info *ssi, screenhack *hack)
 {
-  raise_window (first_time_p, True);
-  XFlush (dpy);
+  saver_info *si = ssi->global;
+  saver_preferences *p = &si->prefs;
+  Bool selected;
+
+  if (hack->visual && *hack->visual)
+    selected = select_visual(ssi, hack->visual);
+  else
+    selected = select_visual(ssi, 0);
+
+  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 (screenhacks_count || demo_mode_p)
+  if (errno == ENOENT &&
+      (token = getenv("PATH")))
     {
-      char *hack;
+# 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");
+    }
+}
+
+
+/* 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;
+}
+
+
+static void
+spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
+{
+  saver_info *si = ssi->global;
+  saver_preferences *p = &si->prefs;
+  raise_window (si, first_time_p, True, False);
+  XFlush (si->dpy);
+
+  if (p->screenhacks_count)
+    {
+      screenhack *hack;
       pid_t forked;
       char buf [255];
-      int new_hack;
-      if (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 = demo_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)
+       {
+          /* 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 (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 (screenhacks_count == 1)
-           new_hack = 0;
-         else if (next_mode_p == 1)
-           new_hack = (current_hack + 1) % screenhacks_count,
-           next_mode_p = 0;
-         else if (next_mode_p == 2)
+         if (++retry_count > (p->screenhacks_count*4))
            {
-             new_hack = ((current_hack + screenhacks_count - 1)
-                         % screenhacks_count);
-             next_mode_p = 0;
+             /* 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
-           while ((new_hack = random () % screenhacks_count) == current_hack)
-             ;
-         current_hack = new_hack;
-         hack = screenhacks[current_hack];
+           goto AGAIN;
        }
 
-      switch (forked = fork ())
+      /* Turn off "next" and "prev" modes now, but "demo" mode is only
+        turned off by explicit action.
+       */
+      if (si->selection_mode < 0)
+       si->selection_mode = 0;
+
+      forked = fork_and_exec (ssi, hack->command);
+      switch ((int) forked)
        {
-       case -1:
-         sprintf (buf, "%s: %scouldn't fork",
-                  progname, (verbose_p ? "## " : ""));
+       case -1: /* fork failed */
+       case 0:  /* child fork (can't happen) */
+         sprintf (buf, "%s: couldn't fork", blurb());
          perror (buf);
-         restore_real_vroot ();
-         exit (1);
-       case 0:
-         exec_screenhack (hack); /* this does not return */
+         restore_real_vroot (si);
+         saver_exit (si, 1, "couldn't fork");
          break;
+
        default:
-         pid = forked;
+         ssi->pid = forked;
          break;
        }
     }
 }
 
+
 void
-kill_screenhack ()
+spawn_screenhack (saver_info *si, Bool first_time_p)
 {
-  killing = 1;
-  if (! pid)
-    return;
-  if (kill (pid, SIGTERM) < 0)
+  if (monitor_powered_on_p (si))
     {
-      if (errno == ESRCH)
-       {
-         /* Sometimes we don't get a SIGCHLD at all!  WTF?
-            It's a race condition.  It looks to me like what's happening is
-            something like: a subprocess dies of natural causes.  There is a
-            small window between when the process dies and when the SIGCHLD
-            is (would have been) delivered.  If we happen to try to kill()
-            the process during that time, the kill() fails, because the
-            process is already dead.  But! no SIGCHLD is delivered (perhaps
-            because the failed kill() has reset some state in the kernel?)
-            Anyway, if kill() says "No such process", then we have to wait()
-            for it anyway, because the process has already become a zombie.
-            I love Unix.
-          */
-         await_child_death (False);
-       }
-      else
-       {
-         char buf [255];
-         sprintf (buf, "%s: %scouldn't kill child process %d", progname,
-                  (verbose_p ? "## " : ""), pid);
-         perror (buf);
-       }
+      int i;
+      for (i = 0; i < si->nscreens; i++)
+        {
+          saver_screen_info *ssi = &si->screens[i];
+          spawn_screenhack_1 (ssi, first_time_p);
+        }
     }
-  else
+  else if (si->prefs.verbose_p)
+    fprintf (stderr,
+             "%s: X says monitor has powered down; "
+             "not launching a hack.\n", blurb());
+
+  store_saver_status (si);  /* store current hack numbers */
+}
+
+
+void
+kill_screenhack (saver_info *si)
+{
+  int i;
+  for (i = 0; i < si->nscreens; i++)
     {
-      if (verbose_p)
-       printf ("%s: killing pid %d.\n", progname, pid);
-      await_child_death (True);
+      saver_screen_info *ssi = &si->screens[i];
+      if (ssi->pid)
+       kill_job (si, ssi->pid, SIGTERM);
+      ssi->pid = 0;
     }
 }
 
 
 void
-suspend_screenhack (suspend_p)
-     Bool suspend_p;
+suspend_screenhack (saver_info *si, Bool suspend_p)
 {
-  
-  suspending = suspend_p;
-  if (! pid)
-    ;
-  else if (kill (pid, (suspend_p ? SIGSTOP : SIGCONT)) < 0)
+#ifdef SIGSTOP /* older VMS doesn't have it... */
+  int i;
+  for (i = 0; i < si->nscreens; i++)
     {
-      char buf [255];
-      sprintf (buf, "%s: %scouldn't %s child process %d", progname,
-              (verbose_p ? "## " : ""),
-              (suspend_p ? "suspend" : "resume"),
-              pid);
-      perror (buf);
+      saver_screen_info *ssi = &si->screens[i];
+      if (ssi->pid)
+       kill_job (si, ssi->pid, (suspend_p ? SIGSTOP : SIGCONT));
     }
-  else if (verbose_p)
-    printf ("%s: %s pid %d.\n", progname,
-           (suspend_p ? "suspending" : "resuming"), pid);
+#endif /* SIGSTOP */
 }
 
-\f
-/* Restarting the xscreensaver process from scratch. */
-
-static char **saved_argv;
 
+/* Called when we're exiting abnormally, to kill off the subproc. */
 void
-save_argv (argc, argv)
-     int argc;
-     char **argv;
+emergency_kill_subproc (saver_info *si)
 {
-  saved_argv = (char **) malloc ((argc + 2) * sizeof (char *));
-  saved_argv [argc] = 0;
-  while (argc--)
+  int i;
+#ifdef SIGCHLD
+  signal (SIGCHLD, SIG_IGN);
+#endif /* SIGCHLD */
+
+  for (i = 0; i < si->nscreens; i++)
     {
-      int i = strlen (argv [argc]) + 1;
-      saved_argv [argc] = (char *) malloc (i);
-      memcpy (saved_argv [argc], argv [argc], i);
+      saver_screen_info *ssi = &si->screens[i];
+      if (ssi->pid)
+       {
+         kill_job (si, ssi->pid, SIGTERM);
+         ssi->pid = 0;
+       }
     }
 }
 
-void
-restart_process ()
+Bool
+screenhack_running_p (saver_info *si)
 {
-  XCloseDisplay (dpy);
-  fflush (stdout);
-  fflush (stderr);
-  execvp (saved_argv [0], saved_argv);
-  fprintf (stderr, "%s: %scould not restart process: %s (%d)\n",
-          progname, (verbose_p ? "## " : ""),
-          (errno == E2BIG ? "arglist too big" :
-           errno == EACCES ? "could not execute" :
-           errno == EFAULT ? "memory fault" :
-           errno == EIO ? "I/O error" :
-           errno == ENAMETOOLONG ? "name too long" :
-           errno == ELOOP ? "too many symbolic links" :
-           errno == ENOENT ? "file no longer exists" :
-           errno == ENOTDIR ? "directory no longer exists" :
-           errno == ENOEXEC ? "bad executable file" :
-           errno == ENOMEM ? "out of memory" :
-           "execvp() returned unknown error code"),
-          errno);
-  exit (1);
+  Bool any_running_p = False;
+  int i;
+  for (i = 0; i < si->nscreens; i++)
+    {
+      saver_screen_info *ssi = &si->screens[i];
+      if (ssi->pid) any_running_p = True;
+    }
+  return any_running_p;
 }
 
+\f
+/* Environment variables. */
+
+
+/* Modifies $PATH in the current environment, so that if DEFAULT_PATH_PREFIX
+   is defined, the xscreensaver daemon will search that directory for hacks.
+ */
 void
-demo_mode_restart_process ()
+hack_environment (saver_info *si)
 {
-  int i;
-  for (i = 0; saved_argv [i]; i++);
-  /* add the -demo switch; save_argv() left room for this. */
-  saved_argv [i] = "-demo";
-  saved_argv [i+1] = 0;
-  restart_process ();
+#if defined(HAVE_PUTENV) && defined(DEFAULT_PATH_PREFIX)
+  static const char *def_path = DEFAULT_PATH_PREFIX;
+  if (def_path && *def_path)
+    {
+      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
-hack_environment ()
+hack_subproc_environment (saver_screen_info *ssi)
 {
   /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
-     the spawned processes inherit is the same as the value of -display passed
-     in on our command line (which is not necessarily the same as what our
-     $DISPLAY variable is.)
+     the spawned processes inherit is correct.  First, it must be on the same
+     host and display as the value of -display passed in on our command line
+     (which is not necessarily the same as what our $DISPLAY variable is.)
+     Second, the screen number in the $DISPLAY passed to the subprocess should
+     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.
    */
-  char *s, buf [2048];
-  int i;
-  sprintf (buf, "DISPLAY=%s", DisplayString (dpy));
-  i = strlen (buf);
-  s = (char *) malloc (i+1);
-  strncpy (s, buf, i+1);
-  if (putenv (s))
+  saver_info *si = ssi->global;
+  const char *odpy = DisplayString (si->dpy);
+  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);
+
+  /* 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 */
+  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 ();
+  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 */
 }
 
 \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.
- */
+/* GL crap */
 
-#ifndef NO_SETUID
+Visual *
+get_best_gl_visual (saver_screen_info *ssi)
+{
+  saver_info *si = ssi->global;
+  pid_t forked;
+  int fds [2];
+  int in, out;
+  char buf[1024];
 
-static int hack_uid_errno;
-static char hack_uid_buf [255], *hack_uid_error;
+  char *av[10];
+  int ac = 0;
 
-void
-hack_uid ()
-{
-  /* 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)
-    {
-      struct passwd *p = getpwnam ("nobody");
-      locking_disabled_p = True;
-      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 (%d/%d).",
-                  p->pw_name, (g ? g->gr_name : "???"), p->pw_uid, 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 (%d)",
-                      (g ? g->gr_name : "???"), p->pw_gid);
-           }
+  av[ac++] = "xscreensaver-gl-helper";
+  av[ac] = 0;
 
-         /* 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 (%d)",
-                      p->pw_name, p->pw_uid);
-           }
-       }
+  if (pipe (fds))
+    {
+      perror ("error creating pipe:");
+      return 0;
+    }
+
+  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, unless verbose.
+               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;
+          }
+      }
     }
-#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, "daemon") ||
-        !strcmp (p->pw_name, "bin") ||
-        !strcmp (p->pw_name, "sys"))
-       locking_disabled_p = True;
-   }
-#endif /* NO_LOCKING */
+
+  abort();
 }
 
+
+\f
+/* Restarting the xscreensaver process from scratch. */
+
+static char **saved_argv;
+
 void
-hack_uid_warn ()
+save_argv (int argc, char **argv)
 {
-  if (! hack_uid_error)
-    ;
-  else if (hack_uid_errno == 0)
+  saved_argv = (char **) calloc (argc+2, sizeof (char *));
+  saved_argv [argc] = 0;
+  while (argc--)
     {
-      if (verbose_p)
-       printf ("%s: %s\n", progname, hack_uid_error);
+      int i = strlen (argv [argc]) + 1;
+      saved_argv [argc] = (char *) malloc (i);
+      memcpy (saved_argv [argc], argv [argc], i);
     }
-  else
+}
+
+
+/* Re-execs the process with the arguments in saved_argv.  Does not return.
+ */
+void
+restart_process (saver_info *si)
+{
+  fflush (stdout);
+  fflush (stderr);
+  shutdown_stderr (si);
+  if (si->prefs.verbose_p)
     {
-      char buf [255];
-      sprintf (buf, "%s: %s%s", progname, (verbose_p ? "## " : ""),
-              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();
+  }
+}