9070b81bb829537b440baa7e0d492011f9559ec0
[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 #ifdef DEBUG
305 static 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 #endif
321
322
323 static void clean_job_list (void);
324
325 static struct screenhack_job *
326 make_job (pid_t pid, const char *cmd)
327 {
328   struct screenhack_job *job = (struct screenhack_job *) malloc (sizeof(*job));
329
330   static char name [1024];
331   const char *in = cmd;
332   char *out = name;
333
334   clean_job_list();
335
336   while (isspace(*in)) in++;            /* skip whitespace */
337   while (!isspace(*in) && *in != ':')
338     *out++ = *in++;                     /* snarf first token */
339   while (isspace(*in)) in++;            /* skip whitespace */
340   if (*in == ':')                       /* token was a visual name; skip it. */
341     {
342       in++;
343       out = name;
344       while (isspace(*in)) in++;                /* skip whitespace */
345       while (!isspace(*in)) *out++ = *in++;     /* snarf first token */
346     }
347   *out = 0;
348
349   job->name = strdup(name);
350   job->pid = pid;
351   job->status = job_running;
352   job->next = jobs;
353   jobs = job;
354
355   return jobs;
356 }
357
358
359 static void
360 free_job (struct screenhack_job *job)
361 {
362   if (!job)
363     return;
364   else if (job == jobs)
365     jobs = jobs->next;
366   else
367     {
368       struct screenhack_job *job2, *prev;
369       for (prev = 0, job2 = jobs;
370            job2;
371            prev = job2, job2 = job2->next)
372         if (job2 == job)
373           {
374             prev->next = job->next;
375             break;
376           }
377     }
378   free(job->name);
379   free(job);
380 }
381
382
383 /* Cleans out dead jobs from the jobs list -- this must only be called
384    from the main thread, not from a signal handler. 
385  */
386 static void
387 clean_job_list (void)
388 {
389   struct screenhack_job *job, *prev, *next;
390   for (prev = 0, job = jobs, next = (job ? job->next : 0);
391        job;
392        prev = job, job = next, next = (job ? job->next : 0))
393     {
394       if (job->status == job_dead)
395         {
396           if (prev)
397             prev->next = next;
398           free_job (job);
399           job = prev;
400         }
401     }
402 }
403
404
405 static struct screenhack_job *
406 find_job (pid_t pid)
407 {
408   struct screenhack_job *job;
409   for (job = jobs; job; job = job->next)
410     if (job->pid == pid)
411       return job;
412   return 0;
413 }
414
415 static void await_dying_children (saver_info *si);
416 #ifndef VMS
417 static void describe_dead_child (saver_info *, pid_t, int wait_status);
418 #endif
419
420
421 /* Semaphore to temporarily turn the SIGCHLD handler into a no-op.
422    Don't alter this directly -- use block_sigchld() / unblock_sigchld().
423  */
424 static int block_sigchld_handler = 0;
425
426
427 static void
428 block_sigchld (void)
429 {
430 #ifdef HAVE_SIGACTION
431   sigset_t child_set;
432   sigemptyset (&child_set);
433   sigaddset (&child_set, SIGCHLD);
434   sigprocmask (SIG_BLOCK, &child_set, 0);
435 #endif /* HAVE_SIGACTION */
436
437   block_sigchld_handler++;
438 }
439
440 static void
441 unblock_sigchld (void)
442 {
443 #ifdef HAVE_SIGACTION
444   sigset_t child_set;
445   sigemptyset(&child_set);
446   sigaddset(&child_set, SIGCHLD);
447   sigprocmask(SIG_UNBLOCK, &child_set, 0);
448 #endif /* HAVE_SIGACTION */
449
450   block_sigchld_handler--;
451 }
452
453 static int
454 kill_job (saver_info *si, pid_t pid, int signal)
455 {
456   saver_preferences *p = &si->prefs;
457   struct screenhack_job *job;
458   int status = -1;
459
460   clean_job_list();
461
462   if (block_sigchld_handler)
463     /* This function should not be called from the signal handler. */
464     abort();
465
466   block_sigchld();                      /* we control the horizontal... */
467
468   job = find_job (pid);
469   if (!job ||
470       !job->pid ||
471       job->status == job_killed)
472     {
473       if (p->verbose_p)
474         fprintf (stderr, "%s: no child %ld to signal!\n",
475                  progname, (long) pid);
476       goto DONE;
477     }
478
479   switch (signal) {
480   case SIGTERM: job->status = job_killed;  break;
481 #ifdef SIGSTOP
482     /* #### there must be a way to do this on VMS... */
483   case SIGSTOP: job->status = job_stopped; break;
484   case SIGCONT: job->status = job_running; break;
485 #endif /* SIGSTOP */
486   default: abort();
487   }
488
489 #ifdef SIGSTOP
490   if (p->verbose_p)
491     fprintf (stderr, "%s: %s pid %lu.\n", progname,
492              (signal == SIGTERM ? "killing" :
493               signal == SIGSTOP ? "suspending" :
494               signal == SIGCONT ? "resuming" : "signalling"),
495              (unsigned long) job->pid);
496 #else  /* !SIGSTOP */
497   if (p->verbose_p)
498     fprintf (stderr, "%s: %s pid %lu.\n", progname, "killing",
499              (unsigned long) job->pid);
500 #endif /* !SIGSTOP */
501
502   status = kill (job->pid, signal);
503
504   if (p->verbose_p && status < 0)
505     {
506       if (errno == ESRCH)
507         fprintf (stderr, "%s: child process %lu (%s) was already dead.\n",
508                  progname, job->pid, job->name);
509       else
510         {
511           char buf [1024];
512           sprintf (buf, "%s: couldn't kill child process %lu (%s)",
513                    progname, job->pid, job->name);
514           perror (buf);
515         }
516     }
517
518   await_dying_children (si);
519
520  DONE:
521   unblock_sigchld();
522   if (block_sigchld_handler < 0)
523     abort();
524
525   clean_job_list();
526   return status;
527 }
528
529
530 #ifdef SIGCHLD
531 static RETSIGTYPE
532 sigchld_handler (int sig)
533 {
534   saver_info *si = global_si_kludge;    /* I hate C so much... */
535
536 #ifdef DEBUG
537   if (si->prefs.debug_p)
538     fprintf(stderr, "%s: got SIGCHLD%s\n", progname,
539             (block_sigchld_handler ? " (blocked)" : ""));
540 #endif /* DEBUG */
541
542   if (block_sigchld_handler < 0)
543     abort();
544   else if (block_sigchld_handler == 0)
545     {
546       block_sigchld();
547       await_dying_children (si);
548       unblock_sigchld();
549     }
550
551   init_sigchld();
552 }
553 #endif /* SIGCHLD */
554
555
556 #ifndef VMS
557 static void
558 await_dying_children (saver_info *si)
559 {
560   while (1)
561     {
562       int wait_status = 0;
563       pid_t kid;
564
565       errno = 0;
566       kid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED);
567 #ifdef DEBUG
568       if (si->prefs.debug_p)
569         if (kid < 0 && errno)
570           fprintf (stderr, "%s: waitpid(-1) ==> %ld (%d)\n", progname,
571                    (long) kid, errno);
572       else
573           fprintf (stderr, "%s: waitpid(-1) ==> %ld\n", progname, (long) kid);
574 #endif /* DEBUG */
575
576       /* 0 means no more children to reap.
577          -1 means error -- except "interrupted system call" isn't a "real"
578          error, so if we get that, we should just try again. */
579       if (kid == 0 ||
580           (kid < 0 && errno != EINTR))
581         break;
582
583       describe_dead_child (si, kid, wait_status);
584     }
585 }
586
587
588 static void
589 describe_dead_child (saver_info *si, pid_t kid, int wait_status)
590 {
591   int i;
592   saver_preferences *p = &si->prefs;
593   struct screenhack_job *job = find_job (kid);
594   const char *name = job ? job->name : "<unknown>";
595
596   if (WIFEXITED (wait_status))
597     {
598       int exit_status = WEXITSTATUS (wait_status);
599
600       /* Treat exit code as a signed 8-bit quantity. */
601       if (exit_status & 0x80) exit_status |= ~0xFF;
602
603       /* One might assume that exiting with non-0 means something went wrong.
604          But that loser xswarm exits with the code that it was killed with, so
605          it *always* exits abnormally.  Treat abnormal exits as "normal" (don't
606          mention them) if we've just killed the subprocess.  But mention them
607          if they happen on their own.
608        */
609       if (!job ||
610           (exit_status != 0 &&
611            (p->verbose_p || job->status != job_killed)))
612         fprintf (stderr,
613                  "%s: child pid %lu (%s) exited abnormally (code %d).\n",
614                  progname, (unsigned long) kid, name, exit_status);
615       else if (p->verbose_p)
616         printf ("%s: child pid %lu (%s) exited normally.\n",
617                 progname, (unsigned long) kid, name);
618
619       if (job)
620         job->status = job_dead;
621     }
622   else if (WIFSIGNALED (wait_status))
623     {
624       if (p->verbose_p ||
625           !job ||
626           job->status != job_killed ||
627           WTERMSIG (wait_status) != SIGTERM)
628         fprintf (stderr, "%s: child pid %lu (%s) terminated with %s.\n",
629                  progname, (unsigned long) kid, name,
630                  signal_name (WTERMSIG(wait_status)));
631
632       if (job)
633         job->status = job_dead;
634     }
635   else if (WIFSTOPPED (wait_status))
636     {
637       if (p->verbose_p)
638         fprintf (stderr, "%s: child pid %lu (%s) stopped with %s.\n",
639                  progname, (unsigned long) kid, name,
640                  signal_name (WSTOPSIG (wait_status)));
641
642       if (job)
643         job->status = job_stopped;
644     }
645   else
646     {
647       fprintf (stderr, "%s: child pid %lu (%s) died in a mysterious way!",
648                progname, (unsigned long) kid, name);
649       if (job)
650         job->status = job_dead;
651     }
652
653   /* Clear out the pid so that screenhack_running_p() knows it's dead.
654    */
655   if (!job || job->status == job_dead)
656     for (i = 0; i < si->nscreens; i++)
657       {
658         saver_screen_info *ssi = &si->screens[i];
659         if (kid == ssi->pid)
660           ssi->pid = 0;
661       }
662 }
663
664 #else  /* VMS */
665 static void await_dying_children (saver_info *si) { return; }
666 #endif /* VMS */
667
668
669 void
670 init_sigchld (void)
671 {
672 #ifdef SIGCHLD
673
674 # ifdef HAVE_SIGACTION  /* Thanks to Tom Kelly <tom@ancilla.toronto.on.ca> */
675
676   static Bool sigchld_initialized_p = 0;
677   if (!sigchld_initialized_p)
678     {
679       struct sigaction action, old;
680
681       action.sa_handler = sigchld_handler;
682       sigemptyset(&action.sa_mask);
683       action.sa_flags = 0;
684
685       if (sigaction(SIGCHLD, &action, &old) < 0)
686         {
687           char buf [255];
688           sprintf (buf, "%s: couldn't catch SIGCHLD", progname);
689           perror (buf);
690         }
691       sigchld_initialized_p = True;
692     }
693
694 # else  /* !HAVE_SIGACTION */
695
696   if (((long) signal (SIGCHLD, sigchld_handler)) == -1L)
697     {
698       char buf [255];
699       sprintf (buf, "%s: couldn't catch SIGCHLD", progname);
700       perror (buf);
701     }
702 # endif /* !HAVE_SIGACTION */
703 #endif /* SIGCHLD */
704 }
705
706
707
708 \f
709
710 static Bool
711 select_visual_of_hack (saver_screen_info *ssi, const char *hack)
712 {
713   saver_info *si = ssi->global;
714   saver_preferences *p = &si->prefs;
715   Bool selected;
716   static char vis [1024];
717   const char *in = hack;
718   char *out = vis;
719   while (isspace(*in)) in++;            /* skip whitespace */
720   while (!isspace(*in) && *in != ':')
721     *out++ = *in++;                     /* snarf first token */
722   while (isspace(*in)) in++;            /* skip whitespace */
723   *out = 0;
724
725   if (*in == ':')
726     selected = select_visual(ssi, vis);
727   else
728     selected = select_visual(ssi, 0);
729
730   if (!selected && (p->verbose_p || si->demo_mode_p))
731     {
732       if (*in == ':') in++;
733       while (isspace(*in)) in++;
734       fprintf (stderr,
735                (si->demo_mode_p
736                 ? "%s: warning, no \"%s\" visual for \"%s\".\n"
737                 : "%s: no \"%s\" visual; skipping \"%s\".\n"),
738                progname, (vis ? vis : "???"), in);
739     }
740
741   return selected;
742 }
743
744
745 static void
746 spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p)
747 {
748   saver_info *si = ssi->global;
749   saver_preferences *p = &si->prefs;
750   raise_window (si, first_time_p, True, False);
751   XFlush (si->dpy);
752
753   if (p->screenhacks_count || si->demo_mode_p)
754     {
755       char *hack;
756       pid_t forked;
757       char buf [255];
758       int new_hack;
759
760       if (si->demo_mode_p)
761         {
762           hack = si->demo_hack;
763
764           /* Ignore visual-selection failure if in demo mode. */
765           (void) select_visual_of_hack (ssi, hack);
766         }
767       else
768         {
769           int retry_count = 0;
770
771         AGAIN:
772           if (p->screenhacks_count == 1)
773             new_hack = 0;
774           else if (si->next_mode_p == 1)
775             new_hack = (ssi->current_hack + 1) % p->screenhacks_count;
776           else if (si->next_mode_p == 2)
777             new_hack = ((ssi->current_hack + p->screenhacks_count - 1)
778                         % p->screenhacks_count);
779           else
780             while ((new_hack = random () % p->screenhacks_count)
781                    == ssi->current_hack)
782               ;
783           ssi->current_hack = new_hack;
784           hack = p->screenhacks[ssi->current_hack];
785
786           if (!select_visual_of_hack (ssi, hack))
787             {
788               if (++retry_count > (p->screenhacks_count*4))
789                 {
790                   /* Uh, oops.  Odds are, there are no suitable visuals,
791                      and we're looping.  Give up.  (This is totally lame,
792                      what we should do is make a list of suitable hacks at
793                      the beginning, then only loop over them.)
794                   */
795                   if (p->verbose_p)
796                     fprintf(stderr,
797                             "%s: no suitable visuals for these programs.\n",
798                             progname);
799                   return;
800                 }
801               else
802                 goto AGAIN;
803             }
804         }
805       si->next_mode_p = 0;
806
807
808       /* If there's a visual description on the front of the command, nuke it.
809        */
810       {
811         char *in = hack;
812         while (isspace(*in)) in++;                      /* skip whitespace */
813         hack = in;
814         while (!isspace(*in) && *in != ':') in++;       /* snarf first token */
815         while (isspace(*in)) in++;                      /* skip whitespace */
816         if (*in == ':')
817           {
818             in++;
819             while (isspace(*in)) in++;
820             hack = in;
821           }
822       }
823
824       switch ((int) (forked = fork ()))
825         {
826         case -1:
827           sprintf (buf, "%s: couldn't fork", progname);
828           perror (buf);
829           restore_real_vroot (si);
830           saver_exit (si, 1);
831
832         case 0:
833           close (ConnectionNumber (si->dpy));   /* close display fd */
834           nice_subproc (p->nice_inferior);      /* change process priority */
835           hack_environment (ssi);               /* set $DISPLAY */
836           exec_screenhack (si, hack);           /* this does not return */
837           abort();
838           break;
839
840         default:
841           ssi->pid = forked;
842           (void) make_job (forked, hack);
843           break;
844         }
845     }
846 }
847
848
849 void
850 spawn_screenhack (saver_info *si, Bool first_time_p)
851 {
852   int i;
853   for (i = 0; i < si->nscreens; i++)
854     {
855       saver_screen_info *ssi = &si->screens[i];
856       spawn_screenhack_1 (ssi, first_time_p);
857     }
858 }
859
860
861 void
862 kill_screenhack (saver_info *si)
863 {
864   int i;
865   for (i = 0; i < si->nscreens; i++)
866     {
867       saver_screen_info *ssi = &si->screens[i];
868       if (ssi->pid)
869         kill_job (si, ssi->pid, SIGTERM);
870       ssi->pid = 0;
871     }
872 }
873
874
875 void
876 suspend_screenhack (saver_info *si, Bool suspend_p)
877 {
878 #ifdef SIGSTOP  /* older VMS doesn't have it... */
879   int i;
880   for (i = 0; i < si->nscreens; i++)
881     {
882       saver_screen_info *ssi = &si->screens[i];
883       if (ssi->pid)
884         kill_job (si, ssi->pid, (suspend_p ? SIGSTOP : SIGCONT));
885     }
886 #endif /* SIGSTOP */
887 }
888
889
890 /* Called when we're exiting abnormally, to kill off the subproc. */
891 void
892 emergency_kill_subproc (saver_info *si)
893 {
894   int i;
895 #ifdef SIGCHLD
896   signal (SIGCHLD, SIG_IGN);
897 #endif /* SIGCHLD */
898
899   for (i = 0; i < si->nscreens; i++)
900     {
901       saver_screen_info *ssi = &si->screens[i];
902       if (ssi->pid)
903         {
904           kill_job (si, ssi->pid, SIGTERM);
905           ssi->pid = 0;
906         }
907     }
908 }
909
910 Bool
911 screenhack_running_p (saver_info *si)
912 {
913   Bool result = True;
914   int i;
915   for (i = 0; i < si->nscreens; i++)
916     {
917       saver_screen_info *ssi = &si->screens[i];
918       if (!ssi->pid)
919         result = False;
920     }
921   return result;
922 }
923
924 \f
925 /* Restarting the xscreensaver process from scratch. */
926
927 static char **saved_argv;
928
929 void
930 save_argv (int argc, char **argv)
931 {
932   saved_argv = (char **) malloc ((argc + 2) * sizeof (char *));
933   saved_argv [argc] = 0;
934   while (argc--)
935     {
936       int i = strlen (argv [argc]) + 1;
937       saved_argv [argc] = (char *) malloc (i);
938       memcpy (saved_argv [argc], argv [argc], i);
939     }
940 }
941
942 void
943 restart_process (saver_info *si)
944 {
945   fflush (real_stdout);
946   fflush (real_stderr);
947   execvp (saved_argv [0], saved_argv);  /* shouldn't return */
948   {
949     char buf [512];
950     sprintf (buf, "%s: could not restart process", progname);
951     perror(buf);
952     fflush(stderr);
953   }
954 }
955
956 /* Like restart_process(), but ensures that when it restarts,
957    it comes up in demo-mode. */
958 void
959 demo_mode_restart_process (saver_info *si)
960 {
961   int i;
962   for (i = 0; saved_argv [i]; i++);
963   /* add the -demo switch; save_argv() left room for this. */
964   saved_argv [i] = "-demo";
965   saved_argv [i+1] = 0;
966   restart_process (si);         /* shouldn't return */
967   saved_argv [i] = 0;
968   XBell(si->dpy, 0);
969 }
970
971 static void
972 hack_environment (saver_screen_info *ssi)
973 {
974   /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
975      the spawned processes inherit is correct.  First, it must be on the same
976      host and display as the value of -display passed in on our command line
977      (which is not necessarily the same as what our $DISPLAY variable is.)
978      Second, the screen number in the $DISPLAY passed to the subprocess should
979      be the screen on which this particular hack is running -- not the display
980      specification which the driver itself is using, since the driver ignores
981      its screen number and manages all existing screens.
982    */
983   saver_info *si = ssi->global;
984   const char *odpy = DisplayString (si->dpy);
985   char *ndpy = (char *) malloc(strlen(odpy) + 20);
986   int screen_number;
987   char *s;
988
989   for (screen_number = 0; screen_number < si->nscreens; screen_number++)
990     if (ssi == &si->screens[screen_number])
991       break;
992   if (screen_number >= si->nscreens) abort();
993
994   strcpy (ndpy, "DISPLAY=");
995   s = ndpy + strlen(ndpy);
996   strcpy (s, odpy);
997
998   while (*s && *s != ':') s++;                  /* skip to colon */
999   while (*s == ':') s++;                        /* skip over colons */
1000   while (isdigit(*s)) s++;                      /* skip over dpy number */
1001   while (*s == '.') s++;                        /* skip over dot */
1002   if (s[-1] != '.') *s++ = '.';                 /* put on a dot */
1003   sprintf(s, "%d", screen_number);              /* put on screen number */
1004
1005   /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
1006      any more, right?  It's not Posix, but everyone seems to have it. */
1007 #ifdef HAVE_PUTENV
1008   if (putenv (ndpy))
1009     abort ();
1010 #endif /* HAVE_PUTENV */
1011 }
1012
1013 \f
1014 /* Change the uid/gid of the screensaver process, so that it is safe for it
1015    to run setuid root (which it needs to do on some systems to read the 
1016    encrypted passwords from the passwd file.)
1017
1018    hack_uid() is run before opening the X connection, so that XAuth works.
1019    hack_uid_warn() is called after the connection is opened and the command
1020    line arguments are parsed, so that the messages from hack_uid() get 
1021    printed after we know whether we're in `verbose' mode.
1022  */
1023
1024 #ifndef NO_SETUID
1025
1026 static int hack_uid_errno;
1027 static char hack_uid_buf [255], *hack_uid_error;
1028
1029 void
1030 hack_uid (saver_info *si)
1031 {
1032
1033   /* If we've been run as setuid or setgid to someone else (most likely root)
1034      turn off the extra permissions so that random user-specified programs
1035      don't get special privileges.  (On some systems it might be necessary
1036      to install this as setuid root in order to read the passwd file to
1037      implement lock-mode...)
1038   */
1039   setgid (getgid ());
1040   setuid (getuid ());
1041
1042   hack_uid_errno = 0;
1043   hack_uid_error = 0;
1044
1045   /* If we're being run as root (as from xdm) then switch the user id
1046      to something safe. */
1047   if (getuid () == 0)
1048     {
1049       struct passwd *p;
1050       /* Locking can't work when running as root, because we have no way of
1051          knowing what the user id of the logged in user is (so we don't know
1052          whose password to prompt for.)
1053        */
1054       si->locking_disabled_p = True;
1055       si->nolock_reason = "running as root";
1056       p = getpwnam ("nobody");
1057       if (! p) p = getpwnam ("noaccess");
1058       if (! p) p = getpwnam ("daemon");
1059       if (! p) p = getpwnam ("bin");
1060       if (! p) p = getpwnam ("sys");
1061       if (! p)
1062         {
1063           hack_uid_error = "couldn't find safe uid; running as root.";
1064           hack_uid_errno = -1;
1065         }
1066       else
1067         {
1068           struct group *g = getgrgid (p->pw_gid);
1069           hack_uid_error = hack_uid_buf;
1070           sprintf (hack_uid_error, "changing uid/gid to %s/%s (%ld/%ld).",
1071                    p->pw_name, (g ? g->gr_name : "???"),
1072                    (long) p->pw_uid, (long) p->pw_gid);
1073
1074           /* Change the gid to be a safe one.  If we can't do that, then
1075              print a warning.  We change the gid before the uid so that we
1076              change the gid while still root. */
1077           if (setgid (p->pw_gid) != 0)
1078             {
1079               hack_uid_errno = errno;
1080               sprintf (hack_uid_error, "couldn't set gid to %s (%ld)",
1081                        (g ? g->gr_name : "???"), (long) p->pw_gid);
1082             }
1083
1084           /* Now change the uid to be a safe one. */
1085           if (setuid (p->pw_uid) != 0)
1086             {
1087               hack_uid_errno = errno;
1088               sprintf (hack_uid_error, "couldn't set uid to %s (%ld)",
1089                        p->pw_name, (long) p->pw_uid);
1090             }
1091         }
1092     }
1093 # ifndef NO_LOCKING
1094  else   /* disable locking if already being run as "someone else" */
1095    {
1096      struct passwd *p = getpwuid (getuid ());
1097      if (!p ||
1098          !strcmp (p->pw_name, "root") ||
1099          !strcmp (p->pw_name, "nobody") ||
1100          !strcmp (p->pw_name, "noaccess") ||
1101          !strcmp (p->pw_name, "daemon") ||
1102          !strcmp (p->pw_name, "bin") ||
1103          !strcmp (p->pw_name, "sys"))
1104        {
1105          si->locking_disabled_p = True;
1106          si->nolock_reason = hack_uid_buf;
1107          sprintf (si->nolock_reason, "running as %s", p->pw_name);
1108        }
1109    }
1110 # endif /* !NO_LOCKING */
1111 }
1112
1113 void
1114 hack_uid_warn (saver_info *si)
1115 {
1116   saver_preferences *p = &si->prefs;
1117
1118   if (! hack_uid_error)
1119     ;
1120   else if (hack_uid_errno == 0)
1121     {
1122       if (p->verbose_p)
1123         printf ("%s: %s\n", progname, hack_uid_error);
1124     }
1125   else
1126     {
1127       char buf [255];
1128       sprintf (buf, "%s: %s", progname, hack_uid_error);
1129       if (hack_uid_errno == -1)
1130         fprintf (stderr, "%s\n", buf);
1131       else
1132         {
1133           errno = hack_uid_errno;
1134           perror (buf);
1135         }
1136     }
1137 }
1138
1139 #endif /* !NO_SETUID */