1 /* xscreensaver, Copyright (c) 1991-1993 Jamie Zawinski <jwz@mcom.com>
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
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.
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
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
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.
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.)
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.)
56 #include <X11/Xlib.h> /* not used for much... */
62 #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 */
69 #include <processes.h>
75 #include <signal.h> /* for the signal names */
77 extern char **environ; /* why isn't this in some header file? */
80 #include <pwd.h> /* for getpwnam() and struct passwd */
81 #include <grp.h> /* for getgrgid() and struct group */
82 #endif /* NO_SETUID */
84 #if !defined(SIGCHLD) && defined(SIGCLD)
85 #define SIGCHLD SIGCLD
89 extern int putenv (/* const char * */); /* getenv() is in stdlib.h... */
90 extern int kill (pid_t, int); /* signal() is in sys/signal.h... */
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 */
101 #include "xscreensaver.h"
103 /* this must be `sh', not whatever $SHELL happens to be. */
105 static pid_t pid = 0;
107 int screenhacks_count;
108 int current_hack = -1;
111 Bool locking_disabled_p = False;
112 char *nolock_reason = 0;
113 int nice_inferior = 0;
115 extern Bool demo_mode_p;
119 exec_screenhack (char *command)
121 exec_screenhack (command)
130 /* Close this fork's version of the display's fd. It will open its own. */
131 close (ConnectionNumber (dpy));
133 /* I don't believe what a sorry excuse for an operating system UNIX is!
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.
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.)
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.
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.
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.
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
163 command = (char *) malloc (strlen (tmp) + 6);
164 memcpy (command, "exec ", 5);
165 memcpy (command + 5, tmp, strlen (tmp) + 1);
168 /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
178 printf ("%s: spawning \"%s\" in pid %d.\n", progname, command, getpid ());
180 #if defined(SYSV) || defined(SVR4) || defined(__hpux) || defined(VMS)
182 int old_nice = nice (0);
183 int n = nice_inferior - old_nice;
185 if (nice (n) == -1 && errno != 0)
187 sprintf (buf, "%s: %snice(%d) failed", progname,
188 (verbose_p ? "## " : ""), n);
194 if (setpriority (PRIO_PROCESS, getpid(), nice_inferior) != 0)
196 sprintf (buf, "%s: %ssetpriority(PRIO_PROCESS, %d, %d) failed",
197 progname, (verbose_p ? "## " : ""), getpid(), nice_inferior);
200 #else /* !PRIO_PROCESS */
201 if (nice_inferior != 0)
203 "%s: %sdon't know how to change process priority on this system.\n",
204 progname, (verbose_p ? "## " : ""));
205 #endif /* !PRIO_PROCESS */
208 /* Now overlay the current process with /bin/sh running the command.
209 If this returns, it's an error.
212 execve (av [0], av, environ);
217 sprintf (buf, "%s: %sexecve() failed", progname, (verbose_p ? "## " : ""));
219 exit (1); /* Note that this only exits a child fork. */
222 /* to avoid a race between the main thread and the SIGCHLD handler */
223 static int killing = 0;
224 static Bool suspending = False;
226 static char *current_hack_name P((void));
230 await_child_death (Bool killed)
232 await_child_death (killed)
236 Bool suspended_p = False;
248 kid = waitpid (pid, &status, WUNTRACED);
250 while (kid == -1 && errno == EINTR);
254 if (WIFEXITED (status))
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.
265 if (exit_status != 0 && (verbose_p || (! killed)))
267 "%s: %schild pid %d (%s) exited abnormally (code %d).\n",
268 progname, (verbose_p ? "## " : ""),
269 pid, current_hack_name (), exit_status);
271 printf ("%s: child pid %d (%s) exited normally.\n",
272 progname, pid, current_hack_name ());
274 else if (WIFSIGNALED (status))
276 if (!killed || WTERMSIG (status) != SIGTERM)
278 "%s: %schild pid %d (%s) terminated with signal %d!\n",
279 progname, (verbose_p ? "## " : ""),
280 pid, current_hack_name (), WTERMSIG (status));
282 printf ("%s: child pid %d (%s) terminated with SIGTERM.\n",
283 progname, pid, current_hack_name ());
288 suspending = False; /* complain if it happens twice */
290 else if (WIFSTOPPED (status))
293 fprintf (stderr, "%s: %schild pid %d (%s) stopped with signal %d!\n",
294 progname, (verbose_p ? "## " : ""), pid,
295 current_hack_name (), WSTOPSIG (status));
298 fprintf (stderr, "%s: %schild pid %d (%s) died in a mysterious way!",
299 progname, (verbose_p ? "## " : ""), pid, current_hack_name());
302 fprintf (stderr, "%s: %swaitpid(%d, ...) says there are no kids? (%d)\n",
303 progname, (verbose_p ? "## " : ""), pid, kid);
305 fprintf (stderr, "%s: %swaitpid(%d, ...) says proc %d died, not %d?\n",
306 progname, (verbose_p ? "## " : ""), pid, kid, pid);
308 if (suspended_p != True)
316 static char chn [1024];
317 char *hack = (demo_mode_p ? demo_hack : screenhacks [current_hack]);
319 for (i = 0; hack [i] != 0 && hack [i] != ' ' && hack [i] != '\t'; i++)
327 sigchld_handler (sig)
334 await_child_death (False);
343 if (((int) signal (SIGCHLD, sigchld_handler)) == -1)
346 sprintf (buf, "%s: %scouldn't catch SIGCHLD", progname,
347 (verbose_p ? "## " : ""));
354 extern void raise_window P((Bool inhibit_fade, Bool between_hacks_p));
357 spawn_screenhack (first_time_p)
360 raise_window (first_time_p, True);
363 if (screenhacks_count || demo_mode_p)
375 if (screenhacks_count == 1)
377 else if (next_mode_p == 1)
378 new_hack = (current_hack + 1) % screenhacks_count,
380 else if (next_mode_p == 2)
382 new_hack = ((current_hack + screenhacks_count - 1)
383 % screenhacks_count);
387 while ((new_hack = random () % screenhacks_count) == current_hack)
389 current_hack = new_hack;
390 hack = screenhacks[current_hack];
393 switch (forked = fork ())
396 sprintf (buf, "%s: %scouldn't fork",
397 progname, (verbose_p ? "## " : ""));
399 restore_real_vroot ();
402 exec_screenhack (hack); /* this does not return */
417 if (kill (pid, SIGTERM) < 0)
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.
433 await_child_death (False);
438 sprintf (buf, "%s: %scouldn't kill child process %d", progname,
439 (verbose_p ? "## " : ""), pid);
446 printf ("%s: killing pid %d.\n", progname, pid);
447 await_child_death (True);
453 suspend_screenhack (suspend_p)
457 suspending = suspend_p;
461 else if (kill (pid, (suspend_p ? SIGSTOP : SIGCONT)) < 0)
464 sprintf (buf, "%s: %scouldn't %s child process %d", progname,
465 (verbose_p ? "## " : ""),
466 (suspend_p ? "suspend" : "resume"),
471 printf ("%s: %s pid %d.\n", progname,
472 (suspend_p ? "suspending" : "resuming"), pid);
477 /* Restarting the xscreensaver process from scratch. */
479 static char **saved_argv;
482 save_argv (argc, argv)
486 saved_argv = (char **) malloc ((argc + 2) * sizeof (char *));
487 saved_argv [argc] = 0;
490 int i = strlen (argv [argc]) + 1;
491 saved_argv [argc] = (char *) malloc (i);
492 memcpy (saved_argv [argc], argv [argc], i);
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"),
521 demo_mode_restart_process ()
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;
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.)
541 sprintf (buf, "DISPLAY=%s", DisplayString (dpy));
543 s = (char *) malloc (i+1);
544 strncpy (s, buf, i+1);
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.)
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.
564 static int hack_uid_errno;
565 static char hack_uid_buf [255], *hack_uid_error;
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...)
582 /* If we're being run as root (as from xdm) then switch the user id
583 to something safe. */
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.)
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");
599 hack_uid_error = "couldn't find safe uid; running as root.";
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);
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)
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);
619 /* Now change the uid to be a safe one. */
620 if (setuid (p->pw_uid) != 0)
622 hack_uid_errno = errno;
623 sprintf (hack_uid_error, "couldn't set uid to %s (%d)",
624 p->pw_name, p->pw_uid);
629 else /* disable locking if already being run as "someone else" */
631 struct passwd *p = getpwuid (getuid ());
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"))
639 locking_disabled_p = True;
640 nolock_reason = hack_uid_buf;
641 sprintf (nolock_reason, "running as %s", p->pw_name);
644 #endif /* NO_LOCKING */
650 if (! hack_uid_error)
652 else if (hack_uid_errno == 0)
655 printf ("%s: %s\n", progname, hack_uid_error);
660 sprintf (buf, "%s: %s%s", progname, (verbose_p ? "## " : ""),
662 if (hack_uid_errno == -1)
663 fprintf (stderr, "%s\n", buf);
666 errno = hack_uid_errno;
672 #endif /* !NO_SETUID */