From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / driver / subprocs.c
index 463b3189450310fbbeb2f8e5ae552c17537de79e..4f327e95851bcfa72f8989b2f055ddd32e1de0e0 100644 (file)
@@ -1,5 +1,5 @@
 /* subprocs.c --- choosing, spawning, and killing screenhacks.
 /* subprocs.c --- choosing, spawning, and killing screenhacks.
- * xscreensaver, Copyright (c) 1991-2003 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright (c) 1991-2017 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
@@ -25,6 +25,7 @@
 #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 */
 
 #ifdef HAVE_SYS_WAIT_H
 # include <sys/wait.h>         /* for waitpid() and associated macros */
 
 #ifdef HAVE_SYS_WAIT_H
 # include <sys/wait.h>         /* for waitpid() and associated macros */
@@ -43,6 +44,7 @@
 #endif /* VMS */
 
 #include <signal.h>            /* for the signal names */
 #endif /* VMS */
 
 #include <signal.h>            /* for the signal names */
+#include <time.h>
 
 #if !defined(SIGCHLD) && defined(SIGCLD)
 # define SIGCHLD SIGCLD
 
 #if !defined(SIGCHLD) && defined(SIGCLD)
 # define SIGCHLD SIGCLD
@@ -65,12 +67,66 @@ 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 "visual.h"    /* for id_to_visual() */
 
 extern saver_info *global_si_kludge;   /* I hate C so much... */
 
 
 #include "yarandom.h"
 #include "visual.h"    /* for id_to_visual() */
 
 extern saver_info *global_si_kludge;   /* I hate C so much... */
 
 
+/* Used when printing error/debugging messages from signal handlers.
+ */
+static const char *
+no_malloc_number_to_string (long num)
+{
+  static char string[128] = "";
+  int num_digits;
+  Bool negative_p = False;
+
+  num_digits = 0;
+
+  if (num == 0)
+    return "0";
+
+  if (num < 0)
+    {
+      negative_p = True;
+      num = -num;
+    }
+
+  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;
+    }
+
+  if (negative_p)
+    {
+      num_digits++;
+      string[sizeof(string) - 1 - num_digits] = '-';
+    }
+
+  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 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.
 /* 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.
@@ -158,26 +214,37 @@ struct screenhack_job {
   pid_t pid;
   int screen;
   enum job_status status;
   pid_t pid;
   int screen;
   enum job_status status;
+  time_t launched, killed;
   struct screenhack_job *next;
 };
 
 static struct screenhack_job *jobs = 0;
 
   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: %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);
+    {
+      char b[] = "           ??:??:??     ";
+      char *t = (job->killed   ? timestring (job->killed) :
+                 job->launched ? timestring (job->launched) : b);
+      t += 11;
+      t[8] = 0;
+        fprintf (stderr, "  %5ld: %2d: (%s) %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" : "    ???"),
+                 t, job->name);
+    }
   fprintf (stderr, "\n");
 }
 
   fprintf (stderr, "\n");
 }
 
@@ -193,7 +260,6 @@ make_job (pid_t pid, int screen, const char *cmd)
   const char *in = cmd;
   char *out = name;
   int got_eq = 0;
   const char *in = cmd;
   char *out = name;
   int got_eq = 0;
-  int first = 1;
 
   clean_job_list();
 
 
   clean_job_list();
 
@@ -208,7 +274,6 @@ make_job (pid_t pid, int screen, const char *cmd)
     {                                  /* then get the next token instead. */
       got_eq = 0;
       out = name;
     {                                  /* then get the next token instead. */
       got_eq = 0;
       out = name;
-      first = 0;
       goto AGAIN;
     }
 
       goto AGAIN;
     }
 
@@ -219,6 +284,8 @@ make_job (pid_t pid, int screen, const char *cmd)
   job->pid = pid;
   job->screen = screen;
   job->status = job_running;
   job->pid = pid;
   job->screen = screen;
   job->status = job_running;
+  job->launched = time ((time_t *) 0);
+  job->killed = 0;
   job->next = jobs;
   jobs = job;
 
   job->next = jobs;
   jobs = job;
 
@@ -257,6 +324,10 @@ static void
 clean_job_list (void)
 {
   struct screenhack_job *job, *prev, *next;
 clean_job_list (void)
 {
   struct screenhack_job *job, *prev, *next;
+  time_t now = time ((time_t *) 0);
+  static time_t last_warn = 0;
+  Bool warnedp = False;
+
   for (prev = 0, job = jobs, next = (job ? job->next : 0);
        job;
        prev = job, job = next, next = (job ? job->next : 0))
   for (prev = 0, job = jobs, next = (job ? job->next : 0);
        job;
        prev = job, job = next, next = (job ? job->next : 0))
@@ -268,7 +339,21 @@ clean_job_list (void)
          free_job (job);
          job = prev;
        }
          free_job (job);
          job = prev;
        }
+      else if (job->status == job_killed &&
+               now - job->killed > 10 &&
+               now - last_warn   > 10)
+        {
+          fprintf (stderr,
+                   "%s: WARNING: pid %ld (%s) sent SIGTERM %ld seconds ago"
+                   " and did not die!\n",
+                   blurb(),
+                   (long) job->pid,
+                   job->name,
+                   (long) (now - job->killed));
+          warnedp = True;
+        }
     }
     }
+  if (warnedp) last_warn = now;
 }
 
 
 }
 
 
@@ -302,12 +387,20 @@ static int block_sigchld_handler = 0;
 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);
   sigemptyset (&child_set);
   sigaddset (&child_set, SIGCHLD);
-  sigaddset (&child_set, SIGPIPE);
   sigprocmask (SIG_BLOCK, &child_set, 0);
   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++;
 
@@ -321,13 +414,27 @@ block_sigchld (void)
 void
 unblock_sigchld (void)
 {
 void
 unblock_sigchld (void)
 {
+  if (block_sigchld_handler <= 0)
+    abort();
+
+  if (block_sigchld_handler <= 1)  /* only unblock if count going to 0 */
+    {
 #ifdef HAVE_SIGACTION
 #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);
   sigemptyset(&child_set);
   sigaddset(&child_set, SIGCHLD);
-  sigaddset(&child_set, SIGPIPE);
   sigprocmask(SIG_UNBLOCK, &child_set, 0);
   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--;
 }
@@ -341,7 +448,7 @@ kill_job (saver_info *si, pid_t pid, int signal)
 
   clean_job_list();
 
 
   clean_job_list();
 
-  if (block_sigchld_handler)
+  if (in_signal_handler_p)
     /* This function should not be called from the signal handler. */
     abort();
 
     /* This function should not be called from the signal handler. */
     abort();
 
@@ -359,7 +466,10 @@ kill_job (saver_info *si, pid_t pid, int signal)
     }
 
   switch (signal) {
     }
 
   switch (signal) {
-  case SIGTERM: job->status = job_killed;  break;
+  case SIGTERM:
+    job->status = job_killed;
+    job->killed = time ((time_t *) 0);
+    break;
 #ifdef SIGSTOP
     /* #### there must be a way to do this on VMS... */
   case SIGSTOP: job->status = job_stopped; break;
 #ifdef SIGSTOP
     /* #### there must be a way to do this on VMS... */
   case SIGSTOP: job->status = job_stopped; break;
@@ -410,10 +520,22 @@ static RETSIGTYPE
 sigchld_handler (int sig)
 {
   saver_info *si = global_si_kludge;   /* I hate C so much... */
 sigchld_handler (int sig)
 {
   saver_info *si = global_si_kludge;   /* I hate C so much... */
+  in_signal_handler_p++;
 
   if (si->prefs.debug_p)
 
   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();
@@ -425,11 +547,13 @@ sigchld_handler (int sig)
     }
 
   init_sigchld();
     }
 
   init_sigchld();
+  in_signal_handler_p--;
 }
 #endif /* SIGCHLD */
 
 
 #ifndef VMS
 }
 #endif /* SIGCHLD */
 
 
 #ifndef VMS
+
 static void
 await_dying_children (saver_info *si)
 {
 static void
 await_dying_children (saver_info *si)
 {
@@ -444,11 +568,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.
@@ -488,12 +630,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: %d: child pid %lu (%s) exited abnormally (code %d).\n",
-                blurb(), screen_no, (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)
-       fprintf (stderr, "%s: %d: child pid %lu (%s) exited normally.\n",
-                blurb(), screen_no, (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;
@@ -504,9 +672,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: %d: child pid %lu (%s) terminated with %s.\n",
-                blurb(), screen_no, (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;
@@ -514,17 +696,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;
     }
@@ -637,7 +843,8 @@ print_path_error (const char *program)
       fprintf (stderr, "\n");
       *path = 0;
 # if defined(HAVE_GETCWD)
       fprintf (stderr, "\n");
       *path = 0;
 # if defined(HAVE_GETCWD)
-      getcwd (path, sizeof(path));
+      if (! getcwd (path, sizeof(path)))
+        *path = 0;
 # elif defined(HAVE_GETWD)
       getwd (path);
 # endif
 # elif defined(HAVE_GETWD)
       getwd (path);
 # endif
@@ -655,20 +862,82 @@ print_path_error (const char *program)
 }
 
 
 }
 
 
-static void
-spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
+/* 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->screen, ssi->screensaver_window);
+
+      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;
+}
+
+
+void
+spawn_screenhack (saver_screen_info *ssi)
 {
   saver_info *si = ssi->global;
   saver_preferences *p = &si->prefs;
 {
   saver_info *si = ssi->global;
   saver_preferences *p = &si->prefs;
-  raise_window (si, first_time_p, True, False);
   XFlush (si->dpy);
 
   XFlush (si->dpy);
 
+  if (!monitor_powered_on_p (si))
+    {
+      if (si->prefs.verbose_p)
+        fprintf (stderr,
+                 "%s: %d: X says monitor has powered down; "
+                 "not launching a hack.\n", blurb(), ssi->number);
+      return;
+    }
+
   if (p->screenhacks_count)
     {
       screenhack *hack;
       pid_t forked;
       char buf [255];
   if (p->screenhacks_count)
     {
       screenhack *hack;
       pid_t forked;
       char buf [255];
-      int new_hack;
+      int new_hack = -1;
       int retry_count = 0;
       Bool force = False;
 
       int retry_count = 0;
       Bool force = False;
 
@@ -715,6 +984,14 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
         {
           new_hack = -1;
         }
         {
           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.) */
       else  /* (p->mode == RANDOM_HACKS) */
        {
          /* Select a random hack (but not the one we just ran.) */
@@ -744,6 +1021,7 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
         
       if (!force &&
          (!hack->enabled_p ||
         
       if (!force &&
          (!hack->enabled_p ||
+          !on_path_p (hack->command) ||
           !select_visual_of_hack (ssi, hack)))
        {
          if (++retry_count > (p->screenhacks_count*4))
           !select_visual_of_hack (ssi, hack)))
        {
          if (++retry_count > (p->screenhacks_count*4))
@@ -769,90 +1047,44 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
       if (si->selection_mode < 0)
        si->selection_mode = 0;
 
       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, 0);
-
-       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, hack->command,
-                     (unsigned long) getpid ());
-
-         exec_command (p->shell, hack->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 (hack->command);
-
-          exit (1);  /* exits child fork */
+         saver_exit (si, 1, "couldn't fork");
          break;
 
        default:
          ssi->pid = forked;
          break;
 
        default:
          ssi->pid = forked;
-         (void) make_job (forked, ssi->number, hack->command);
          break;
        }
     }
          break;
        }
     }
-}
-
-
-void
-spawn_screenhack (saver_info *si, Bool first_time_p)
-{
-  if (monitor_powered_on_p (si))
-    {
-      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());
 
 
-  store_saver_status (si);  /* store current hack numbers */
+  store_saver_status (si);  /* store current hack number */
 }
 
 
 void
 }
 
 
 void
-kill_screenhack (saver_info *si)
+kill_screenhack (saver_screen_info *ssi)
 {
 {
-  int i;
-  for (i = 0; i < si->nscreens; i++)
-    {
-      saver_screen_info *ssi = &si->screens[i];
-      if (ssi->pid)
-       kill_job (si, ssi->pid, SIGTERM);
-      ssi->pid = 0;
-    }
+  saver_info *si = ssi->global;
+  if (ssi->pid)
+    kill_job (si, ssi->pid, SIGTERM);
+  ssi->pid = 0;
 }
 
 
 void
 }
 
 
 void
-suspend_screenhack (saver_info *si, Bool suspend_p)
+suspend_screenhack (saver_screen_info *ssi, Bool suspend_p)
 {
 #ifdef SIGSTOP /* older VMS doesn't have it... */
 {
 #ifdef SIGSTOP /* older VMS doesn't have it... */
-  int i;
-  for (i = 0; i < si->nscreens; i++)
-    {
-      saver_screen_info *ssi = &si->screens[i];
-      if (ssi->pid)
-       kill_job (si, ssi->pid, (suspend_p ? SIGSTOP : SIGCONT));
-    }
+  saver_info *si = ssi->global;
+  if (ssi->pid)
+    kill_job (si, ssi->pid, (suspend_p ? SIGSTOP : SIGCONT));
 #endif /* SIGSTOP */
 }
 
 #endif /* SIGSTOP */
 }
 
@@ -905,7 +1137,9 @@ hack_environment (saver_info *si)
   if (def_path && *def_path)
     {
       const char *opath = getenv("PATH");
   if (def_path && *def_path)
     {
       const char *opath = getenv("PATH");
-      char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
+      char *npath;
+      if (! opath) opath = "/bin:/usr/bin";  /* WTF */
+      npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
       strcpy (npath, "PATH=");
       strcat (npath, def_path);
       strcat (npath, ":");
       strcpy (npath, "PATH=");
       strcat (npath, def_path);
       strcat (npath, ":");
@@ -924,7 +1158,7 @@ hack_environment (saver_info *si)
 
 
 void
 
 
 void
-hack_subproc_environment (saver_screen_info *ssi)
+hack_subproc_environment (Screen *screen, Window saver_window)
 {
   /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
      the spawned processes inherit is correct.  First, it must be on the same
 {
   /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
      the spawned processes inherit is correct.  First, it must be on the same
@@ -934,29 +1168,45 @@ 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);
-  char *ndpy = (char *) malloc(strlen(odpy) + 20);
-  char *s;
+  Display *dpy = DisplayOfScreen (screen);
+  const char *odpy = DisplayString (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);
 
 
   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", ssi->number);               /* put on screen number */
+  sprintf(s, "%d", screen_number (screen));    /* put on screen number */
+
+  sprintf (nssw, "XSCREENSAVER_WINDOW=0x%lX", (unsigned long) saver_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 ();
-  /* do not free(ndpy) -- see above. */
+  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 */
 }
 
@@ -964,12 +1214,13 @@ hack_subproc_environment (saver_screen_info *ssi)
 /* GL crap */
 
 Visual *
 /* GL crap */
 
 Visual *
-get_best_gl_visual (saver_screen_info *ssi)
+get_best_gl_visual (saver_info *si, Screen *screen)
 {
 {
-  saver_info *si = ssi->global;
   pid_t forked;
   int fds [2];
   int in, out;
   pid_t forked;
   int fds [2];
   int in, out;
+  int errfds[2];
+  int errin = -1, errout = -1;
   char buf[1024];
 
   char *av[10];
   char buf[1024];
 
   char *av[10];
@@ -987,6 +1238,23 @@ get_best_gl_visual (saver_screen_info *ssi)
   in = fds [0];
   out = fds [1];
 
   in = fds [0];
   out = fds [1];
 
+  if (!si->prefs.verbose_p)
+    {
+      if (pipe (errfds))
+        {
+          perror ("error creating pipe:");
+          return 0;
+        }
+
+      errin = errfds [0];
+      errout = errfds [1];
+    }
+
+  block_sigchld();   /* This blocks it in the parent and child, to avoid
+                        racing.  It is never unblocked in the child before
+                        the child exits, but that doesn't matter.
+                      */
+
   switch ((int) (forked = fork ()))
     {
     case -1:
   switch ((int) (forked = fork ()))
     {
     case -1:
@@ -997,23 +1265,32 @@ get_best_gl_visual (saver_screen_info *ssi)
       }
     case 0:
       {
       }
     case 0:
       {
-        int stdout_fd = 1;
-
         close (in);  /* don't need this one */
         close (ConnectionNumber (si->dpy));    /* close display fd */
 
         close (in);  /* don't need this one */
         close (ConnectionNumber (si->dpy));    /* close display fd */
 
-        if (dup2 (out, stdout_fd) < 0)         /* pipe stdout */
+        if (dup2 (out, STDOUT_FILENO) < 0)     /* pipe stdout */
           {
             perror ("could not dup() a new stdout:");
             return 0;
           }
           {
             perror ("could not dup() a new stdout:");
             return 0;
           }
-        hack_subproc_environment (ssi);                /* set $DISPLAY */
+
+        if (! si->prefs.verbose_p)
+          {
+            close(errin);
+            if (dup2 (errout, STDERR_FILENO) < 0)
+              {
+                perror ("could not dup() a new stderr:");
+                return 0;
+              }
+          }
+
+        hack_subproc_environment (screen, 0);  /* set $DISPLAY */
 
         execvp (av[0], av);                    /* shouldn't return. */
 
 
         execvp (av[0], av);                    /* shouldn't return. */
 
-        if (errno != ENOENT || si->prefs.verbose_p)
+        if (errno != ENOENT /* || si->prefs.verbose_p */ )
           {
           {
-            /* Ignore "no such file or directory" errors, unless verbose.
+            /* Ignore "no such file or directory" errors.
                Issue all other exec errors, though. */
             sprintf (buf, "%s: running %s", blurb(), av[0]);
             perror (buf);
                Issue all other exec errors, though. */
             sprintf (buf, "%s: running %s", blurb(), av[0]);
             perror (buf);
@@ -1025,6 +1302,7 @@ get_best_gl_visual (saver_screen_info *ssi)
       {
         int result = 0;
         int wait_status = 0;
       {
         int result = 0;
         int wait_status = 0;
+        pid_t pid = -1;
 
         FILE *f = fdopen (in, "r");
         unsigned long v = 0;
 
         FILE *f = fdopen (in, "r");
         unsigned long v = 0;
@@ -1033,11 +1311,29 @@ get_best_gl_visual (saver_screen_info *ssi)
         close (out);  /* don't need this one */
 
         *buf = 0;
         close (out);  /* don't need this one */
 
         *buf = 0;
-        fgets (buf, sizeof(buf)-1, f);
+        if (! fgets (buf, sizeof(buf)-1, f))
+          *buf = 0;
         fclose (f);
 
         fclose (f);
 
-        /* Wait for the child to die. */
-        waitpid (-1, &wait_status, 0);
+        if (! si->prefs.verbose_p)
+          {
+            close (errout);
+            close (errin);
+          }
+
+        /* Wait for the child to die - wait for this pid only, not others. */
+        pid = waitpid (forked, &wait_status, 0);
+        if (si->prefs.debug_p)
+          {
+            write_string (STDERR_FILENO, blurb());
+            write_string (STDERR_FILENO, ": waitpid(");
+            write_long   (STDERR_FILENO, (long) forked);
+            write_string (STDERR_FILENO, ") ==> ");
+            write_long   (STDERR_FILENO, (long) pid);
+            write_string (STDERR_FILENO, "\n");
+          }
+
+        unblock_sigchld();   /* child is dead and waited, unblock now. */
 
         if (1 == sscanf (buf, "0x%lx %c", &v, &c))
           result = (int) v;
 
         if (1 == sscanf (buf, "0x%lx %c", &v, &c))
           result = (int) v;
@@ -1060,12 +1356,13 @@ get_best_gl_visual (saver_screen_info *ssi)
           }
         else
           {
           }
         else
           {
-            Visual *v = id_to_visual (ssi->screen, result);
+            Visual *v = id_to_visual (screen, result);
             if (si->prefs.verbose_p)
               fprintf (stderr, "%s: %d: %s: GL visual is 0x%X%s.\n",
             if (si->prefs.verbose_p)
               fprintf (stderr, "%s: %d: %s: GL visual is 0x%X%s.\n",
-                       blurb(), ssi->number,
+                       blurb(), screen_number (screen),
                        av[0], result,
                        av[0], result,
-                       (v == ssi->default_visual ? " (default)" : ""));
+                       (v == DefaultVisualOfScreen (screen)
+                        ? " (default)" : ""));
             return v;
           }
       }
             return v;
           }
       }