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