1 /* xscreensaver, Copyright (c) 1991-1993 Jamie Zawinski <jwz@lucid.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 */
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 */
67 extern char **environ; /* why isn't this in some header file? */
70 #include <pwd.h> /* for getpwnam() and struct passwd */
71 #include <grp.h> /* for getgrgid() and struct group */
72 #endif /* NO_SETUID */
74 #if !defined(SIGCHLD) && defined(SIGCLD)
75 #define SIGCHLD SIGCLD
79 extern int putenv (/* const char * */); /* getenv() is in stdlib.h... */
80 extern int kill (pid_t, int); /* signal() is in sys/signal.h... */
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 */
91 #include "xscreensaver.h"
93 /* this must be `sh', not whatever $SHELL happens to be. */
97 int screenhacks_count;
98 int current_hack = -1;
101 Bool locking_disabled_p = False;
102 char *nolock_reason = 0;
103 int nice_inferior = 0;
105 extern Bool demo_mode_p;
108 exec_screenhack (command)
116 /* Close this fork's version of the display's fd. It will open its own. */
117 close (ConnectionNumber (dpy));
119 /* I don't believe what a sorry excuse for an operating system UNIX is!
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.
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.)
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.
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.
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.
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
148 command = (char *) malloc (strlen (tmp) + 6);
149 memcpy (command, "exec ", 5);
150 memcpy (command + 5, tmp, strlen (tmp) + 1);
152 /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
159 printf ("%s: spawning \"%s\" in pid %d.\n", progname, command, getpid ());
161 #if defined(SYSV) || defined(SVR4) || defined(__hpux)
163 int old_nice = nice (0);
164 int n = nice_inferior - old_nice;
166 if (nice (n) == -1 && errno != 0)
168 sprintf (buf, "%s: %snice(%d) failed", progname,
169 (verbose_p ? "## " : ""), n);
175 if (setpriority (PRIO_PROCESS, getpid(), nice_inferior) != 0)
177 sprintf (buf, "%s: %ssetpriority(PRIO_PROCESS, %d, %d) failed",
178 progname, (verbose_p ? "## " : ""), getpid(), nice_inferior);
181 #else /* !PRIO_PROCESS */
182 if (nice_inferior != 0)
184 "%s: %sdon't know how to change process priority on this system.\n",
185 progname, (verbose_p ? "## " : ""));
186 #endif /* !PRIO_PROCESS */
189 /* Now overlay the current process with /bin/sh running the command.
190 If this returns, it's an error.
192 execve (av [0], av, environ);
194 sprintf (buf, "%s: %sexecve() failed", progname, (verbose_p ? "## " : ""));
196 exit (1); /* Note that this only exits a child fork. */
199 /* to avoid a race between the main thread and the SIGCHLD handler */
200 static int killing = 0;
201 static Bool suspending = False;
203 static char *current_hack_name P((void));
206 await_child_death (killed)
209 Bool suspended_p = False;
218 kid = waitpid (pid, &status, WUNTRACED);
220 while (kid == -1 && errno == EINTR);
224 if (WIFEXITED (status))
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.
235 if (exit_status != 0 && (verbose_p || (! killed)))
237 "%s: %schild pid %d (%s) exited abnormally (code %d).\n",
238 progname, (verbose_p ? "## " : ""),
239 pid, current_hack_name (), exit_status);
241 printf ("%s: child pid %d (%s) exited normally.\n",
242 progname, pid, current_hack_name ());
244 else if (WIFSIGNALED (status))
246 if (!killed || WTERMSIG (status) != SIGTERM)
248 "%s: %schild pid %d (%s) terminated with signal %d!\n",
249 progname, (verbose_p ? "## " : ""),
250 pid, current_hack_name (), WTERMSIG (status));
252 printf ("%s: child pid %d (%s) terminated with SIGTERM.\n",
253 progname, pid, current_hack_name ());
258 suspending = False; /* complain if it happens twice */
260 else if (WIFSTOPPED (status))
263 fprintf (stderr, "%s: %schild pid %d (%s) stopped with signal %d!\n",
264 progname, (verbose_p ? "## " : ""), pid,
265 current_hack_name (), WSTOPSIG (status));
268 fprintf (stderr, "%s: %schild pid %d (%s) died in a mysterious way!",
269 progname, (verbose_p ? "## " : ""), pid, current_hack_name());
272 fprintf (stderr, "%s: %swaitpid(%d, ...) says there are no kids? (%d)\n",
273 progname, (verbose_p ? "## " : ""), pid, kid);
275 fprintf (stderr, "%s: %swaitpid(%d, ...) says proc %d died, not %d?\n",
276 progname, (verbose_p ? "## " : ""), pid, kid, pid);
278 if (suspended_p != True)
285 static char chn [1024];
286 char *hack = (demo_mode_p ? demo_hack : screenhacks [current_hack]);
288 for (i = 0; hack [i] != 0 && hack [i] != ' ' && hack [i] != '\t'; i++)
296 sigchld_handler (sig)
303 await_child_death (False);
312 if (((int) signal (SIGCHLD, sigchld_handler)) == -1)
315 sprintf (buf, "%s: %scouldn't catch SIGCHLD", progname,
316 (verbose_p ? "## " : ""));
323 extern void raise_window P((Bool inhibit_fade, Bool between_hacks_p));
326 spawn_screenhack (first_time_p)
329 raise_window (first_time_p, True);
332 if (screenhacks_count || demo_mode_p)
344 if (screenhacks_count == 1)
346 else if (next_mode_p == 1)
347 new_hack = (current_hack + 1) % screenhacks_count,
349 else if (next_mode_p == 2)
351 new_hack = ((current_hack + screenhacks_count - 1)
352 % screenhacks_count);
356 while ((new_hack = random () % screenhacks_count) == current_hack)
358 current_hack = new_hack;
359 hack = screenhacks[current_hack];
362 switch (forked = fork ())
365 sprintf (buf, "%s: %scouldn't fork",
366 progname, (verbose_p ? "## " : ""));
368 restore_real_vroot ();
371 exec_screenhack (hack); /* this does not return */
386 if (kill (pid, SIGTERM) < 0)
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.
402 await_child_death (False);
407 sprintf (buf, "%s: %scouldn't kill child process %d", progname,
408 (verbose_p ? "## " : ""), pid);
415 printf ("%s: killing pid %d.\n", progname, pid);
416 await_child_death (True);
422 suspend_screenhack (suspend_p)
426 suspending = suspend_p;
429 else if (kill (pid, (suspend_p ? SIGSTOP : SIGCONT)) < 0)
432 sprintf (buf, "%s: %scouldn't %s child process %d", progname,
433 (verbose_p ? "## " : ""),
434 (suspend_p ? "suspend" : "resume"),
439 printf ("%s: %s pid %d.\n", progname,
440 (suspend_p ? "suspending" : "resuming"), pid);
444 /* Restarting the xscreensaver process from scratch. */
446 static char **saved_argv;
449 save_argv (argc, argv)
453 saved_argv = (char **) malloc ((argc + 2) * sizeof (char *));
454 saved_argv [argc] = 0;
457 int i = strlen (argv [argc]) + 1;
458 saved_argv [argc] = (char *) malloc (i);
459 memcpy (saved_argv [argc], argv [argc], i);
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"),
488 demo_mode_restart_process ()
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;
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.)
508 sprintf (buf, "DISPLAY=%s", DisplayString (dpy));
510 s = (char *) malloc (i+1);
511 strncpy (s, buf, i+1);
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.)
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.
529 static int hack_uid_errno;
530 static char hack_uid_buf [255], *hack_uid_error;
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...)
547 /* If we're being run as root (as from xdm) then switch the user id
548 to something safe. */
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.)
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");
564 hack_uid_error = "couldn't find safe uid; running as root.";
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);
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)
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);
584 /* Now change the uid to be a safe one. */
585 if (setuid (p->pw_uid) != 0)
587 hack_uid_errno = errno;
588 sprintf (hack_uid_error, "couldn't set uid to %s (%d)",
589 p->pw_name, p->pw_uid);
594 else /* disable locking if already being run as "someone else" */
596 struct passwd *p = getpwuid (getuid ());
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"))
604 locking_disabled_p = True;
605 nolock_reason = hack_uid_buf;
606 sprintf (nolock_reason, "running as %s", p->pw_name);
609 #endif /* NO_LOCKING */
615 if (! hack_uid_error)
617 else if (hack_uid_errno == 0)
620 printf ("%s: %s\n", progname, hack_uid_error);
625 sprintf (buf, "%s: %s%s", progname, (verbose_p ? "## " : ""),
627 if (hack_uid_errno == -1)
628 fprintf (stderr, "%s\n", buf);
631 errno = hack_uid_errno;
637 #endif /* !NO_SETUID */