http://ftp.x.org/contrib/applications/xscreensaver-2.34.tar.gz
[xscreensaver] / driver / subprocs.c
index 4126960b0bc177a0c8a48a349c6c499e569de782..3f43139860243883555f9a910c20f7155c579010 100644 (file)
@@ -1,6 +1,6 @@
 /* subprocs.c --- choosing, spawning, and killing screenhacks.
  * xscreensaver, Copyright (c) 1991, 1992, 1993, 1995, 1997, 1998
- *  Jamie Zawinski <jwz@netscape.com>
+ *  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
 #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 */
 
-#ifndef VMS
-
-# include <sys/resource.h>     /* for setpriority() and PRIO_PROCESS */
+#ifdef HAVE_SYS_WAIT_H
 # include <sys/wait.h>         /* for waitpid() and associated macros */
+#endif
 
-#else  /* VMS */
-
-# if __DECC_VER >= 50200000
-#  include <sys/wait.h>
-# endif
+#if defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
+# include <sys/resource.h>     /* for setpriority() and PRIO_PROCESS */
+#endif
 
+#ifdef VMS
 # include <processes.h>
 # include <unixio.h>           /* for close */
 # include <unixlib.h>          /* for getpid */
-# define pid_t    int
-# define fork     vfork
-
+# define pid_t int
+# define fork  vfork
 #endif /* VMS */
 
 #include <signal.h>            /* for the signal names */
 
-#ifndef NO_SETUID
-#include <pwd.h>               /* for getpwnam() and struct passwd */
-#include <grp.h>               /* for getgrgid() and struct group */
-#endif /* NO_SETUID */
-
 #if !defined(SIGCHLD) && defined(SIGCLD)
-#define SIGCHLD SIGCLD
+# define SIGCHLD SIGCLD
 #endif
 
+#if 0 /* putenv() is declared in stdlib.h on modern linux systems. */
 #ifdef HAVE_PUTENV
 extern int putenv (/* const char * */);        /* getenv() is in stdlib.h... */
 #endif
+#endif
+
 extern int kill (pid_t, int);          /* signal() is in sys/signal.h... */
 
 /* This file doesn't need the Xt headers, so stub these types out... */
@@ -76,7 +71,7 @@ extern int kill (pid_t, int);         /* signal() is in sys/signal.h... */
 
 extern saver_info *global_si_kludge;   /* I hate C so much... */
 
-static void hack_environment (saver_screen_info *ssi);
+static void hack_subproc_environment (saver_screen_info *ssi);
 
 
 static void
@@ -93,7 +88,7 @@ nice_subproc (int nice_level)
     if (nice (n) == -1 && errno != 0)
       {
        char buf [512];
-       sprintf (buf, "%s: nice(%d) failed", progname, n);
+       sprintf (buf, "%s: nice(%d) failed", blurb(), n);
        perror (buf);
     }
   }
@@ -102,13 +97,13 @@ nice_subproc (int nice_level)
     {
       char buf [512];
       sprintf (buf, "%s: setpriority(PRIO_PROCESS, %lu, %d) failed",
-              progname, (unsigned long) getpid(), nice_level);
+              blurb(), (unsigned long) getpid(), nice_level);
       perror (buf);
     }
 #else
   fprintf (stderr,
           "%s: don't know how to change process priority on this system.\n",
-          progname);
+          blurb());
 
 #endif
 }
@@ -133,7 +128,7 @@ exec_simple_command (const char *command)
 
   {
     char buf [512];
-    sprintf (buf, "%s: could not execute \"%s\"", progname, av[0]);
+    sprintf (buf, "%s: could not execute \"%s\"", blurb(), av[0]);
     perror (buf);
 
     if (errno == ENOENT &&
@@ -191,7 +186,7 @@ exec_complex_command (const char *shell, const char *command)
 
   {
     char buf [512];
-    sprintf (buf, "%s: execvp(\"%s\") failed", progname, av[0]);
+    sprintf (buf, "%s: execvp(\"%s\") failed", blurb(), av[0]);
     perror (buf);
     fflush(stderr);
     fflush(stdout);
@@ -256,9 +251,9 @@ exec_screenhack (saver_info *si, const char *command)
   Bool hairy_p = !!strpbrk (command, "*?$&!<>[];`'\\\"");
 
   if (p->verbose_p)
-    printf ("%s: spawning \"%s\" in pid %lu%s.\n",
-           progname, command, (unsigned long) getpid (),
-           (hairy_p ? " (via shell)" : ""));
+    fprintf (stderr, "%s: spawning \"%s\" in pid %lu%s.\n",
+            blurb(), command, (unsigned long) getpid (),
+            (hairy_p ? " (via shell)" : ""));
 
   if (hairy_p)
     /* If it contains any shell metacharacters, do it the hard way,
@@ -270,7 +265,8 @@ exec_screenhack (saver_info *si, const char *command)
 
 #else /* VMS */
   if (p->verbose_p)
-    printf ("%s: spawning \"%s\" in pid %lu.\n", progname, command, getpid());
+    fprintf (stderr, "%s: spawning \"%s\" in pid %lu.\n",
+            blurb(), command, getpid());
   exec_vms_command (command);
 #endif /* VMS */
 
@@ -306,7 +302,7 @@ void
 show_job_list (void)
 {
   struct screenhack_job *job;
-  fprintf(stderr, "%s: job list:\n", progname);
+  fprintf(stderr, "%s: job list:\n", blurb());
   for (job = jobs; job; job = job->next)
     fprintf (stderr, "  %5ld: (%s) %s\n",
             (long) job->pid,
@@ -471,7 +467,7 @@ kill_job (saver_info *si, pid_t pid, int signal)
     {
       if (p->verbose_p)
        fprintf (stderr, "%s: no child %ld to signal!\n",
-                progname, (long) pid);
+                blurb(), (long) pid);
       goto DONE;
     }
 
@@ -487,14 +483,14 @@ kill_job (saver_info *si, pid_t pid, int signal)
 
 #ifdef SIGSTOP
   if (p->verbose_p)
-    fprintf (stderr, "%s: %s pid %lu.\n", progname,
+    fprintf (stderr, "%s: %s pid %lu.\n", blurb(),
             (signal == SIGTERM ? "killing" :
              signal == SIGSTOP ? "suspending" :
              signal == SIGCONT ? "resuming" : "signalling"),
             (unsigned long) job->pid);
 #else  /* !SIGSTOP */
   if (p->verbose_p)
-    fprintf (stderr, "%s: %s pid %lu.\n", progname, "killing",
+    fprintf (stderr, "%s: %s pid %lu.\n", blurb(), "killing",
             (unsigned long) job->pid);
 #endif /* !SIGSTOP */
 
@@ -504,12 +500,12 @@ kill_job (saver_info *si, pid_t pid, int signal)
     {
       if (errno == ESRCH)
        fprintf (stderr, "%s: child process %lu (%s) was already dead.\n",
-                progname, job->pid, job->name);
+                blurb(), job->pid, job->name);
       else
        {
          char buf [1024];
          sprintf (buf, "%s: couldn't kill child process %lu (%s)",
-                  progname, job->pid, job->name);
+                  blurb(), job->pid, job->name);
          perror (buf);
        }
     }
@@ -533,7 +529,7 @@ sigchld_handler (int sig)
   saver_info *si = global_si_kludge;   /* I hate C so much... */
 
   if (si->prefs.debug_p)
-    fprintf(stderr, "%s: got SIGCHLD%s\n", progname,
+    fprintf(stderr, "%s: got SIGCHLD%s\n", blurb(),
            (block_sigchld_handler ? " (blocked)" : ""));
 
   if (block_sigchld_handler < 0)
@@ -565,10 +561,10 @@ await_dying_children (saver_info *si)
       if (si->prefs.debug_p)
        {
          if (kid < 0 && errno)
-           fprintf (stderr, "%s: waitpid(-1) ==> %ld (%d)\n", progname,
+           fprintf (stderr, "%s: waitpid(-1) ==> %ld (%d)\n", blurb(),
                     (long) kid, errno);
          else
-           fprintf (stderr, "%s: waitpid(-1) ==> %ld\n", progname,
+           fprintf (stderr, "%s: waitpid(-1) ==> %ld\n", blurb(),
                     (long) kid);
        }
 
@@ -610,10 +606,10 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status)
           (p->verbose_p || job->status != job_killed)))
        fprintf (stderr,
                 "%s: child pid %lu (%s) exited abnormally (code %d).\n",
-                progname, (unsigned long) kid, name, exit_status);
+                blurb(), (unsigned long) kid, name, exit_status);
       else if (p->verbose_p)
-       printf ("%s: child pid %lu (%s) exited normally.\n",
-               progname, (unsigned long) kid, name);
+       fprintf (stderr, "%s: child pid %lu (%s) exited normally.\n",
+                blurb(), (unsigned long) kid, name);
 
       if (job)
        job->status = job_dead;
@@ -625,7 +621,7 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status)
          job->status != job_killed ||
          WTERMSIG (wait_status) != SIGTERM)
        fprintf (stderr, "%s: child pid %lu (%s) terminated with %s.\n",
-                progname, (unsigned long) kid, name,
+                blurb(), (unsigned long) kid, name,
                 signal_name (WTERMSIG(wait_status)));
 
       if (job)
@@ -635,7 +631,7 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status)
     {
       if (p->verbose_p)
        fprintf (stderr, "%s: child pid %lu (%s) stopped with %s.\n",
-                progname, (unsigned long) kid, name,
+                blurb(), (unsigned long) kid, name,
                 signal_name (WSTOPSIG (wait_status)));
 
       if (job)
@@ -644,7 +640,7 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status)
   else
     {
       fprintf (stderr, "%s: child pid %lu (%s) died in a mysterious way!",
-              progname, (unsigned long) kid, name);
+              blurb(), (unsigned long) kid, name);
       if (job)
        job->status = job_dead;
     }
@@ -684,7 +680,7 @@ init_sigchld (void)
       if (sigaction(SIGCHLD, &action, &old) < 0)
        {
          char buf [255];
-         sprintf (buf, "%s: couldn't catch SIGCHLD", progname);
+         sprintf (buf, "%s: couldn't catch SIGCHLD", blurb());
          perror (buf);
        }
       sigchld_initialized_p = True;
@@ -695,7 +691,7 @@ init_sigchld (void)
   if (((long) signal (SIGCHLD, sigchld_handler)) == -1L)
     {
       char buf [255];
-      sprintf (buf, "%s: couldn't catch SIGCHLD", progname);
+      sprintf (buf, "%s: couldn't catch SIGCHLD", blurb());
       perror (buf);
     }
 # endif /* !HAVE_SIGACTION */
@@ -734,7 +730,7 @@ select_visual_of_hack (saver_screen_info *ssi, const char *hack)
               (si->demo_mode_p
                ? "%s: warning, no \"%s\" visual for \"%s\".\n"
                : "%s: no \"%s\" visual; skipping \"%s\".\n"),
-              progname, (vis ? vis : "???"), in);
+              blurb(), (vis ? vis : "???"), in);
     }
 
   return selected;
@@ -770,11 +766,13 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
        AGAIN:
          if (p->screenhacks_count == 1)
            new_hack = 0;
-         else if (si->next_mode_p == 1)
+         else if (si->selection_mode == -1)
            new_hack = (ssi->current_hack + 1) % p->screenhacks_count;
-         else if (si->next_mode_p == 2)
+         else if (si->selection_mode == -2)
            new_hack = ((ssi->current_hack + p->screenhacks_count - 1)
                        % p->screenhacks_count);
+         else if (si->selection_mode > 0)
+           new_hack = ((si->selection_mode - 1) % p->screenhacks_count);
          else
            while ((new_hack = random () % p->screenhacks_count)
                   == ssi->current_hack)
@@ -794,14 +792,16 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
                  if (p->verbose_p)
                    fprintf(stderr,
                            "%s: no suitable visuals for these programs.\n",
-                           progname);
+                           blurb());
                  return;
                }
              else
                goto AGAIN;
            }
        }
-      si->next_mode_p = 0;
+
+      if (si->selection_mode < 0)
+       si->selection_mode = 0;
 
 
       /* If there's a visual description on the front of the command, nuke it.
@@ -823,15 +823,15 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
       switch ((int) (forked = fork ()))
        {
        case -1:
-         sprintf (buf, "%s: couldn't fork", progname);
+         sprintf (buf, "%s: couldn't fork", blurb());
          perror (buf);
          restore_real_vroot (si);
-         saver_exit (si, 1);
+         saver_exit (si, 1, 0);
 
        case 0:
          close (ConnectionNumber (si->dpy));   /* close display fd */
          nice_subproc (p->nice_inferior);      /* change process priority */
-         hack_environment (ssi);               /* set $DISPLAY */
+         hack_subproc_environment (ssi);       /* set $DISPLAY */
          exec_screenhack (si, hack);           /* this does not return */
          abort();
          break;
@@ -849,6 +849,16 @@ void
 spawn_screenhack (saver_info *si, Bool first_time_p)
 {
   int i;
+
+  if (!monitor_powered_on_p (si))
+    {
+      if (si->prefs.verbose_p)
+       fprintf (stderr,
+                "%s: server reports that monitor has powered down; "
+                "not launching a new hack.\n", blurb());
+      return;
+    }
+
   for (i = 0; i < si->nscreens; i++)
     {
       saver_screen_info *ssi = &si->screens[i];
@@ -921,54 +931,35 @@ screenhack_running_p (saver_info *si)
 }
 
 \f
-/* Restarting the xscreensaver process from scratch. */
+/* Environment variables. */
 
-static char **saved_argv;
 
+/* Modifies $PATH in the current environment, so that if DEFAULT_PATH_PREFIX
+   is defined, the xscreensaver daemon will search that directory for hacks.
+ */
 void
-save_argv (int argc, char **argv)
+hack_environment (saver_info *si)
 {
-  saved_argv = (char **) malloc ((argc + 2) * sizeof (char *));
-  saved_argv [argc] = 0;
-  while (argc--)
+#if defined(HAVE_PUTENV) && defined(DEFAULT_PATH_PREFIX)
+  static const char *def_path = DEFAULT_PATH_PREFIX;
+  if (def_path && *def_path)
     {
-      int i = strlen (argv [argc]) + 1;
-      saved_argv [argc] = (char *) malloc (i);
-      memcpy (saved_argv [argc], argv [argc], i);
+      const char *opath = getenv("PATH");
+      char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
+      strcpy (npath, "PATH=");
+      strcat (npath, def_path);
+      strcat (npath, ":");
+      strcat (npath, opath);
+
+      if (putenv (npath))
+       abort ();
     }
+#endif /* HAVE_PUTENV && DEFAULT_PATH_PREFIX */
 }
 
-void
-restart_process (saver_info *si)
-{
-  fflush (real_stdout);
-  fflush (real_stderr);
-  execvp (saved_argv [0], saved_argv); /* shouldn't return */
-  {
-    char buf [512];
-    sprintf (buf, "%s: could not restart process", progname);
-    perror(buf);
-    fflush(stderr);
-  }
-}
-
-/* Like restart_process(), but ensures that when it restarts,
-   it comes up in demo-mode. */
-void
-demo_mode_restart_process (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 (si);                /* shouldn't return */
-  saved_argv [i] = 0;
-  XBell(si->dpy, 0);
-}
 
 static void
-hack_environment (saver_screen_info *ssi)
+hack_subproc_environment (saver_screen_info *ssi)
 {
   /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
      the spawned processes inherit is correct.  First, it must be on the same
@@ -988,7 +979,6 @@ hack_environment (saver_screen_info *ssi)
   for (screen_number = 0; screen_number < si->nscreens; screen_number++)
     if (ssi == &si->screens[screen_number])
       break;
-  if (screen_number >= si->nscreens) abort();
 
   strcpy (ndpy, "DISPLAY=");
   s = ndpy + strlen(ndpy);
@@ -1010,129 +1000,94 @@ hack_environment (saver_screen_info *ssi)
 }
 
 \f
-/* Change the uid/gid of the screensaver process, so that it is safe for it
-   to run setuid root (which it needs to do on some systems to read the 
-   encrypted passwords from the passwd file.)
-
-   hack_uid() is run before opening the X connection, so that XAuth works.
-   hack_uid_warn() is called after the connection is opened and the command
-   line arguments are parsed, so that the messages from hack_uid() get 
-   printed after we know whether we're in `verbose' mode.
- */
-
-#ifndef NO_SETUID
+/* Restarting the xscreensaver process from scratch. */
 
-static int hack_uid_errno;
-static char hack_uid_buf [255], *hack_uid_error;
+static char **saved_argv;
 
 void
-hack_uid (saver_info *si)
+save_argv (int argc, char **argv)
 {
-
-  /* If we've been run as setuid or setgid to someone else (most likely root)
-     turn off the extra permissions so that random user-specified programs
-     don't get special privileges.  (On some systems it might be necessary
-     to install this as setuid root in order to read the passwd file to
-     implement lock-mode...)
-  */
-  setgid (getgid ());
-  setuid (getuid ());
-
-  hack_uid_errno = 0;
-  hack_uid_error = 0;
-
-  /* If we're being run as root (as from xdm) then switch the user id
-     to something safe. */
-  if (getuid () == 0)
+  /* Leave room for one more argument, the -initial-demo-mode switch. */
+  saved_argv = (char **) calloc (argc+2, sizeof (char *));
+  saved_argv [argc] = 0;
+  while (argc--)
     {
-      struct passwd *p;
-      /* Locking can't work when running as root, because we have no way of
-        knowing what the user id of the logged in user is (so we don't know
-        whose password to prompt for.)
-       */
-      si->locking_disabled_p = True;
-      si->nolock_reason = "running as root";
-      p = getpwnam ("nobody");
-      if (! p) p = getpwnam ("noaccess");
-      if (! p) p = getpwnam ("daemon");
-      if (! p) p = getpwnam ("bin");
-      if (! p) p = getpwnam ("sys");
-      if (! p)
-       {
-         hack_uid_error = "couldn't find safe uid; running as root.";
-         hack_uid_errno = -1;
-       }
-      else
-       {
-         struct group *g = getgrgid (p->pw_gid);
-         hack_uid_error = hack_uid_buf;
-         sprintf (hack_uid_error, "changing uid/gid to %s/%s (%ld/%ld).",
-                  p->pw_name, (g ? g->gr_name : "???"),
-                  (long) p->pw_uid, (long) p->pw_gid);
-
-         /* Change the gid to be a safe one.  If we can't do that, then
-            print a warning.  We change the gid before the uid so that we
-            change the gid while still root. */
-         if (setgid (p->pw_gid) != 0)
-           {
-             hack_uid_errno = errno;
-             sprintf (hack_uid_error, "couldn't set gid to %s (%ld)",
-                      (g ? g->gr_name : "???"), (long) p->pw_gid);
-           }
-
-         /* Now change the uid to be a safe one. */
-         if (setuid (p->pw_uid) != 0)
-           {
-             hack_uid_errno = errno;
-             sprintf (hack_uid_error, "couldn't set uid to %s (%ld)",
-                      p->pw_name, (long) p->pw_uid);
-           }
-       }
+      int i = strlen (argv [argc]) + 1;
+      saved_argv [argc] = (char *) malloc (i);
+      memcpy (saved_argv [argc], argv [argc], i);
     }
-# ifndef NO_LOCKING
- else  /* disable locking if already being run as "someone else" */
-   {
-     struct passwd *p = getpwuid (getuid ());
-     if (!p ||
-        !strcmp (p->pw_name, "root") ||
-        !strcmp (p->pw_name, "nobody") ||
-        !strcmp (p->pw_name, "noaccess") ||
-        !strcmp (p->pw_name, "daemon") ||
-        !strcmp (p->pw_name, "bin") ||
-        !strcmp (p->pw_name, "sys"))
-       {
-        si->locking_disabled_p = True;
-        si->nolock_reason = hack_uid_buf;
-        sprintf (si->nolock_reason, "running as %s", p->pw_name);
-       }
-   }
-# endif /* !NO_LOCKING */
 }
 
-void
-hack_uid_warn (saver_info *si)
+/* Modifies saved_argv to either contain or not contain "-initial-demo-mode".
+ */
+static void
+hack_saved_argv (Bool demo_mode_p)
 {
-  saver_preferences *p = &si->prefs;
+  static char *demo_mode_switch = "-initial-demo-mode";
 
-  if (! hack_uid_error)
-    ;
-  else if (hack_uid_errno == 0)
+  if (demo_mode_p)             /* We want the switch to be in the args. */
     {
-      if (p->verbose_p)
-       printf ("%s: %s\n", progname, hack_uid_error);
+      /* See if the switch is there already.  If so, we're done. */
+      int i;
+      for (i = 0; saved_argv[i]; i++)
+       if (!strcmp (saved_argv[i], demo_mode_switch))
+         return;
+
+      /* If it wasn't there, add it to the end.  save_argv() made room. */
+      saved_argv [i] = demo_mode_switch;
+      saved_argv [i+1] = 0;
     }
-  else
+  else                         /* We want the switch to not be in the args. */
     {
-      char buf [255];
-      sprintf (buf, "%s: %s", progname, hack_uid_error);
-      if (hack_uid_errno == -1)
-       fprintf (stderr, "%s\n", buf);
-      else
-       {
-         errno = hack_uid_errno;
-         perror (buf);
-       }
+      int i;
+      for (i = 0; saved_argv[i]; i++)
+       while (!strcmp (saved_argv [i], demo_mode_switch))
+         {
+           int j;
+           for (j = i; saved_argv[j]; j++)
+             saved_argv [j] = saved_argv [j+1];
+         }
     }
 }
 
-#endif /* !NO_SETUID */
+
+/* Re-execs the process with the arguments in saved_argv.
+   Does not return unless there was an error.
+ */
+static void
+restart_process_1 (saver_info *si)
+{
+  fflush (real_stdout);
+  fflush (real_stderr);
+  execvp (saved_argv [0], saved_argv); /* shouldn't return */
+  {
+    char buf [512];
+    sprintf (buf, "%s: could not restart process", blurb());
+    perror(buf);
+    fflush(stderr);
+  }
+  XBell(si->dpy, 0);
+}
+
+
+/* Re-execs the process with the arguments in saved_argv,
+   minus -initial-demo-mode.
+   Does not return unless there was an error.
+ */
+void
+restart_process (saver_info *si)
+{
+  hack_saved_argv (True);
+  restart_process_1 (si);
+}
+
+/* Re-execs the process with the arguments in saved_argv,
+   plus -initial-demo-mode.
+   Does not return unless there was an error.
+ */
+void
+demo_mode_restart_process (saver_info *si)
+{
+  hack_saved_argv (False);
+  restart_process_1 (si);
+}