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