http://x.cybermirror.org/R5contrib/xscreensaver-1.21.tar.Z
[xscreensaver] / driver / subprocs.c
1 /* xscreensaver, Copyright (c) 1991-1993 Jamie Zawinski <jwz@lucid.com>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 /* I would really like some error messages to show up on the screensaver window
13    itself when subprocs die, or when we can't launch them.  If the process
14    produces output, but does not actually die, I would like that output to go
15    to the appropriate stdout/stderr as they do now.  X and Unix conspire to
16    make this incredibly difficult.
17
18    - Not all systems have SIGIO, so we can't necessarily be signalled when a
19      process dies, so we'd have to poll it with wait() or something awful like
20      that, which would mean the main thread waking up more often than it does
21      now.
22
23    - We can't tell the difference between a process dying, and a process not
24      being launched correctly (for example, not being on $PATH) partly because
25      of the contortions we need to go through with /bin/sh in order to launch
26      it.
27
28    - We can't do X stuff from signal handlers, so we'd need to set a flag, 
29      save the error message, and notice that flag in the main thread.  The
30      problem is that the main thread is probably sleeping, waiting for the 
31      next X event, so to do this we'd have to register a pipe FD or something,
32      and write to it when something loses.
33
34    - We could assume that any output produced by a subproc indicates an error,
35      and blast that across the screen.  This means we'd need to use popen()
36      instead of forking and execing /bin/sh to run it for us.  Possibly this
37      would work, but see comment in exec_screenhack() about getting pids.
38      I think we could do the "exec " trick with popen() but would SIGIO get
39      delivered correctly?  Who knows.  (We could register the pipe-FD with
40      Xt, and handle output on it with a callback.)
41
42    - For the simple case of the programs not being on $PATH, we could just 
43      search $PATH before launching the shell, but that seems hardly worth the
44      effort...  And it's broken!!  Why should we have to duplicate half the
45      work of the shell?  (Because it's Unix, that's why!  Bend over.)
46  */
47
48 #if __STDC__
49 #include <stdlib.h>
50 #include <unistd.h>
51 #include <string.h>
52 #endif
53
54 #include <stdio.h>
55
56 #include <X11/Xlib.h>           /* not used for much... */
57
58 #ifndef ESRCH
59 #include <errno.h>
60 #endif
61
62 #include <sys/time.h>           /* sys/resource.h needs this for timeval */
63 #include <sys/resource.h>       /* for setpriority() and PRIO_PROCESS */
64 #include <sys/wait.h>           /* for waitpid() and associated macros */
65 #include <signal.h>             /* for the signal names */
66
67 extern char **environ;          /* why isn't this in some header file? */
68
69 #ifndef NO_SETUID
70 #include <pwd.h>                /* for getpwnam() and struct passwd */
71 #include <grp.h>                /* for getgrgid() and struct group */
72 #endif /* NO_SETUID */
73
74 #if !defined(SIGCHLD) && defined(SIGCLD)
75 #define SIGCHLD SIGCLD
76 #endif
77
78 #if __STDC__
79 extern int putenv (/* const char * */); /* getenv() is in stdlib.h... */
80 extern int kill (pid_t, int);           /* signal() is in sys/signal.h... */
81 #endif
82
83 # if defined(SVR4) || defined(SYSV)
84 #  define random() rand()
85 # else /* !totally-losing-SYSV */
86 extern long random();                   /* rand() is in stdlib.h... */
87 # endif /* !totally-losing-SYSV */
88
89
90 #include "xscreensaver.h"
91
92 /* this must be `sh', not whatever $SHELL happens to be. */
93 char *shell;
94 static pid_t pid = 0;
95 char **screenhacks;
96 int screenhacks_count;
97 int current_hack = -1;
98 char *demo_hack;
99 int next_mode_p = 0;
100 Bool locking_disabled_p = False;
101 char *nolock_reason = 0;
102 int nice_inferior = 0;
103
104 extern Bool demo_mode_p;
105
106 static void
107 exec_screenhack (command)
108      char *command;
109 {
110   char *tmp;
111   char buf [512];
112   char *av [5];
113   int ac = 0;
114
115   /* Close this fork's version of the display's fd.  It will open its own. */
116   close (ConnectionNumber (dpy));
117   
118   /* I don't believe what a sorry excuse for an operating system UNIX is!
119
120      - I want to spawn a process.
121      - I want to know it's pid so that I can kill it.
122      - I would like to receive a message when it dies of natural causes.
123      - I want the spawned process to have user-specified arguments.
124
125      The *only way* to parse arguments the way the shells do is to run a
126      shell (or duplicate what they do, which would be a *lot* of code.)
127
128      The *only way* to know the pid of the process is to fork() and exec()
129      it in the spawned side of the fork.
130
131      But if you're running a shell to parse your arguments, this gives you
132      the pid of the SHELL, not the pid of the PROCESS that you're actually
133      interested in, which is an *inferior* of the shell.  This also means
134      that the SIGCHLD you get applies to the shell, not its inferior.
135
136      So, the only solution other than implementing an argument parser here
137      is to force the shell to exec() its inferior.  What a fucking hack!
138      We prepend "exec " to the command string.
139
140      (Actually, Clint Wong <clint@jts.com> points out that process groups
141      might be used to take care of this problem; this may be worth considering
142      some day, except that, 1: this code works now, so why fix it, and 2: from
143      what I've seen in Emacs, dealing with process groups isn't especially
144      portable.)
145    */
146   tmp = command;
147   command = (char *) malloc (strlen (tmp) + 6);
148   memcpy (command, "exec ", 5);
149   memcpy (command + 5, tmp, strlen (tmp) + 1);
150
151   /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
152   av [ac++] = shell;
153   av [ac++] = "-c";
154   av [ac++] = command;
155   av [ac++] = 0;
156   
157   if (verbose_p)
158     printf ("%s: spawning \"%s\" in pid %d.\n", progname, command, getpid ());
159
160 #if defined(SYSV) || defined(SVR4) || defined(__hpux)
161   {
162     int old_nice = nice (0);
163     int n = nice_inferior - old_nice;
164     errno = 0;
165     if (nice (n) == -1 && errno != 0)
166       {
167         sprintf (buf, "%s: %snice(%d) failed", progname,
168                  (verbose_p ? "## " : ""), n);
169         perror (buf);
170       }
171   }
172 #else /* !SYSV */
173 #ifdef PRIO_PROCESS
174   if (setpriority (PRIO_PROCESS, getpid(), nice_inferior) != 0)
175     {
176       sprintf (buf, "%s: %ssetpriority(PRIO_PROCESS, %d, %d) failed",
177                progname, (verbose_p ? "## " : ""), getpid(), nice_inferior);
178       perror (buf);
179     }
180 #else /* !PRIO_PROCESS */
181   if (nice_inferior != 0)
182     fprintf (stderr,
183            "%s: %sdon't know how to change process priority on this system.\n",
184              progname, (verbose_p ? "## " : ""));
185 #endif /* !PRIO_PROCESS */
186 #endif /* !SYSV */
187
188   /* Now overlay the current process with /bin/sh running the command.
189      If this returns, it's an error.
190    */
191   execve (av [0], av, environ);
192
193   sprintf (buf, "%s: %sexecve() failed", progname, (verbose_p ? "## " : ""));
194   perror (buf);
195   exit (1);     /* Note that this only exits a child fork.  */
196 }
197
198 /* to avoid a race between the main thread and the SIGCHLD handler */
199 static int killing = 0;
200 static Bool suspending = False;
201
202 static char *current_hack_name P((void));
203
204 static void
205 await_child_death (killed)
206      Bool killed;
207 {
208   Bool suspended_p = False;
209   int status;
210   pid_t kid;
211   killing = 1;
212   if (! pid)
213     return;
214
215   do
216     {
217       kid = waitpid (pid, &status, WUNTRACED);
218     }
219   while (kid == -1 && errno == EINTR);
220
221   if (kid == pid)
222     {
223       if (WIFEXITED (status))
224         {
225           int exit_status = WEXITSTATUS (status);
226           if (exit_status & 0x80)
227             exit_status |= ~0xFF;
228           /* One might assume that exiting with non-0 means something went
229              wrong.  But that loser xswarm exits with the code that it was
230              killed with, so it *always* exits abnormally.  Treat abnormal
231              exits as "normal" (don't mention them) if we've just killed
232              the subprocess.  But mention them if they happen on their own.
233            */
234           if (exit_status != 0 && (verbose_p || (! killed)))
235             fprintf (stderr,
236                      "%s: %schild pid %d (%s) exited abnormally (code %d).\n",
237                     progname, (verbose_p ? "## " : ""),
238                      pid, current_hack_name (), exit_status);
239           else if (verbose_p)
240             printf ("%s: child pid %d (%s) exited normally.\n",
241                     progname, pid, current_hack_name ());
242         }
243       else if (WIFSIGNALED (status))
244         {
245           if (!killed || WTERMSIG (status) != SIGTERM)
246             fprintf (stderr,
247                      "%s: %schild pid %d (%s) terminated with signal %d!\n",
248                      progname, (verbose_p ? "## " : ""),
249                      pid, current_hack_name (), WTERMSIG (status));
250           else if (verbose_p)
251             printf ("%s: child pid %d (%s) terminated with SIGTERM.\n",
252                     progname, pid, current_hack_name ());
253         }
254       else if (suspending)
255         {
256           suspended_p = True;
257           suspending = False; /* complain if it happens twice */
258         }
259       else if (WIFSTOPPED (status))
260         {
261           suspended_p = True;
262           fprintf (stderr, "%s: %schild pid %d (%s) stopped with signal %d!\n",
263                    progname, (verbose_p ? "## " : ""), pid,
264                    current_hack_name (), WSTOPSIG (status));
265         }
266       else
267         fprintf (stderr, "%s: %schild pid %d (%s) died in a mysterious way!",
268                  progname, (verbose_p ? "## " : ""), pid, current_hack_name());
269     }
270   else if (kid <= 0)
271     fprintf (stderr, "%s: %swaitpid(%d, ...) says there are no kids?  (%d)\n",
272              progname, (verbose_p ? "## " : ""), pid, kid);
273   else
274     fprintf (stderr, "%s: %swaitpid(%d, ...) says proc %d died, not %d?\n",
275              progname, (verbose_p ? "## " : ""), pid, kid, pid);
276   killing = 0;
277   if (suspended_p != True)
278     pid = 0;
279 }
280
281 static char *
282 current_hack_name ()
283 {
284   static char chn [1024];
285   char *hack = (demo_mode_p ? demo_hack : screenhacks [current_hack]);
286   int i;
287   for (i = 0; hack [i] != 0 && hack [i] != ' ' && hack [i] != '\t'; i++)
288     chn [i] = hack [i];
289   chn [i] = 0;
290   return chn;
291 }
292
293 #ifdef SIGCHLD
294 static void
295 sigchld_handler (sig)
296      int sig;
297 {
298   if (killing)
299     return;
300   if (! pid)
301     abort ();
302   await_child_death (False);
303 }
304 #endif
305
306
307 void
308 init_sigchld ()
309 {
310 #ifdef SIGCHLD
311   if (((int) signal (SIGCHLD, sigchld_handler)) == -1)
312     {
313       char buf [255];
314       sprintf (buf, "%s: %scouldn't catch SIGCHLD", progname,
315                (verbose_p ? "## " : ""));
316       perror (buf);
317     }
318 #endif
319 }
320
321
322 extern void raise_window P((Bool inhibit_fade, Bool between_hacks_p));
323
324 void
325 spawn_screenhack (first_time_p)
326      Bool first_time_p;
327 {
328   raise_window (first_time_p, True);
329   XFlush (dpy);
330
331   if (screenhacks_count || demo_mode_p)
332     {
333       char *hack;
334       pid_t forked;
335       char buf [255];
336       int new_hack;
337       if (demo_mode_p)
338         {
339           hack = demo_hack;
340         }
341       else
342         {
343           if (screenhacks_count == 1)
344             new_hack = 0;
345           else if (next_mode_p == 1)
346             new_hack = (current_hack + 1) % screenhacks_count,
347             next_mode_p = 0;
348           else if (next_mode_p == 2)
349             {
350               new_hack = ((current_hack + screenhacks_count - 1)
351                           % screenhacks_count);
352               next_mode_p = 0;
353             }
354           else
355             while ((new_hack = random () % screenhacks_count) == current_hack)
356               ;
357           current_hack = new_hack;
358           hack = screenhacks[current_hack];
359         }
360
361       switch (forked = fork ())
362         {
363         case -1:
364           sprintf (buf, "%s: %scouldn't fork",
365                    progname, (verbose_p ? "## " : ""));
366           perror (buf);
367           restore_real_vroot ();
368           exit (1);
369         case 0:
370           exec_screenhack (hack); /* this does not return */
371           break;
372         default:
373           pid = forked;
374           break;
375         }
376     }
377 }
378
379 void
380 kill_screenhack ()
381 {
382   killing = 1;
383   if (! pid)
384     return;
385   if (kill (pid, SIGTERM) < 0)
386     {
387       if (errno == ESRCH)
388         {
389           /* Sometimes we don't get a SIGCHLD at all!  WTF?
390              It's a race condition.  It looks to me like what's happening is
391              something like: a subprocess dies of natural causes.  There is a
392              small window between when the process dies and when the SIGCHLD
393              is (would have been) delivered.  If we happen to try to kill()
394              the process during that time, the kill() fails, because the
395              process is already dead.  But! no SIGCHLD is delivered (perhaps
396              because the failed kill() has reset some state in the kernel?)
397              Anyway, if kill() says "No such process", then we have to wait()
398              for it anyway, because the process has already become a zombie.
399              I love Unix.
400            */
401           await_child_death (False);
402         }
403       else
404         {
405           char buf [255];
406           sprintf (buf, "%s: %scouldn't kill child process %d", progname,
407                    (verbose_p ? "## " : ""), pid);
408           perror (buf);
409         }
410     }
411   else
412     {
413       if (verbose_p)
414         printf ("%s: killing pid %d.\n", progname, pid);
415       await_child_death (True);
416     }
417 }
418
419
420 void
421 suspend_screenhack (suspend_p)
422      Bool suspend_p;
423 {
424   
425   suspending = suspend_p;
426   if (! pid)
427     ;
428   else if (kill (pid, (suspend_p ? SIGSTOP : SIGCONT)) < 0)
429     {
430       char buf [255];
431       sprintf (buf, "%s: %scouldn't %s child process %d", progname,
432                (verbose_p ? "## " : ""),
433                (suspend_p ? "suspend" : "resume"),
434                pid);
435       perror (buf);
436     }
437   else if (verbose_p)
438     printf ("%s: %s pid %d.\n", progname,
439             (suspend_p ? "suspending" : "resuming"), pid);
440 }
441
442 \f
443 /* Restarting the xscreensaver process from scratch. */
444
445 static char **saved_argv;
446
447 void
448 save_argv (argc, argv)
449      int argc;
450      char **argv;
451 {
452   saved_argv = (char **) malloc ((argc + 2) * sizeof (char *));
453   saved_argv [argc] = 0;
454   while (argc--)
455     {
456       int i = strlen (argv [argc]) + 1;
457       saved_argv [argc] = (char *) malloc (i);
458       memcpy (saved_argv [argc], argv [argc], i);
459     }
460 }
461
462 void
463 restart_process ()
464 {
465   XCloseDisplay (dpy);
466   fflush (stdout);
467   fflush (stderr);
468   execvp (saved_argv [0], saved_argv);
469   fprintf (stderr, "%s: %scould not restart process: %s (%d)\n",
470            progname, (verbose_p ? "## " : ""),
471            (errno == E2BIG ? "arglist too big" :
472             errno == EACCES ? "could not execute" :
473             errno == EFAULT ? "memory fault" :
474             errno == EIO ? "I/O error" :
475             errno == ENAMETOOLONG ? "name too long" :
476             errno == ELOOP ? "too many symbolic links" :
477             errno == ENOENT ? "file no longer exists" :
478             errno == ENOTDIR ? "directory no longer exists" :
479             errno == ENOEXEC ? "bad executable file" :
480             errno == ENOMEM ? "out of memory" :
481             "execvp() returned unknown error code"),
482            errno);
483   exit (1);
484 }
485
486 void
487 demo_mode_restart_process ()
488 {
489   int i;
490   for (i = 0; saved_argv [i]; i++);
491   /* add the -demo switch; save_argv() left room for this. */
492   saved_argv [i] = "-demo";
493   saved_argv [i+1] = 0;
494   restart_process ();
495 }
496
497 void
498 hack_environment ()
499 {
500   /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
501      the spawned processes inherit is the same as the value of -display passed
502      in on our command line (which is not necessarily the same as what our
503      $DISPLAY variable is.)
504    */
505   char *s, buf [2048];
506   int i;
507   sprintf (buf, "DISPLAY=%s", DisplayString (dpy));
508   i = strlen (buf);
509   s = (char *) malloc (i+1);
510   strncpy (s, buf, i+1);
511   if (putenv (s))
512     abort ();
513 }
514
515 \f
516 /* Change the uid/gid of the screensaver process, so that it is safe for it
517    to run setuid root (which it needs to do on some systems to read the 
518    encrypted passwords from the passwd file.)
519
520    hack_uid() is run before opening the X connection, so that XAuth works.
521    hack_uid_warn() is called after the connection is opened and the command
522    line arguments are parsed, so that the messages from hack_uid() get 
523    printed after we know whether we're in `verbose' mode.
524  */
525
526 #ifndef NO_SETUID
527
528 static int hack_uid_errno;
529 static char hack_uid_buf [255], *hack_uid_error;
530
531 void
532 hack_uid ()
533 {
534   /* If we've been run as setuid or setgid to someone else (most likely root)
535      turn off the extra permissions so that random user-specified programs
536      don't get special privileges.  (On some systems it might be necessary
537      to install this as setuid root in order to read the passwd file to
538      implement lock-mode...)
539   */
540   setgid (getgid ());
541   setuid (getuid ());
542
543   hack_uid_errno = 0;
544   hack_uid_error = 0;
545
546   /* If we're being run as root (as from xdm) then switch the user id
547      to something safe. */
548   if (getuid () == 0)
549     {
550       struct passwd *p;
551       /* Locking can't work when running as root, because we have no way of
552          knowing what the user id of the logged in user is (so we don't know
553          whose password to prompt for.)
554        */
555       locking_disabled_p = True;
556       nolock_reason = "running as root";
557       p = getpwnam ("nobody");
558       if (! p) p = getpwnam ("daemon");
559       if (! p) p = getpwnam ("bin");
560       if (! p) p = getpwnam ("sys");
561       if (! p)
562         {
563           hack_uid_error = "couldn't find safe uid; running as root.";
564           hack_uid_errno = -1;
565         }
566       else
567         {
568           struct group *g = getgrgid (p->pw_gid);
569           hack_uid_error = hack_uid_buf;
570           sprintf (hack_uid_error, "changing uid/gid to %s/%s (%d/%d).",
571                    p->pw_name, (g ? g->gr_name : "???"), p->pw_uid, p->pw_gid);
572
573           /* Change the gid to be a safe one.  If we can't do that, then
574              print a warning.  We change the gid before the uid so that we
575              change the gid while still root. */
576           if (setgid (p->pw_gid) != 0)
577             {
578               hack_uid_errno = errno;
579               sprintf (hack_uid_error, "couldn't set gid to %s (%d)",
580                        (g ? g->gr_name : "???"), p->pw_gid);
581             }
582
583           /* Now change the uid to be a safe one. */
584           if (setuid (p->pw_uid) != 0)
585             {
586               hack_uid_errno = errno;
587               sprintf (hack_uid_error, "couldn't set uid to %s (%d)",
588                        p->pw_name, p->pw_uid);
589             }
590         }
591     }
592 #ifndef NO_LOCKING
593  else   /* disable locking if already being run as "someone else" */
594    {
595      struct passwd *p = getpwuid (getuid ());
596      if (!p ||
597          !strcmp (p->pw_name, "root") ||
598          !strcmp (p->pw_name, "nobody") ||
599          !strcmp (p->pw_name, "daemon") ||
600          !strcmp (p->pw_name, "bin") ||
601          !strcmp (p->pw_name, "sys"))
602        {
603          locking_disabled_p = True;
604          nolock_reason = hack_uid_buf;
605          sprintf (nolock_reason, "running as %s", p->pw_name);
606        }
607    }
608 #endif /* NO_LOCKING */
609 }
610
611 void
612 hack_uid_warn ()
613 {
614   if (! hack_uid_error)
615     ;
616   else if (hack_uid_errno == 0)
617     {
618       if (verbose_p)
619         printf ("%s: %s\n", progname, hack_uid_error);
620     }
621   else
622     {
623       char buf [255];
624       sprintf (buf, "%s: %s%s", progname, (verbose_p ? "## " : ""),
625                hack_uid_error);
626       if (hack_uid_errno == -1)
627         fprintf (stderr, "%s\n", buf);
628       else
629         {
630           errno = hack_uid_errno;
631           perror (buf);
632         }
633     }
634 }
635
636 #endif /* !NO_SETUID */