1 /* xscreensaver, Copyright (c) 1991, 1992, 1993, 1995
2 * Jamie Zawinski <jwz@netscape.com>
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
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.
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
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
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.
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.)
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.)
57 #include <X11/Xlib.h> /* not used for much... */
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 */
68 extern char **environ; /* why isn't this in some header file? */
71 #include <pwd.h> /* for getpwnam() and struct passwd */
72 #include <grp.h> /* for getgrgid() and struct group */
73 #endif /* NO_SETUID */
75 #if !defined(SIGCHLD) && defined(SIGCLD)
76 #define SIGCHLD SIGCLD
80 extern int putenv (/* const char * */); /* getenv() is in stdlib.h... */
81 extern int kill (pid_t, int); /* signal() is in sys/signal.h... */
85 #include "xscreensaver.h"
87 /* this must be `sh', not whatever $SHELL happens to be. */
91 int screenhacks_count;
92 int current_hack = -1;
95 Bool locking_disabled_p = False;
96 char *nolock_reason = 0;
97 int nice_inferior = 0;
99 extern Bool demo_mode_p;
103 exec_screenhack (char *command)
105 exec_screenhack (command)
114 /* Close this fork's version of the display's fd. It will open its own. */
115 close (ConnectionNumber (dpy));
117 /* I don't believe what a sorry excuse for an operating system UNIX is!
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.
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.)
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.
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.
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.
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
146 command = (char *) malloc (strlen (tmp) + 6);
147 memcpy (command, "exec ", 5);
148 memcpy (command + 5, tmp, strlen (tmp) + 1);
150 /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
157 printf ("%s: spawning \"%s\" in pid %d.\n", progname, command, getpid ());
159 #if defined(SYSV) || defined(SVR4) || defined(__hpux)
161 int old_nice = nice (0);
162 int n = nice_inferior - old_nice;
164 if (nice (n) == -1 && errno != 0)
166 sprintf (buf, "%s: %snice(%d) failed", progname,
167 (verbose_p ? "## " : ""), n);
173 if (setpriority (PRIO_PROCESS, getpid(), nice_inferior) != 0)
175 sprintf (buf, "%s: %ssetpriority(PRIO_PROCESS, %d, %d) failed",
176 progname, (verbose_p ? "## " : ""), getpid(), nice_inferior);
179 #else /* !PRIO_PROCESS */
180 if (nice_inferior != 0)
182 "%s: %sdon't know how to change process priority on this system.\n",
183 progname, (verbose_p ? "## " : ""));
184 #endif /* !PRIO_PROCESS */
187 /* Now overlay the current process with /bin/sh running the command.
188 If this returns, it's an error.
190 execve (av [0], av, environ);
192 sprintf (buf, "%s: %sexecve() failed", progname, (verbose_p ? "## " : ""));
194 exit (1); /* Note that this only exits a child fork. */
197 /* to avoid a race between the main thread and the SIGCHLD handler */
198 static int killing = 0;
199 static Bool suspending = False;
201 static char *current_hack_name P((void));
205 await_child_death (Bool killed)
207 await_child_death (killed)
211 Bool suspended_p = False;
220 kid = waitpid (pid, &status, WUNTRACED);
222 while (kid == -1 && errno == EINTR);
226 if (WIFEXITED (status))
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.
237 if (exit_status != 0 && (verbose_p || (! killed)))
239 "%s: %schild pid %d (%s) exited abnormally (code %d).\n",
240 progname, (verbose_p ? "## " : ""),
241 pid, current_hack_name (), exit_status);
243 printf ("%s: child pid %d (%s) exited normally.\n",
244 progname, pid, current_hack_name ());
246 else if (WIFSIGNALED (status))
248 if (!killed || WTERMSIG (status) != SIGTERM)
250 "%s: %schild pid %d (%s) terminated with signal %d!\n",
251 progname, (verbose_p ? "## " : ""),
252 pid, current_hack_name (), WTERMSIG (status));
254 printf ("%s: child pid %d (%s) terminated with SIGTERM.\n",
255 progname, pid, current_hack_name ());
260 suspending = False; /* complain if it happens twice */
262 else if (WIFSTOPPED (status))
265 fprintf (stderr, "%s: %schild pid %d (%s) stopped with signal %d!\n",
266 progname, (verbose_p ? "## " : ""), pid,
267 current_hack_name (), WSTOPSIG (status));
270 fprintf (stderr, "%s: %schild pid %d (%s) died in a mysterious way!",
271 progname, (verbose_p ? "## " : ""), pid, current_hack_name());
274 fprintf (stderr, "%s: %swaitpid(%d, ...) says there are no kids? (%d)\n",
275 progname, (verbose_p ? "## " : ""), pid, kid);
277 fprintf (stderr, "%s: %swaitpid(%d, ...) says proc %d died, not %d?\n",
278 progname, (verbose_p ? "## " : ""), pid, kid, pid);
280 if (suspended_p != True)
287 static char chn [1024];
288 char *hack = (demo_mode_p ? demo_hack : screenhacks [current_hack]);
290 for (i = 0; hack [i] != 0 && hack [i] != ' ' && hack [i] != '\t'; i++)
298 sigchld_handler (sig)
305 await_child_death (False);
314 if (((int) signal (SIGCHLD, sigchld_handler)) == -1)
317 sprintf (buf, "%s: %scouldn't catch SIGCHLD", progname,
318 (verbose_p ? "## " : ""));
325 extern void raise_window P((Bool inhibit_fade, Bool between_hacks_p));
328 spawn_screenhack (first_time_p)
331 raise_window (first_time_p, True);
334 if (screenhacks_count || demo_mode_p)
346 if (screenhacks_count == 1)
348 else if (next_mode_p == 1)
349 new_hack = (current_hack + 1) % screenhacks_count,
351 else if (next_mode_p == 2)
353 new_hack = ((current_hack + screenhacks_count - 1)
354 % screenhacks_count);
358 while ((new_hack = random () % screenhacks_count) == current_hack)
360 current_hack = new_hack;
361 hack = screenhacks[current_hack];
364 switch (forked = fork ())
367 sprintf (buf, "%s: %scouldn't fork",
368 progname, (verbose_p ? "## " : ""));
370 restore_real_vroot ();
373 exec_screenhack (hack); /* this does not return */
388 if (kill (pid, SIGTERM) < 0)
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.
404 await_child_death (False);
409 sprintf (buf, "%s: %scouldn't kill child process %d", progname,
410 (verbose_p ? "## " : ""), pid);
417 printf ("%s: killing pid %d.\n", progname, pid);
418 await_child_death (True);
424 suspend_screenhack (suspend_p)
428 suspending = suspend_p;
431 else if (kill (pid, (suspend_p ? SIGSTOP : SIGCONT)) < 0)
434 sprintf (buf, "%s: %scouldn't %s child process %d", progname,
435 (verbose_p ? "## " : ""),
436 (suspend_p ? "suspend" : "resume"),
441 printf ("%s: %s pid %d.\n", progname,
442 (suspend_p ? "suspending" : "resuming"), pid);
446 /* Restarting the xscreensaver process from scratch. */
448 static char **saved_argv;
451 save_argv (argc, argv)
455 saved_argv = (char **) malloc ((argc + 2) * sizeof (char *));
456 saved_argv [argc] = 0;
459 int i = strlen (argv [argc]) + 1;
460 saved_argv [argc] = (char *) malloc (i);
461 memcpy (saved_argv [argc], argv [argc], i);
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"),
490 demo_mode_restart_process ()
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;
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.)
510 sprintf (buf, "DISPLAY=%s", DisplayString (dpy));
512 s = (char *) malloc (i+1);
513 strncpy (s, buf, i+1);
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.)
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.
531 static int hack_uid_errno;
532 static char hack_uid_buf [255], *hack_uid_error;
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...)
549 /* If we're being run as root (as from xdm) then switch the user id
550 to something safe. */
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.)
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");
566 hack_uid_error = "couldn't find safe uid; running as root.";
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);
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)
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);
586 /* Now change the uid to be a safe one. */
587 if (setuid (p->pw_uid) != 0)
589 hack_uid_errno = errno;
590 sprintf (hack_uid_error, "couldn't set uid to %s (%d)",
591 p->pw_name, p->pw_uid);
596 else /* disable locking if already being run as "someone else" */
598 struct passwd *p = getpwuid (getuid ());
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"))
606 locking_disabled_p = True;
607 nolock_reason = hack_uid_buf;
608 sprintf (nolock_reason, "running as %s", p->pw_name);
611 #endif /* NO_LOCKING */
617 if (! hack_uid_error)
619 else if (hack_uid_errno == 0)
622 printf ("%s: %s\n", progname, hack_uid_error);
627 sprintf (buf, "%s: %s%s", progname, (verbose_p ? "## " : ""),
629 if (hack_uid_errno == -1)
630 fprintf (stderr, "%s\n", buf);
633 errno = hack_uid_errno;
639 #endif /* !NO_SETUID */