97904499981d1eeb69f1deebd416e0084144f52a
[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_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_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   for (i = 0; i < si->nscreens; i++)
853     {
854       saver_screen_info *ssi = &si->screens[i];
855       spawn_screenhack_1 (ssi, first_time_p);
856     }
857 }
858
859
860 void
861 kill_screenhack (saver_info *si)
862 {
863   int i;
864   for (i = 0; i < si->nscreens; i++)
865     {
866       saver_screen_info *ssi = &si->screens[i];
867       if (ssi->pid)
868         kill_job (si, ssi->pid, SIGTERM);
869       ssi->pid = 0;
870     }
871 }
872
873
874 void
875 suspend_screenhack (saver_info *si, Bool suspend_p)
876 {
877 #ifdef SIGSTOP  /* older VMS doesn't have it... */
878   int i;
879   for (i = 0; i < si->nscreens; i++)
880     {
881       saver_screen_info *ssi = &si->screens[i];
882       if (ssi->pid)
883         kill_job (si, ssi->pid, (suspend_p ? SIGSTOP : SIGCONT));
884     }
885 #endif /* SIGSTOP */
886 }
887
888
889 /* Called when we're exiting abnormally, to kill off the subproc. */
890 void
891 emergency_kill_subproc (saver_info *si)
892 {
893   int i;
894 #ifdef SIGCHLD
895   signal (SIGCHLD, SIG_IGN);
896 #endif /* SIGCHLD */
897
898   for (i = 0; i < si->nscreens; i++)
899     {
900       saver_screen_info *ssi = &si->screens[i];
901       if (ssi->pid)
902         {
903           kill_job (si, ssi->pid, SIGTERM);
904           ssi->pid = 0;
905         }
906     }
907 }
908
909 Bool
910 screenhack_running_p (saver_info *si)
911 {
912   Bool result = True;
913   int i;
914   for (i = 0; i < si->nscreens; i++)
915     {
916       saver_screen_info *ssi = &si->screens[i];
917       if (!ssi->pid)
918         result = False;
919     }
920   return result;
921 }
922
923 \f
924 /* Restarting the xscreensaver process from scratch. */
925
926 static char **saved_argv;
927
928 void
929 save_argv (int argc, char **argv)
930 {
931   saved_argv = (char **) malloc ((argc + 2) * sizeof (char *));
932   saved_argv [argc] = 0;
933   while (argc--)
934     {
935       int i = strlen (argv [argc]) + 1;
936       saved_argv [argc] = (char *) malloc (i);
937       memcpy (saved_argv [argc], argv [argc], i);
938     }
939 }
940
941 void
942 restart_process (saver_info *si)
943 {
944   fflush (real_stdout);
945   fflush (real_stderr);
946   execvp (saved_argv [0], saved_argv);  /* shouldn't return */
947   {
948     char buf [512];
949     sprintf (buf, "%s: could not restart process", progname);
950     perror(buf);
951     fflush(stderr);
952   }
953 }
954
955 /* Like restart_process(), but ensures that when it restarts,
956    it comes up in demo-mode. */
957 void
958 demo_mode_restart_process (saver_info *si)
959 {
960   int i;
961   for (i = 0; saved_argv [i]; i++);
962   /* add the -initial-demo-mode switch; save_argv() left room for this. */
963   saved_argv [i] = "-initial-demo-mode";
964   saved_argv [i+1] = 0;
965   restart_process (si);         /* shouldn't return */
966   saved_argv [i] = 0;
967   XBell(si->dpy, 0);
968 }
969
970 static void
971 hack_environment (saver_screen_info *ssi)
972 {
973   /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
974      the spawned processes inherit is correct.  First, it must be on the same
975      host and display as the value of -display passed in on our command line
976      (which is not necessarily the same as what our $DISPLAY variable is.)
977      Second, the screen number in the $DISPLAY passed to the subprocess should
978      be the screen on which this particular hack is running -- not the display
979      specification which the driver itself is using, since the driver ignores
980      its screen number and manages all existing screens.
981    */
982   saver_info *si = ssi->global;
983   const char *odpy = DisplayString (si->dpy);
984   char *ndpy = (char *) malloc(strlen(odpy) + 20);
985   int screen_number;
986   char *s;
987
988   for (screen_number = 0; screen_number < si->nscreens; screen_number++)
989     if (ssi == &si->screens[screen_number])
990       break;
991   if (screen_number >= si->nscreens) abort();
992
993   strcpy (ndpy, "DISPLAY=");
994   s = ndpy + strlen(ndpy);
995   strcpy (s, odpy);
996
997   while (*s && *s != ':') s++;                  /* skip to colon */
998   while (*s == ':') s++;                        /* skip over colons */
999   while (isdigit(*s)) s++;                      /* skip over dpy number */
1000   while (*s == '.') s++;                        /* skip over dot */
1001   if (s[-1] != '.') *s++ = '.';                 /* put on a dot */
1002   sprintf(s, "%d", screen_number);              /* put on screen number */
1003
1004   /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
1005      any more, right?  It's not Posix, but everyone seems to have it. */
1006 #ifdef HAVE_PUTENV
1007   if (putenv (ndpy))
1008     abort ();
1009 #endif /* HAVE_PUTENV */
1010 }
1011
1012 \f
1013 /* Change the uid/gid of the screensaver process, so that it is safe for it
1014    to run setuid root (which it needs to do on some systems to read the 
1015    encrypted passwords from the passwd file.)
1016
1017    hack_uid() is run before opening the X connection, so that XAuth works.
1018    hack_uid_warn() is called after the connection is opened and the command
1019    line arguments are parsed, so that the messages from hack_uid() get 
1020    printed after we know whether we're in `verbose' mode.
1021  */
1022
1023 #ifndef NO_SETUID
1024
1025 static int hack_uid_errno;
1026 static char hack_uid_buf [255], *hack_uid_error;
1027
1028 void
1029 hack_uid (saver_info *si)
1030 {
1031
1032   /* If we've been run as setuid or setgid to someone else (most likely root)
1033      turn off the extra permissions so that random user-specified programs
1034      don't get special privileges.  (On some systems it might be necessary
1035      to install this as setuid root in order to read the passwd file to
1036      implement lock-mode...)
1037   */
1038   setgid (getgid ());
1039   setuid (getuid ());
1040
1041   hack_uid_errno = 0;
1042   hack_uid_error = 0;
1043
1044   /* If we're being run as root (as from xdm) then switch the user id
1045      to something safe. */
1046   if (getuid () == 0)
1047     {
1048       struct passwd *p;
1049       /* Locking can't work when running as root, because we have no way of
1050          knowing what the user id of the logged in user is (so we don't know
1051          whose password to prompt for.)
1052        */
1053       si->locking_disabled_p = True;
1054       si->nolock_reason = "running as root";
1055       p = getpwnam ("nobody");
1056       if (! p) p = getpwnam ("noaccess");
1057       if (! p) p = getpwnam ("daemon");
1058       if (! p) p = getpwnam ("bin");
1059       if (! p) p = getpwnam ("sys");
1060       if (! p)
1061         {
1062           hack_uid_error = "couldn't find safe uid; running as root.";
1063           hack_uid_errno = -1;
1064         }
1065       else
1066         {
1067           struct group *g = getgrgid (p->pw_gid);
1068           hack_uid_error = hack_uid_buf;
1069           sprintf (hack_uid_error, "changing uid/gid to %s/%s (%ld/%ld).",
1070                    p->pw_name, (g ? g->gr_name : "???"),
1071                    (long) p->pw_uid, (long) p->pw_gid);
1072
1073           /* Change the gid to be a safe one.  If we can't do that, then
1074              print a warning.  We change the gid before the uid so that we
1075              change the gid while still root. */
1076           if (setgid (p->pw_gid) != 0)
1077             {
1078               hack_uid_errno = errno;
1079               sprintf (hack_uid_error, "couldn't set gid to %s (%ld)",
1080                        (g ? g->gr_name : "???"), (long) p->pw_gid);
1081             }
1082
1083           /* Now change the uid to be a safe one. */
1084           if (setuid (p->pw_uid) != 0)
1085             {
1086               hack_uid_errno = errno;
1087               sprintf (hack_uid_error, "couldn't set uid to %s (%ld)",
1088                        p->pw_name, (long) p->pw_uid);
1089             }
1090         }
1091     }
1092 # ifndef NO_LOCKING
1093  else   /* disable locking if already being run as "someone else" */
1094    {
1095      struct passwd *p = getpwuid (getuid ());
1096      if (!p ||
1097          !strcmp (p->pw_name, "root") ||
1098          !strcmp (p->pw_name, "nobody") ||
1099          !strcmp (p->pw_name, "noaccess") ||
1100          !strcmp (p->pw_name, "daemon") ||
1101          !strcmp (p->pw_name, "bin") ||
1102          !strcmp (p->pw_name, "sys"))
1103        {
1104          si->locking_disabled_p = True;
1105          si->nolock_reason = hack_uid_buf;
1106          sprintf (si->nolock_reason, "running as %s", p->pw_name);
1107        }
1108    }
1109 # endif /* !NO_LOCKING */
1110 }
1111
1112 void
1113 hack_uid_warn (saver_info *si)
1114 {
1115   saver_preferences *p = &si->prefs;
1116
1117   if (! hack_uid_error)
1118     ;
1119   else if (hack_uid_errno == 0)
1120     {
1121       if (p->verbose_p)
1122         printf ("%s: %s\n", progname, hack_uid_error);
1123     }
1124   else
1125     {
1126       char buf [255];
1127       sprintf (buf, "%s: %s", progname, hack_uid_error);
1128       if (hack_uid_errno == -1)
1129         fprintf (stderr, "%s\n", buf);
1130       else
1131         {
1132           errno = hack_uid_errno;
1133           perror (buf);
1134         }
1135     }
1136 }
1137
1138 #endif /* !NO_SETUID */