From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / driver / subprocs.c
index b6948e8789c30dd8c8c025f79eeafa1d7a568c27..4f327e95851bcfa72f8989b2f055ddd32e1de0e0 100644 (file)
@@ -1,5 +1,5 @@
 /* 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
@@ -25,6 +25,7 @@
 #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 */
@@ -43,6 +44,7 @@
 #endif /* VMS */
 
 #include <signal.h>            /* for the signal names */
+#include <time.h>
 
 #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"
+#include "exec.h"
 #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.
@@ -88,6 +144,12 @@ extern saver_info *global_si_kludge;        /* I hate C so much... */
 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;
 
@@ -152,26 +214,37 @@ struct screenhack_job {
   pid_t pid;
   int screen;
   enum job_status status;
+  time_t launched, killed;
   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)
-    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");
 }
 
@@ -187,7 +260,6 @@ make_job (pid_t pid, int screen, const char *cmd)
   const char *in = cmd;
   char *out = name;
   int got_eq = 0;
-  int first = 1;
 
   clean_job_list();
 
@@ -202,7 +274,6 @@ make_job (pid_t pid, int screen, const char *cmd)
     {                                  /* then get the next token instead. */
       got_eq = 0;
       out = name;
-      first = 0;
       goto AGAIN;
     }
 
@@ -213,6 +284,8 @@ make_job (pid_t pid, int screen, const char *cmd)
   job->pid = pid;
   job->screen = screen;
   job->status = job_running;
+  job->launched = time ((time_t *) 0);
+  job->killed = 0;
   job->next = jobs;
   jobs = job;
 
@@ -251,6 +324,10 @@ static void
 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))
@@ -262,7 +339,21 @@ clean_job_list (void)
          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;
 }
 
 
@@ -288,28 +379,62 @@ static void describe_dead_child (saver_info *, pid_t, int wait_status);
 static int block_sigchld_handler = 0;
 
 
-void
+#ifdef HAVE_SIGACTION
+ sigset_t
+#else  /* !HAVE_SIGACTION */
+ int
+#endif /* !HAVE_SIGACTION */
 block_sigchld (void)
 {
 #ifdef HAVE_SIGACTION
+  struct sigaction sa;
   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);
-#endif /* HAVE_SIGACTION */
+
+#else  /* !HAVE_SIGACTION */
+  signal (SIGPIPE, SIG_IGN);
+#endif /* !HAVE_SIGACTION */
 
   block_sigchld_handler++;
+
+#ifdef HAVE_SIGACTION
+  return child_set;
+#else  /* !HAVE_SIGACTION */
+  return 0;
+#endif /* !HAVE_SIGACTION */
 }
 
 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
+  struct sigaction sa;
   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);
-#endif /* HAVE_SIGACTION */
+
+#else /* !HAVE_SIGACTION */
+  signal(SIGPIPE, SIG_DFL);
+#endif /* !HAVE_SIGACTION */
+    }
 
   block_sigchld_handler--;
 }
@@ -323,7 +448,7 @@ kill_job (saver_info *si, pid_t pid, int signal)
 
   clean_job_list();
 
-  if (block_sigchld_handler)
+  if (in_signal_handler_p)
     /* This function should not be called from the signal handler. */
     abort();
 
@@ -341,7 +466,10 @@ kill_job (saver_info *si, pid_t pid, int 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;
@@ -392,10 +520,22 @@ static RETSIGTYPE
 sigchld_handler (int sig)
 {
   saver_info *si = global_si_kludge;   /* I hate C so much... */
+  in_signal_handler_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)" : ""));
+      */
+      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();
@@ -407,11 +547,13 @@ sigchld_handler (int sig)
     }
 
   init_sigchld();
+  in_signal_handler_p--;
 }
 #endif /* SIGCHLD */
 
 
 #ifndef VMS
+
 static void
 await_dying_children (saver_info *si)
 {
@@ -426,11 +568,29 @@ await_dying_children (saver_info *si)
       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.
@@ -470,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)))
-       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)
-       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;
@@ -486,9 +672,23 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status)
          !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;
@@ -496,17 +696,41 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status)
   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
     {
+      /* 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;
     }
@@ -619,7 +843,8 @@ print_path_error (const char *program)
       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
@@ -637,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;
-  raise_window (si, first_time_p, True, False);
   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];
-      int new_hack;
+      int new_hack = -1;
       int retry_count = 0;
       Bool force = False;
 
@@ -697,6 +984,14 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
         {
           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.) */
@@ -726,6 +1021,7 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
         
       if (!force &&
          (!hack->enabled_p ||
+          !on_path_p (hack->command) ||
           !select_visual_of_hack (ssi, hack)))
        {
          if (++retry_count > (p->screenhacks_count*4))
@@ -751,90 +1047,44 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
       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);
-         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;
-         (void) make_job (forked, ssi->number, hack->command);
          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
-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
-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... */
-  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 */
 }
 
@@ -887,7 +1137,9 @@ hack_environment (saver_info *si)
   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, ":");
@@ -906,7 +1158,7 @@ hack_environment (saver_info *si)
 
 
 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
@@ -916,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.
+
+     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);
 
-  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 */
-  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 ();
-  /* 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 */
 }
 
@@ -946,12 +1214,13 @@ hack_subproc_environment (saver_screen_info *ssi)
 /* 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;
+  int errfds[2];
+  int errin = -1, errout = -1;
   char buf[1024];
 
   char *av[10];
@@ -969,6 +1238,23 @@ get_best_gl_visual (saver_screen_info *ssi)
   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:
@@ -979,23 +1265,32 @@ get_best_gl_visual (saver_screen_info *ssi)
       }
     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 */
+        if (dup2 (out, STDOUT_FILENO) < 0)     /* pipe stdout */
           {
             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. */
 
-        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);
@@ -1007,6 +1302,7 @@ get_best_gl_visual (saver_screen_info *ssi)
       {
         int result = 0;
         int wait_status = 0;
+        pid_t pid = -1;
 
         FILE *f = fdopen (in, "r");
         unsigned long v = 0;
@@ -1015,11 +1311,29 @@ get_best_gl_visual (saver_screen_info *ssi)
         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);
 
-        /* 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;
@@ -1042,12 +1356,13 @@ get_best_gl_visual (saver_screen_info *ssi)
           }
         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",
-                       blurb(), ssi->number,
+                       blurb(), screen_number (screen),
                        av[0], result,
-                       (v == ssi->default_visual ? " (default)" : ""));
+                       (v == DefaultVisualOfScreen (screen)
+                        ? " (default)" : ""));
             return v;
           }
       }