b2b341a8fde7e2af2f00e1dd423367e1f4cba260
[xscreensaver] / driver / subprocs.c
1 /* subprocs.c --- choosing, spawning, and killing screenhacks.
2  * xscreensaver, Copyright (c) 1991, 1992, 1993, 1995, 1997, 1998
3  *  Jamie Zawinski <jwz@netscape.com>
4  *
5  * Permission to use, copy, modify, distribute, and sell this software and its
6  * documentation for any purpose is hereby granted without fee, provided that
7  * the above copyright notice appear in all copies and that both that
8  * copyright notice and this permission notice appear in supporting
9  * documentation.  No representations are made about the suitability of this
10  * software for any purpose.  It is provided "as is" without express or 
11  * implied warranty.
12  */
13
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
17
18 #include <ctype.h>
19 #include <stdio.h>
20 #include <string.h>
21
22 #include <X11/Xlib.h>           /* not used for much... */
23
24 #ifndef ESRCH
25 #include <errno.h>
26 #endif
27
28 #include <sys/time.h>           /* sys/resource.h needs this for timeval */
29
30 #ifndef VMS
31
32 # include <sys/resource.h>      /* for setpriority() and PRIO_PROCESS */
33 # include <sys/wait.h>          /* for waitpid() and associated macros */
34
35 #else  /* VMS */
36
37 # if __DECC_VER >= 50200000
38 #  include <sys/wait.h>
39 # endif
40
41 # include <processes.h>
42 # include <unixio.h>            /* for close */
43 # include <unixlib.h>           /* for getpid */
44 # define pid_t    int
45 # define fork     vfork
46
47 #endif /* VMS */
48
49 #include <signal.h>             /* for the signal names */
50
51 #ifndef NO_SETUID
52 #include <pwd.h>                /* for getpwnam() and struct passwd */
53 #include <grp.h>                /* for getgrgid() and struct group */
54 #endif /* NO_SETUID */
55
56 #if !defined(SIGCHLD) && defined(SIGCLD)
57 #define SIGCHLD SIGCLD
58 #endif
59
60 #ifdef HAVE_PUTENV
61 extern int putenv (/* const char * */); /* getenv() is in stdlib.h... */
62 #endif
63 extern int kill (pid_t, int);           /* signal() is in sys/signal.h... */
64
65 /* This file doesn't need the Xt headers, so stub these types out... */
66 #undef XtPointer
67 #define XtAppContext void*
68 #define XrmDatabase  void*
69 #define XtIntervalId void*
70 #define XtPointer    void*
71 #define Widget       void*
72
73 #include "xscreensaver.h"
74 #include "yarandom.h"
75
76
77 extern saver_info *global_si_kludge;    /* I hate C so much... */
78
79 static void hack_subproc_environment (saver_screen_info *ssi);
80
81
82 static void
83 nice_subproc (int nice_level)
84 {
85   if (nice_level == 0)
86     return;
87
88 #if defined(HAVE_NICE)
89   {
90     int old_nice = nice (0);
91     int n = nice_level - old_nice;
92     errno = 0;
93     if (nice (n) == -1 && errno != 0)
94       {
95         char buf [512];
96         sprintf (buf, "%s: nice(%d) failed", progname, n);
97         perror (buf);
98     }
99   }
100 #elif defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
101   if (setpriority (PRIO_PROCESS, getpid(), nice_level) != 0)
102     {
103       char buf [512];
104       sprintf (buf, "%s: setpriority(PRIO_PROCESS, %lu, %d) failed",
105                progname, (unsigned long) getpid(), nice_level);
106       perror (buf);
107     }
108 #else
109   fprintf (stderr,
110            "%s: don't know how to change process priority on this system.\n",
111            progname);
112
113 #endif
114 }
115
116
117 #ifndef VMS
118
119 static void
120 exec_simple_command (const char *command)
121 {
122   char *av[1024];
123   int ac = 0;
124   char *token = strtok (strdup(command), " \t");
125   while (token)
126     {
127       av[ac++] = token;
128       token = strtok(0, " \t");
129     }
130   av[ac] = 0;
131
132   execvp (av[0], av);                   /* shouldn't return. */
133
134   {
135     char buf [512];
136     sprintf (buf, "%s: could not execute \"%s\"", progname, av[0]);
137     perror (buf);
138
139     if (errno == ENOENT &&
140         (token = getenv("PATH")))
141       {
142 # ifndef PATH_MAX
143 #  ifdef MAXPATHLEN
144 #   define PATH_MAX MAXPATHLEN
145 #  else
146 #   define PATH_MAX 2048
147 #  endif
148 # endif
149         char path[PATH_MAX];
150         fprintf (stderr, "\n");
151         *path = 0;
152 # if defined(HAVE_GETCWD)
153         getcwd (path, sizeof(path));
154 # elif defined(HAVE_GETWD)
155         getwd (path);
156 # endif
157         if (*path)
158           fprintf (stderr, "    Current directory is: %s\n", path);
159         fprintf (stderr, "    PATH is:\n");
160         token = strtok (strdup(token), ":");
161         while (token)
162           {
163             fprintf (stderr, "        %s\n", token);
164             token = strtok(0, ":");
165           }
166         fprintf (stderr, "\n");
167       }
168   }
169   fflush(stderr);
170   fflush(stdout);
171   exit (1);     /* Note that this only exits a child fork.  */
172 }
173
174
175 static void
176 exec_complex_command (const char *shell, const char *command)
177 {
178   char *av[5];
179   int ac = 0;
180   char *command2 = (char *) malloc (strlen (command) + 6);
181   memcpy (command2, "exec ", 5);
182   memcpy (command2 + 5, command, strlen (command) + 1);
183
184   /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
185   av [ac++] = (char *) shell;
186   av [ac++] = "-c";
187   av [ac++] = command2;
188   av [ac]   = 0;
189
190   execvp (av[0], av);                   /* shouldn't return. */
191
192   {
193     char buf [512];
194     sprintf (buf, "%s: execvp(\"%s\") failed", progname, av[0]);
195     perror (buf);
196     fflush(stderr);
197     fflush(stdout);
198     exit (1);   /* Note that this only exits a child fork.  */
199   }
200 }
201
202 #else  /* VMS */
203
204 static void
205 exec_vms_command (const char *command)
206 {
207   system (command);
208   fflush (stderr);
209   fflush (stdout);
210   exit (1);     /* Note that this only exits a child fork.  */
211 }
212
213 #endif /* !VMS */
214
215
216 static void
217 exec_screenhack (saver_info *si, const char *command)
218 {
219   /* I don't believe what a sorry excuse for an operating system UNIX is!
220
221      - I want to spawn a process.
222      - I want to know it's pid so that I can kill it.
223      - I would like to receive a message when it dies of natural causes.
224      - I want the spawned process to have user-specified arguments.
225
226      If shell metacharacters are present (wildcards, backquotes, etc), the
227      only way to parse those arguments is to run a shell to do the parsing
228      for you.
229
230      And the only way to know the pid of the process is to fork() and exec()
231      it in the spawned side of the fork.
232
233      But if you're running a shell to parse your arguments, this gives you
234      the pid of the *shell*, not the pid of the *process* that you're
235      actually interested in, which is an *inferior* of the shell.  This also
236      means that the SIGCHLD you get applies to the shell, not its inferior.
237      (Why isn't that sufficient?  I don't remember any more, but it turns
238      out that it isn't.)
239
240      So, the only solution, when metacharacters are present, is to force the
241      shell to exec() its inferior.  What a fucking hack!  We prepend "exec "
242      to the command string, and hope it doesn't contain unquoted semicolons
243      or ampersands (we don't search for them, because we don't want to
244      prohibit their use in quoted strings (messages, for example) and parsing
245      out the various quote characters is too much of a pain.)
246
247      (Actually, Clint Wong <clint@jts.com> points out that process groups
248      might be used to take care of this problem; this may be worth considering
249      some day, except that, 1: this code works now, so why fix it, and 2: from
250      what I've seen in Emacs, dealing with process groups isn't especially
251      portable.)
252    */
253   saver_preferences *p = &si->prefs;
254
255 #ifndef VMS
256   Bool hairy_p = !!strpbrk (command, "*?$&!<>[];`'\\\"");
257
258   if (p->verbose_p)
259     printf ("%s: spawning \"%s\" in pid %lu%s.\n",
260             progname, command, (unsigned long) getpid (),
261             (hairy_p ? " (via shell)" : ""));
262
263   if (hairy_p)
264     /* If it contains any shell metacharacters, do it the hard way,
265        and fork a shell to parse the arguments for us. */
266     exec_complex_command (p->shell, command);
267   else
268     /* Otherwise, we can just exec the program directly. */
269     exec_simple_command (command);
270
271 #else /* VMS */
272   if (p->verbose_p)
273     printf ("%s: spawning \"%s\" in pid %lu.\n", progname, command, getpid());
274   exec_vms_command (command);
275 #endif /* VMS */
276
277   abort();      /* that shouldn't have returned. */
278 }
279
280
281 \f
282 /* Management of child processes, and de-zombification.
283  */
284
285 enum job_status {
286   job_running,  /* the process is still alive */
287   job_stopped,  /* we have sent it a STOP signal */
288   job_killed,   /* we have sent it a TERM signal */
289   job_dead      /* we have wait()ed for it, and it's dead -- this state only
290                    occurs so that we can avoid calling free() from a signal
291                    handler.  Shortly after going into this state, the list
292                    element will be removed. */
293 };
294
295 struct screenhack_job {
296   char *name;
297   pid_t pid;
298   enum job_status status;
299   struct screenhack_job *next;
300 };
301
302 static struct screenhack_job *jobs = 0;
303
304 /* for debugging -- nothing calls this, but it's useful to invoke from gdb. */
305 void
306 show_job_list (void)
307 {
308   struct screenhack_job *job;
309   fprintf(stderr, "%s: job list:\n", progname);
310   for (job = jobs; job; job = job->next)
311     fprintf (stderr, "  %5ld: (%s) %s\n",
312              (long) job->pid,
313              (job->status == job_running ? "running" :
314               job->status == job_stopped ? "stopped" :
315               job->status == job_killed  ? " killed" :
316               job->status == job_dead    ? "   dead" : "    ???"),
317              job->name);
318   fprintf (stderr, "\n");
319 }
320
321
322 static void clean_job_list (void);
323
324 static struct screenhack_job *
325 make_job (pid_t pid, const char *cmd)
326 {
327   struct screenhack_job *job = (struct screenhack_job *) malloc (sizeof(*job));
328
329   static char name [1024];
330   const char *in = cmd;
331   char *out = name;
332
333   clean_job_list();
334
335   while (isspace(*in)) in++;            /* skip whitespace */
336   while (!isspace(*in) && *in != ':')
337     *out++ = *in++;                     /* snarf first token */
338   while (isspace(*in)) in++;            /* skip whitespace */
339   if (*in == ':')                       /* token was a visual name; skip it. */
340     {
341       in++;
342       out = name;
343       while (isspace(*in)) in++;                /* skip whitespace */
344       while (!isspace(*in)) *out++ = *in++;     /* snarf first token */
345     }
346   *out = 0;
347
348   job->name = strdup(name);
349   job->pid = pid;
350   job->status = job_running;
351   job->next = jobs;
352   jobs = job;
353
354   return jobs;
355 }
356
357
358 static void
359 free_job (struct screenhack_job *job)
360 {
361   if (!job)
362     return;
363   else if (job == jobs)
364     jobs = jobs->next;
365   else
366     {
367       struct screenhack_job *job2, *prev;
368       for (prev = 0, job2 = jobs;
369            job2;
370            prev = job2, job2 = job2->next)
371         if (job2 == job)
372           {
373             prev->next = job->next;
374             break;
375           }
376     }
377   free(job->name);
378   free(job);
379 }
380
381
382 /* Cleans out dead jobs from the jobs list -- this must only be called
383    from the main thread, not from a signal handler. 
384  */
385 static void
386 clean_job_list (void)
387 {
388   struct screenhack_job *job, *prev, *next;
389   for (prev = 0, job = jobs, next = (job ? job->next : 0);
390        job;
391        prev = job, job = next, next = (job ? job->next : 0))
392     {
393       if (job->status == job_dead)
394         {
395           if (prev)
396             prev->next = next;
397           free_job (job);
398           job = prev;
399         }
400     }
401 }
402
403
404 static struct screenhack_job *
405 find_job (pid_t pid)
406 {
407   struct screenhack_job *job;
408   for (job = jobs; job; job = job->next)
409     if (job->pid == pid)
410       return job;
411   return 0;
412 }
413
414 static void await_dying_children (saver_info *si);
415 #ifndef VMS
416 static void describe_dead_child (saver_info *, pid_t, int wait_status);
417 #endif
418
419
420 /* Semaphore to temporarily turn the SIGCHLD handler into a no-op.
421    Don't alter this directly -- use block_sigchld() / unblock_sigchld().
422  */
423 static int block_sigchld_handler = 0;
424
425
426 static void
427 block_sigchld (void)
428 {
429 #ifdef HAVE_SIGACTION
430   sigset_t child_set;
431   sigemptyset (&child_set);
432   sigaddset (&child_set, SIGCHLD);
433   sigprocmask (SIG_BLOCK, &child_set, 0);
434 #endif /* HAVE_SIGACTION */
435
436   block_sigchld_handler++;
437 }
438
439 static void
440 unblock_sigchld (void)
441 {
442 #ifdef HAVE_SIGACTION
443   sigset_t child_set;
444   sigemptyset(&child_set);
445   sigaddset(&child_set, SIGCHLD);
446   sigprocmask(SIG_UNBLOCK, &child_set, 0);
447 #endif /* HAVE_SIGACTION */
448
449   block_sigchld_handler--;
450 }
451
452 static int
453 kill_job (saver_info *si, pid_t pid, int signal)
454 {
455   saver_preferences *p = &si->prefs;
456   struct screenhack_job *job;
457   int status = -1;
458
459   clean_job_list();
460
461   if (block_sigchld_handler)
462     /* This function should not be called from the signal handler. */
463     abort();
464
465   block_sigchld();                      /* we control the horizontal... */
466
467   job = find_job (pid);
468   if (!job ||
469       !job->pid ||
470       job->status == job_killed)
471     {
472       if (p->verbose_p)
473         fprintf (stderr, "%s: no child %ld to signal!\n",
474                  progname, (long) pid);
475       goto DONE;
476     }
477
478   switch (signal) {
479   case SIGTERM: job->status = job_killed;  break;
480 #ifdef SIGSTOP
481     /* #### there must be a way to do this on VMS... */
482   case SIGSTOP: job->status = job_stopped; break;
483   case SIGCONT: job->status = job_running; break;
484 #endif /* SIGSTOP */
485   default: abort();
486   }
487
488 #ifdef SIGSTOP
489   if (p->verbose_p)
490     fprintf (stderr, "%s: %s pid %lu.\n", progname,
491              (signal == SIGTERM ? "killing" :
492               signal == SIGSTOP ? "suspending" :
493               signal == SIGCONT ? "resuming" : "signalling"),
494              (unsigned long) job->pid);
495 #else  /* !SIGSTOP */
496   if (p->verbose_p)
497     fprintf (stderr, "%s: %s pid %lu.\n", progname, "killing",
498              (unsigned long) job->pid);
499 #endif /* !SIGSTOP */
500
501   status = kill (job->pid, signal);
502
503   if (p->verbose_p && status < 0)
504     {
505       if (errno == ESRCH)
506         fprintf (stderr, "%s: child process %lu (%s) was already dead.\n",
507                  progname, job->pid, job->name);
508       else
509         {
510           char buf [1024];
511           sprintf (buf, "%s: couldn't kill child process %lu (%s)",
512                    progname, job->pid, job->name);
513           perror (buf);
514         }
515     }
516
517   await_dying_children (si);
518
519  DONE:
520   unblock_sigchld();
521   if (block_sigchld_handler < 0)
522     abort();
523
524   clean_job_list();
525   return status;
526 }
527
528
529 #ifdef SIGCHLD
530 static RETSIGTYPE
531 sigchld_handler (int sig)
532 {
533   saver_info *si = global_si_kludge;    /* I hate C so much... */
534
535   if (si->prefs.debug_p)
536     fprintf(stderr, "%s: got SIGCHLD%s\n", progname,
537             (block_sigchld_handler ? " (blocked)" : ""));
538
539   if (block_sigchld_handler < 0)
540     abort();
541   else if (block_sigchld_handler == 0)
542     {
543       block_sigchld();
544       await_dying_children (si);
545       unblock_sigchld();
546     }
547
548   init_sigchld();
549 }
550 #endif /* SIGCHLD */
551
552
553 #ifndef VMS
554 static void
555 await_dying_children (saver_info *si)
556 {
557   while (1)
558     {
559       int wait_status = 0;
560       pid_t kid;
561
562       errno = 0;
563       kid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED);
564
565       if (si->prefs.debug_p)
566         {
567           if (kid < 0 && errno)
568             fprintf (stderr, "%s: waitpid(-1) ==> %ld (%d)\n", progname,
569                      (long) kid, errno);
570           else
571             fprintf (stderr, "%s: waitpid(-1) ==> %ld\n", progname,
572                      (long) kid);
573         }
574
575       /* 0 means no more children to reap.
576          -1 means error -- except "interrupted system call" isn't a "real"
577          error, so if we get that, we should just try again. */
578       if (kid == 0 ||
579           (kid < 0 && errno != EINTR))
580         break;
581
582       describe_dead_child (si, kid, wait_status);
583     }
584 }
585
586
587 static void
588 describe_dead_child (saver_info *si, pid_t kid, int wait_status)
589 {
590   int i;
591   saver_preferences *p = &si->prefs;
592   struct screenhack_job *job = find_job (kid);
593   const char *name = job ? job->name : "<unknown>";
594
595   if (WIFEXITED (wait_status))
596     {
597       int exit_status = WEXITSTATUS (wait_status);
598
599       /* Treat exit code as a signed 8-bit quantity. */
600       if (exit_status & 0x80) exit_status |= ~0xFF;
601
602       /* One might assume that exiting with non-0 means something went wrong.
603          But that loser xswarm exits with the code that it was killed with, so
604          it *always* exits abnormally.  Treat abnormal exits as "normal" (don't
605          mention them) if we've just killed the subprocess.  But mention them
606          if they happen on their own.
607        */
608       if (!job ||
609           (exit_status != 0 &&
610            (p->verbose_p || job->status != job_killed)))
611         fprintf (stderr,
612                  "%s: child pid %lu (%s) exited abnormally (code %d).\n",
613                  progname, (unsigned long) kid, name, exit_status);
614       else if (p->verbose_p)
615         printf ("%s: child pid %lu (%s) exited normally.\n",
616                 progname, (unsigned long) kid, name);
617
618       if (job)
619         job->status = job_dead;
620     }
621   else if (WIFSIGNALED (wait_status))
622     {
623       if (p->verbose_p ||
624           !job ||
625           job->status != job_killed ||
626           WTERMSIG (wait_status) != SIGTERM)
627         fprintf (stderr, "%s: child pid %lu (%s) terminated with %s.\n",
628                  progname, (unsigned long) kid, name,
629                  signal_name (WTERMSIG(wait_status)));
630
631       if (job)
632         job->status = job_dead;
633     }
634   else if (WIFSTOPPED (wait_status))
635     {
636       if (p->verbose_p)
637         fprintf (stderr, "%s: child pid %lu (%s) stopped with %s.\n",
638                  progname, (unsigned long) kid, name,
639                  signal_name (WSTOPSIG (wait_status)));
640
641       if (job)
642         job->status = job_stopped;
643     }
644   else
645     {
646       fprintf (stderr, "%s: child pid %lu (%s) died in a mysterious way!",
647                progname, (unsigned long) kid, name);
648       if (job)
649         job->status = job_dead;
650     }
651
652   /* Clear out the pid so that screenhack_running_p() knows it's dead.
653    */
654   if (!job || job->status == job_dead)
655     for (i = 0; i < si->nscreens; i++)
656       {
657         saver_screen_info *ssi = &si->screens[i];
658         if (kid == ssi->pid)
659           ssi->pid = 0;
660       }
661 }
662
663 #else  /* VMS */
664 static void await_dying_children (saver_info *si) { return; }
665 #endif /* VMS */
666
667
668 void
669 init_sigchld (void)
670 {
671 #ifdef SIGCHLD
672
673 # ifdef HAVE_SIGACTION  /* Thanks to Tom Kelly <tom@ancilla.toronto.on.ca> */
674
675   static Bool sigchld_initialized_p = 0;
676   if (!sigchld_initialized_p)
677     {
678       struct sigaction action, old;
679
680       action.sa_handler = sigchld_handler;
681       sigemptyset(&action.sa_mask);
682       action.sa_flags = 0;
683
684       if (sigaction(SIGCHLD, &action, &old) < 0)
685         {
686           char buf [255];
687           sprintf (buf, "%s: couldn't catch SIGCHLD", progname);
688           perror (buf);
689         }
690       sigchld_initialized_p = True;
691     }
692
693 # else  /* !HAVE_SIGACTION */
694
695   if (((long) signal (SIGCHLD, sigchld_handler)) == -1L)
696     {
697       char buf [255];
698       sprintf (buf, "%s: couldn't catch SIGCHLD", progname);
699       perror (buf);
700     }
701 # endif /* !HAVE_SIGACTION */
702 #endif /* SIGCHLD */
703 }
704
705
706
707 \f
708
709 static Bool
710 select_visual_of_hack (saver_screen_info *ssi, const char *hack)
711 {
712   saver_info *si = ssi->global;
713   saver_preferences *p = &si->prefs;
714   Bool selected;
715   static char vis [1024];
716   const char *in = hack;
717   char *out = vis;
718   while (isspace(*in)) in++;            /* skip whitespace */
719   while (!isspace(*in) && *in != ':')
720     *out++ = *in++;                     /* snarf first token */
721   while (isspace(*in)) in++;            /* skip whitespace */
722   *out = 0;
723
724   if (*in == ':')
725     selected = select_visual(ssi, vis);
726   else
727     selected = select_visual(ssi, 0);
728
729   if (!selected && (p->verbose_p || si->demo_mode_p))
730     {
731       if (*in == ':') in++;
732       while (isspace(*in)) in++;
733       fprintf (stderr,
734                (si->demo_mode_p
735                 ? "%s: warning, no \"%s\" visual for \"%s\".\n"
736                 : "%s: no \"%s\" visual; skipping \"%s\".\n"),
737                progname, (vis ? vis : "???"), in);
738     }
739
740   return selected;
741 }
742
743
744 static void
745 spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
746 {
747   saver_info *si = ssi->global;
748   saver_preferences *p = &si->prefs;
749   raise_window (si, first_time_p, True, False);
750   XFlush (si->dpy);
751
752   if (p->screenhacks_count || si->demo_mode_p)
753     {
754       char *hack;
755       pid_t forked;
756       char buf [255];
757       int new_hack;
758
759       if (si->demo_mode_p)
760         {
761           hack = si->demo_hack;
762
763           /* Ignore visual-selection failure if in demo mode. */
764           (void) select_visual_of_hack (ssi, hack);
765         }
766       else
767         {
768           int retry_count = 0;
769
770         AGAIN:
771           if (p->screenhacks_count == 1)
772             new_hack = 0;
773           else if (si->next_mode_p == 1)
774             new_hack = (ssi->current_hack + 1) % p->screenhacks_count;
775           else if (si->next_mode_p == 2)
776             new_hack = ((ssi->current_hack + p->screenhacks_count - 1)
777                         % p->screenhacks_count);
778           else
779             while ((new_hack = random () % p->screenhacks_count)
780                    == ssi->current_hack)
781               ;
782           ssi->current_hack = new_hack;
783           hack = p->screenhacks[ssi->current_hack];
784
785           if (!select_visual_of_hack (ssi, hack))
786             {
787               if (++retry_count > (p->screenhacks_count*4))
788                 {
789                   /* Uh, oops.  Odds are, there are no suitable visuals,
790                      and we're looping.  Give up.  (This is totally lame,
791                      what we should do is make a list of suitable hacks at
792                      the beginning, then only loop over them.)
793                   */
794                   if (p->verbose_p)
795                     fprintf(stderr,
796                             "%s: no suitable visuals for these programs.\n",
797                             progname);
798                   return;
799                 }
800               else
801                 goto AGAIN;
802             }
803         }
804       si->next_mode_p = 0;
805
806
807       /* If there's a visual description on the front of the command, nuke it.
808        */
809       {
810         char *in = hack;
811         while (isspace(*in)) in++;                      /* skip whitespace */
812         hack = in;
813         while (!isspace(*in) && *in != ':') in++;       /* snarf first token */
814         while (isspace(*in)) in++;                      /* skip whitespace */
815         if (*in == ':')
816           {
817             in++;
818             while (isspace(*in)) in++;
819             hack = in;
820           }
821       }
822
823       switch ((int) (forked = fork ()))
824         {
825         case -1:
826           sprintf (buf, "%s: couldn't fork", progname);
827           perror (buf);
828           restore_real_vroot (si);
829           saver_exit (si, 1);
830
831         case 0:
832           close (ConnectionNumber (si->dpy));   /* close display fd */
833           nice_subproc (p->nice_inferior);      /* change process priority */
834           hack_subproc_environment (ssi);       /* set $DISPLAY */
835           exec_screenhack (si, hack);           /* this does not return */
836           abort();
837           break;
838
839         default:
840           ssi->pid = forked;
841           (void) make_job (forked, hack);
842           break;
843         }
844     }
845 }
846
847
848 void
849 spawn_screenhack (saver_info *si, Bool first_time_p)
850 {
851   int i;
852
853   if (!monitor_powered_on_p (si))
854     {
855       if (si->prefs.verbose_p)
856         printf ("%s: server reports that monitor has powered down; "
857                 "not launching a new hack.\n", progname);
858       return;
859     }
860
861   for (i = 0; i < si->nscreens; i++)
862     {
863       saver_screen_info *ssi = &si->screens[i];
864       spawn_screenhack_1 (ssi, first_time_p);
865     }
866 }
867
868
869 void
870 kill_screenhack (saver_info *si)
871 {
872   int i;
873   for (i = 0; i < si->nscreens; i++)
874     {
875       saver_screen_info *ssi = &si->screens[i];
876       if (ssi->pid)
877         kill_job (si, ssi->pid, SIGTERM);
878       ssi->pid = 0;
879     }
880 }
881
882
883 void
884 suspend_screenhack (saver_info *si, Bool suspend_p)
885 {
886 #ifdef SIGSTOP  /* older VMS doesn't have it... */
887   int i;
888   for (i = 0; i < si->nscreens; i++)
889     {
890       saver_screen_info *ssi = &si->screens[i];
891       if (ssi->pid)
892         kill_job (si, ssi->pid, (suspend_p ? SIGSTOP : SIGCONT));
893     }
894 #endif /* SIGSTOP */
895 }
896
897
898 /* Called when we're exiting abnormally, to kill off the subproc. */
899 void
900 emergency_kill_subproc (saver_info *si)
901 {
902   int i;
903 #ifdef SIGCHLD
904   signal (SIGCHLD, SIG_IGN);
905 #endif /* SIGCHLD */
906
907   for (i = 0; i < si->nscreens; i++)
908     {
909       saver_screen_info *ssi = &si->screens[i];
910       if (ssi->pid)
911         {
912           kill_job (si, ssi->pid, SIGTERM);
913           ssi->pid = 0;
914         }
915     }
916 }
917
918 Bool
919 screenhack_running_p (saver_info *si)
920 {
921   Bool result = True;
922   int i;
923   for (i = 0; i < si->nscreens; i++)
924     {
925       saver_screen_info *ssi = &si->screens[i];
926       if (!ssi->pid)
927         result = False;
928     }
929   return result;
930 }
931
932 \f
933 /* Restarting the xscreensaver process from scratch. */
934
935 static char **saved_argv;
936
937 void
938 save_argv (int argc, char **argv)
939 {
940   saved_argv = (char **) malloc ((argc + 2) * sizeof (char *));
941   saved_argv [argc] = 0;
942   while (argc--)
943     {
944       int i = strlen (argv [argc]) + 1;
945       saved_argv [argc] = (char *) malloc (i);
946       memcpy (saved_argv [argc], argv [argc], i);
947     }
948 }
949
950 void
951 restart_process (saver_info *si)
952 {
953   fflush (real_stdout);
954   fflush (real_stderr);
955   execvp (saved_argv [0], saved_argv);  /* shouldn't return */
956   {
957     char buf [512];
958     sprintf (buf, "%s: could not restart process", progname);
959     perror(buf);
960     fflush(stderr);
961   }
962 }
963
964 /* Like restart_process(), but ensures that when it restarts,
965    it comes up in demo-mode. */
966 void
967 demo_mode_restart_process (saver_info *si)
968 {
969   int i;
970   for (i = 0; saved_argv [i]; i++);
971   /* add the -initial-demo-mode switch; save_argv() left room for this. */
972   saved_argv [i] = "-initial-demo-mode";
973   saved_argv [i+1] = 0;
974   restart_process (si);         /* shouldn't return */
975   saved_argv [i] = 0;
976   XBell(si->dpy, 0);
977 }
978
979 static void
980 hack_subproc_environment (saver_screen_info *ssi)
981 {
982   /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
983      the spawned processes inherit is correct.  First, it must be on the same
984      host and display as the value of -display passed in on our command line
985      (which is not necessarily the same as what our $DISPLAY variable is.)
986      Second, the screen number in the $DISPLAY passed to the subprocess should
987      be the screen on which this particular hack is running -- not the display
988      specification which the driver itself is using, since the driver ignores
989      its screen number and manages all existing screens.
990    */
991   saver_info *si = ssi->global;
992   const char *odpy = DisplayString (si->dpy);
993   char *ndpy = (char *) malloc(strlen(odpy) + 20);
994   int screen_number;
995   char *s;
996
997   for (screen_number = 0; screen_number < si->nscreens; screen_number++)
998     if (ssi == &si->screens[screen_number])
999       break;
1000   if (screen_number >= si->nscreens) abort();
1001
1002   strcpy (ndpy, "DISPLAY=");
1003   s = ndpy + strlen(ndpy);
1004   strcpy (s, odpy);
1005
1006   while (*s && *s != ':') s++;                  /* skip to colon */
1007   while (*s == ':') s++;                        /* skip over colons */
1008   while (isdigit(*s)) s++;                      /* skip over dpy number */
1009   while (*s == '.') s++;                        /* skip over dot */
1010   if (s[-1] != '.') *s++ = '.';                 /* put on a dot */
1011   sprintf(s, "%d", screen_number);              /* put on screen number */
1012
1013   /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
1014      any more, right?  It's not Posix, but everyone seems to have it. */
1015 #ifdef HAVE_PUTENV
1016   if (putenv (ndpy))
1017     abort ();
1018 #endif /* HAVE_PUTENV */
1019 }
1020
1021
1022 void
1023 hack_environment (saver_info *si)
1024 {
1025 #if defined(HAVE_PUTENV) && defined(DEFAULT_PATH_PREFIX)
1026   static const char *def_path = DEFAULT_PATH_PREFIX;
1027   if (def_path && *def_path)
1028     {
1029       const char *opath = getenv("PATH");
1030       char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
1031       strcpy (npath, "PATH=");
1032       strcat (npath, def_path);
1033       strcat (npath, ":");
1034       strcat (npath, opath);
1035
1036       if (putenv (npath))
1037         abort ();
1038     }
1039 #endif /* HAVE_PUTENV && DEFAULT_PATH_PREFIX */
1040 }
1041
1042
1043 \f
1044 /* Change the uid/gid of the screensaver process, so that it is safe for it
1045    to run setuid root (which it needs to do on some systems to read the 
1046    encrypted passwords from the passwd file.)
1047
1048    hack_uid() is run before opening the X connection, so that XAuth works.
1049    hack_uid_warn() is called after the connection is opened and the command
1050    line arguments are parsed, so that the messages from hack_uid() get 
1051    printed after we know whether we're in `verbose' mode.
1052  */
1053
1054 #ifndef NO_SETUID
1055
1056 static int hack_uid_errno;
1057 static char hack_uid_buf [255], *hack_uid_error;
1058
1059 void
1060 hack_uid (saver_info *si)
1061 {
1062
1063   /* If we've been run as setuid or setgid to someone else (most likely root)
1064      turn off the extra permissions so that random user-specified programs
1065      don't get special privileges.  (On some systems it might be necessary
1066      to install this as setuid root in order to read the passwd file to
1067      implement lock-mode...)
1068   */
1069   setgid (getgid ());
1070   setuid (getuid ());
1071
1072   hack_uid_errno = 0;
1073   hack_uid_error = 0;
1074
1075   /* If we're being run as root (as from xdm) then switch the user id
1076      to something safe. */
1077   if (getuid () == 0)
1078     {
1079       struct passwd *p;
1080       /* Locking can't work when running as root, because we have no way of
1081          knowing what the user id of the logged in user is (so we don't know
1082          whose password to prompt for.)
1083        */
1084       si->locking_disabled_p = True;
1085       si->nolock_reason = "running as root";
1086       p = getpwnam ("nobody");
1087       if (! p) p = getpwnam ("noaccess");
1088       if (! p) p = getpwnam ("daemon");
1089       if (! p) p = getpwnam ("bin");
1090       if (! p) p = getpwnam ("sys");
1091       if (! p)
1092         {
1093           hack_uid_error = "couldn't find safe uid; running as root.";
1094           hack_uid_errno = -1;
1095         }
1096       else
1097         {
1098           struct group *g = getgrgid (p->pw_gid);
1099           hack_uid_error = hack_uid_buf;
1100           sprintf (hack_uid_error, "changing uid/gid to %s/%s (%ld/%ld).",
1101                    p->pw_name, (g ? g->gr_name : "???"),
1102                    (long) p->pw_uid, (long) p->pw_gid);
1103
1104           /* Change the gid to be a safe one.  If we can't do that, then
1105              print a warning.  We change the gid before the uid so that we
1106              change the gid while still root. */
1107           if (setgid (p->pw_gid) != 0)
1108             {
1109               hack_uid_errno = errno;
1110               sprintf (hack_uid_error, "couldn't set gid to %s (%ld)",
1111                        (g ? g->gr_name : "???"), (long) p->pw_gid);
1112             }
1113
1114           /* Now change the uid to be a safe one. */
1115           if (setuid (p->pw_uid) != 0)
1116             {
1117               hack_uid_errno = errno;
1118               sprintf (hack_uid_error, "couldn't set uid to %s (%ld)",
1119                        p->pw_name, (long) p->pw_uid);
1120             }
1121         }
1122     }
1123 # ifndef NO_LOCKING
1124  else   /* disable locking if already being run as "someone else" */
1125    {
1126      struct passwd *p = getpwuid (getuid ());
1127      if (!p ||
1128          !strcmp (p->pw_name, "root") ||
1129          !strcmp (p->pw_name, "nobody") ||
1130          !strcmp (p->pw_name, "noaccess") ||
1131          !strcmp (p->pw_name, "daemon") ||
1132          !strcmp (p->pw_name, "bin") ||
1133          !strcmp (p->pw_name, "sys"))
1134        {
1135          si->locking_disabled_p = True;
1136          si->nolock_reason = hack_uid_buf;
1137          sprintf (si->nolock_reason, "running as %s", p->pw_name);
1138        }
1139    }
1140 # endif /* !NO_LOCKING */
1141 }
1142
1143 void
1144 hack_uid_warn (saver_info *si)
1145 {
1146   saver_preferences *p = &si->prefs;
1147
1148   if (! hack_uid_error)
1149     ;
1150   else if (hack_uid_errno == 0)
1151     {
1152       if (p->verbose_p)
1153         printf ("%s: %s\n", progname, hack_uid_error);
1154     }
1155   else
1156     {
1157       char buf [255];
1158       sprintf (buf, "%s: %s", progname, hack_uid_error);
1159       if (hack_uid_errno == -1)
1160         fprintf (stderr, "%s\n", buf);
1161       else
1162         {
1163           errno = hack_uid_errno;
1164           perror (buf);
1165         }
1166     }
1167 }
1168
1169 #endif /* !NO_SETUID */