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... */
83 # if defined(SVR4) || defined(SYSV)
84 # define random() rand()
85 # else /* !totally-losing-SYSV */
86 extern long random(); /* rand() is in stdlib.h... */
87 # endif /* !totally-losing-SYSV */
90 #include "xscreensaver.h"
92 /* this must be `sh', not whatever $SHELL happens to be. */
96 int screenhacks_count;
97 int current_hack = -1;
100 Bool locking_disabled_p = False;
101 char *nolock_reason = 0;
102 int nice_inferior = 0;
104 extern Bool demo_mode_p;
107 exec_screenhack (command)
115 /* Close this fork's version of the display's fd. It will open its own. */
116 close (ConnectionNumber (dpy));
118 /* I don't believe what a sorry excuse for an operating system UNIX is!
120 - I want to spawn a process.
121 - I want to know it's pid so that I can kill it.
122 - I would like to receive a message when it dies of natural causes.
123 - I want the spawned process to have user-specified arguments.
125 The *only way* to parse arguments the way the shells do is to run a
126 shell (or duplicate what they do, which would be a *lot* of code.)
128 The *only way* to know the pid of the process is to fork() and exec()
129 it in the spawned side of the fork.
131 But if you're running a shell to parse your arguments, this gives you
132 the pid of the SHELL, not the pid of the PROCESS that you're actually
133 interested in, which is an *inferior* of the shell. This also means
134 that the SIGCHLD you get applies to the shell, not its inferior.
136 So, the only solution other than implementing an argument parser here
137 is to force the shell to exec() its inferior. What a fucking hack!
138 We prepend "exec " to the command string.
140 (Actually, Clint Wong <clint@jts.com> points out that process groups
141 might be used to take care of this problem; this may be worth considering
142 some day, except that, 1: this code works now, so why fix it, and 2: from
143 what I've seen in Emacs, dealing with process groups isn't especially
147 command = (char *) malloc (strlen (tmp) + 6);
148 memcpy (command, "exec ", 5);
149 memcpy (command + 5, tmp, strlen (tmp) + 1);
151 /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
158 printf ("%s: spawning \"%s\" in pid %d.\n", progname, command, getpid ());
160 #if defined(SYSV) || defined(SVR4) || defined(__hpux)
162 int old_nice = nice (0);
163 int n = nice_inferior - old_nice;
165 if (nice (n) == -1 && errno != 0)
167 sprintf (buf, "%s: %snice(%d) failed", progname,
168 (verbose_p ? "## " : ""), n);
174 if (setpriority (PRIO_PROCESS, getpid(), nice_inferior) != 0)
176 sprintf (buf, "%s: %ssetpriority(PRIO_PROCESS, %d, %d) failed",
177 progname, (verbose_p ? "## " : ""), getpid(), nice_inferior);
180 #else /* !PRIO_PROCESS */
181 if (nice_inferior != 0)
183 "%s: %sdon't know how to change process priority on this system.\n",
184 progname, (verbose_p ? "## " : ""));
185 #endif /* !PRIO_PROCESS */
188 /* Now overlay the current process with /bin/sh running the command.
189 If this returns, it's an error.
191 execve (av [0], av, environ);
193 sprintf (buf, "%s: %sexecve() failed", progname, (verbose_p ? "## " : ""));
195 exit (1); /* Note that this only exits a child fork. */
198 /* to avoid a race between the main thread and the SIGCHLD handler */
199 static int killing = 0;
200 static Bool suspending = False;
202 static char *current_hack_name P((void));
205 await_child_death (killed)
208 Bool suspended_p = False;
217 kid = waitpid (pid, &status, WUNTRACED);
219 while (kid == -1 && errno == EINTR);
223 if (WIFEXITED (status))
225 int exit_status = WEXITSTATUS (status);
226 if (exit_status & 0x80)
227 exit_status |= ~0xFF;
228 /* One might assume that exiting with non-0 means something went
229 wrong. But that loser xswarm exits with the code that it was
230 killed with, so it *always* exits abnormally. Treat abnormal
231 exits as "normal" (don't mention them) if we've just killed
232 the subprocess. But mention them if they happen on their own.
234 if (exit_status != 0 && (verbose_p || (! killed)))
236 "%s: %schild pid %d (%s) exited abnormally (code %d).\n",
237 progname, (verbose_p ? "## " : ""),
238 pid, current_hack_name (), exit_status);
240 printf ("%s: child pid %d (%s) exited normally.\n",
241 progname, pid, current_hack_name ());
243 else if (WIFSIGNALED (status))
245 if (!killed || WTERMSIG (status) != SIGTERM)
247 "%s: %schild pid %d (%s) terminated with signal %d!\n",
248 progname, (verbose_p ? "## " : ""),
249 pid, current_hack_name (), WTERMSIG (status));
251 printf ("%s: child pid %d (%s) terminated with SIGTERM.\n",
252 progname, pid, current_hack_name ());
257 suspending = False; /* complain if it happens twice */
259 else if (WIFSTOPPED (status))
262 fprintf (stderr, "%s: %schild pid %d (%s) stopped with signal %d!\n",
263 progname, (verbose_p ? "## " : ""), pid,
264 current_hack_name (), WSTOPSIG (status));
267 fprintf (stderr, "%s: %schild pid %d (%s) died in a mysterious way!",
268 progname, (verbose_p ? "## " : ""), pid, current_hack_name());
271 fprintf (stderr, "%s: %swaitpid(%d, ...) says there are no kids? (%d)\n",
272 progname, (verbose_p ? "## " : ""), pid, kid);
274 fprintf (stderr, "%s: %swaitpid(%d, ...) says proc %d died, not %d?\n",
275 progname, (verbose_p ? "## " : ""), pid, kid, pid);
277 if (suspended_p != True)
284 static char chn [1024];
285 char *hack = (demo_mode_p ? demo_hack : screenhacks [current_hack]);
287 for (i = 0; hack [i] != 0 && hack [i] != ' ' && hack [i] != '\t'; i++)
295 sigchld_handler (sig)
302 await_child_death (False);
311 if (((int) signal (SIGCHLD, sigchld_handler)) == -1)
314 sprintf (buf, "%s: %scouldn't catch SIGCHLD", progname,
315 (verbose_p ? "## " : ""));
322 extern void raise_window P((Bool inhibit_fade, Bool between_hacks_p));
325 spawn_screenhack (first_time_p)
328 raise_window (first_time_p, True);
331 if (screenhacks_count || demo_mode_p)
343 if (screenhacks_count == 1)
345 else if (next_mode_p == 1)
346 new_hack = (current_hack + 1) % screenhacks_count,
348 else if (next_mode_p == 2)
350 new_hack = ((current_hack + screenhacks_count - 1)
351 % screenhacks_count);
355 while ((new_hack = random () % screenhacks_count) == current_hack)
357 current_hack = new_hack;
358 hack = screenhacks[current_hack];
361 switch (forked = fork ())
364 sprintf (buf, "%s: %scouldn't fork",
365 progname, (verbose_p ? "## " : ""));
367 restore_real_vroot ();
370 exec_screenhack (hack); /* this does not return */
385 if (kill (pid, SIGTERM) < 0)
389 /* Sometimes we don't get a SIGCHLD at all! WTF?
390 It's a race condition. It looks to me like what's happening is
391 something like: a subprocess dies of natural causes. There is a
392 small window between when the process dies and when the SIGCHLD
393 is (would have been) delivered. If we happen to try to kill()
394 the process during that time, the kill() fails, because the
395 process is already dead. But! no SIGCHLD is delivered (perhaps
396 because the failed kill() has reset some state in the kernel?)
397 Anyway, if kill() says "No such process", then we have to wait()
398 for it anyway, because the process has already become a zombie.
401 await_child_death (False);
406 sprintf (buf, "%s: %scouldn't kill child process %d", progname,
407 (verbose_p ? "## " : ""), pid);
414 printf ("%s: killing pid %d.\n", progname, pid);
415 await_child_death (True);
421 suspend_screenhack (suspend_p)
425 suspending = suspend_p;
428 else if (kill (pid, (suspend_p ? SIGSTOP : SIGCONT)) < 0)
431 sprintf (buf, "%s: %scouldn't %s child process %d", progname,
432 (verbose_p ? "## " : ""),
433 (suspend_p ? "suspend" : "resume"),
438 printf ("%s: %s pid %d.\n", progname,
439 (suspend_p ? "suspending" : "resuming"), pid);
443 /* Restarting the xscreensaver process from scratch. */
445 static char **saved_argv;
448 save_argv (argc, argv)
452 saved_argv = (char **) malloc ((argc + 2) * sizeof (char *));
453 saved_argv [argc] = 0;
456 int i = strlen (argv [argc]) + 1;
457 saved_argv [argc] = (char *) malloc (i);
458 memcpy (saved_argv [argc], argv [argc], i);
468 execvp (saved_argv [0], saved_argv);
469 fprintf (stderr, "%s: %scould not restart process: %s (%d)\n",
470 progname, (verbose_p ? "## " : ""),
471 (errno == E2BIG ? "arglist too big" :
472 errno == EACCES ? "could not execute" :
473 errno == EFAULT ? "memory fault" :
474 errno == EIO ? "I/O error" :
475 errno == ENAMETOOLONG ? "name too long" :
476 errno == ELOOP ? "too many symbolic links" :
477 errno == ENOENT ? "file no longer exists" :
478 errno == ENOTDIR ? "directory no longer exists" :
479 errno == ENOEXEC ? "bad executable file" :
480 errno == ENOMEM ? "out of memory" :
481 "execvp() returned unknown error code"),
487 demo_mode_restart_process ()
490 for (i = 0; saved_argv [i]; i++);
491 /* add the -demo switch; save_argv() left room for this. */
492 saved_argv [i] = "-demo";
493 saved_argv [i+1] = 0;
500 /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
501 the spawned processes inherit is the same as the value of -display passed
502 in on our command line (which is not necessarily the same as what our
503 $DISPLAY variable is.)
507 sprintf (buf, "DISPLAY=%s", DisplayString (dpy));
509 s = (char *) malloc (i+1);
510 strncpy (s, buf, i+1);
516 /* Change the uid/gid of the screensaver process, so that it is safe for it
517 to run setuid root (which it needs to do on some systems to read the
518 encrypted passwords from the passwd file.)
520 hack_uid() is run before opening the X connection, so that XAuth works.
521 hack_uid_warn() is called after the connection is opened and the command
522 line arguments are parsed, so that the messages from hack_uid() get
523 printed after we know whether we're in `verbose' mode.
528 static int hack_uid_errno;
529 static char hack_uid_buf [255], *hack_uid_error;
534 /* If we've been run as setuid or setgid to someone else (most likely root)
535 turn off the extra permissions so that random user-specified programs
536 don't get special privileges. (On some systems it might be necessary
537 to install this as setuid root in order to read the passwd file to
538 implement lock-mode...)
546 /* If we're being run as root (as from xdm) then switch the user id
547 to something safe. */
551 /* Locking can't work when running as root, because we have no way of
552 knowing what the user id of the logged in user is (so we don't know
553 whose password to prompt for.)
555 locking_disabled_p = True;
556 nolock_reason = "running as root";
557 p = getpwnam ("nobody");
558 if (! p) p = getpwnam ("daemon");
559 if (! p) p = getpwnam ("bin");
560 if (! p) p = getpwnam ("sys");
563 hack_uid_error = "couldn't find safe uid; running as root.";
568 struct group *g = getgrgid (p->pw_gid);
569 hack_uid_error = hack_uid_buf;
570 sprintf (hack_uid_error, "changing uid/gid to %s/%s (%d/%d).",
571 p->pw_name, (g ? g->gr_name : "???"), p->pw_uid, p->pw_gid);
573 /* Change the gid to be a safe one. If we can't do that, then
574 print a warning. We change the gid before the uid so that we
575 change the gid while still root. */
576 if (setgid (p->pw_gid) != 0)
578 hack_uid_errno = errno;
579 sprintf (hack_uid_error, "couldn't set gid to %s (%d)",
580 (g ? g->gr_name : "???"), p->pw_gid);
583 /* Now change the uid to be a safe one. */
584 if (setuid (p->pw_uid) != 0)
586 hack_uid_errno = errno;
587 sprintf (hack_uid_error, "couldn't set uid to %s (%d)",
588 p->pw_name, p->pw_uid);
593 else /* disable locking if already being run as "someone else" */
595 struct passwd *p = getpwuid (getuid ());
597 !strcmp (p->pw_name, "root") ||
598 !strcmp (p->pw_name, "nobody") ||
599 !strcmp (p->pw_name, "daemon") ||
600 !strcmp (p->pw_name, "bin") ||
601 !strcmp (p->pw_name, "sys"))
603 locking_disabled_p = True;
604 nolock_reason = hack_uid_buf;
605 sprintf (nolock_reason, "running as %s", p->pw_name);
608 #endif /* NO_LOCKING */
614 if (! hack_uid_error)
616 else if (hack_uid_errno == 0)
619 printf ("%s: %s\n", progname, hack_uid_error);
624 sprintf (buf, "%s: %s%s", progname, (verbose_p ? "## " : ""),
626 if (hack_uid_errno == -1)
627 fprintf (stderr, "%s\n", buf);
630 errno = hack_uid_errno;
636 #endif /* !NO_SETUID */