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