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