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