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