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 */
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;
109 exec_screenhack (char *command)
111 exec_screenhack (command)
120 /* Close this fork's version of the display's fd. It will open its own. */
121 close (ConnectionNumber (dpy));
123 /* I don't believe what a sorry excuse for an operating system UNIX is!
125 - I want to spawn a process.
126 - I want to know it's pid so that I can kill it.
127 - I would like to receive a message when it dies of natural causes.
128 - I want the spawned process to have user-specified arguments.
130 The *only way* to parse arguments the way the shells do is to run a
131 shell (or duplicate what they do, which would be a *lot* of code.)
133 The *only way* to know the pid of the process is to fork() and exec()
134 it in the spawned side of the fork.
136 But if you're running a shell to parse your arguments, this gives you
137 the pid of the SHELL, not the pid of the PROCESS that you're actually
138 interested in, which is an *inferior* of the shell. This also means
139 that the SIGCHLD you get applies to the shell, not its inferior.
141 So, the only solution other than implementing an argument parser here
142 is to force the shell to exec() its inferior. What a fucking hack!
143 We prepend "exec " to the command string.
145 (Actually, Clint Wong <clint@jts.com> points out that process groups
146 might be used to take care of this problem; this may be worth considering
147 some day, except that, 1: this code works now, so why fix it, and 2: from
148 what I've seen in Emacs, dealing with process groups isn't especially
152 command = (char *) malloc (strlen (tmp) + 6);
153 memcpy (command, "exec ", 5);
154 memcpy (command + 5, tmp, strlen (tmp) + 1);
156 /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
163 printf ("%s: spawning \"%s\" in pid %d.\n", progname, command, getpid ());
165 #if defined(SYSV) || defined(SVR4) || defined(__hpux)
167 int old_nice = nice (0);
168 int n = nice_inferior - old_nice;
170 if (nice (n) == -1 && errno != 0)
172 sprintf (buf, "%s: %snice(%d) failed", progname,
173 (verbose_p ? "## " : ""), n);
179 if (setpriority (PRIO_PROCESS, getpid(), nice_inferior) != 0)
181 sprintf (buf, "%s: %ssetpriority(PRIO_PROCESS, %d, %d) failed",
182 progname, (verbose_p ? "## " : ""), getpid(), nice_inferior);
185 #else /* !PRIO_PROCESS */
186 if (nice_inferior != 0)
188 "%s: %sdon't know how to change process priority on this system.\n",
189 progname, (verbose_p ? "## " : ""));
190 #endif /* !PRIO_PROCESS */
193 /* Now overlay the current process with /bin/sh running the command.
194 If this returns, it's an error.
196 execve (av [0], av, environ);
198 sprintf (buf, "%s: %sexecve() failed", progname, (verbose_p ? "## " : ""));
200 exit (1); /* Note that this only exits a child fork. */
203 /* to avoid a race between the main thread and the SIGCHLD handler */
204 static int killing = 0;
205 static Bool suspending = False;
207 static char *current_hack_name P((void));
211 await_child_death (Bool killed)
213 await_child_death (killed)
217 Bool suspended_p = False;
226 kid = waitpid (pid, &status, WUNTRACED);
228 while (kid == -1 && errno == EINTR);
232 if (WIFEXITED (status))
234 int exit_status = WEXITSTATUS (status);
235 if (exit_status & 0x80)
236 exit_status |= ~0xFF;
237 /* One might assume that exiting with non-0 means something went
238 wrong. But that loser xswarm exits with the code that it was
239 killed with, so it *always* exits abnormally. Treat abnormal
240 exits as "normal" (don't mention them) if we've just killed
241 the subprocess. But mention them if they happen on their own.
243 if (exit_status != 0 && (verbose_p || (! killed)))
245 "%s: %schild pid %d (%s) exited abnormally (code %d).\n",
246 progname, (verbose_p ? "## " : ""),
247 pid, current_hack_name (), exit_status);
249 printf ("%s: child pid %d (%s) exited normally.\n",
250 progname, pid, current_hack_name ());
252 else if (WIFSIGNALED (status))
254 if (!killed || WTERMSIG (status) != SIGTERM)
256 "%s: %schild pid %d (%s) terminated with signal %d!\n",
257 progname, (verbose_p ? "## " : ""),
258 pid, current_hack_name (), WTERMSIG (status));
260 printf ("%s: child pid %d (%s) terminated with SIGTERM.\n",
261 progname, pid, current_hack_name ());
266 suspending = False; /* complain if it happens twice */
268 else if (WIFSTOPPED (status))
271 fprintf (stderr, "%s: %schild pid %d (%s) stopped with signal %d!\n",
272 progname, (verbose_p ? "## " : ""), pid,
273 current_hack_name (), WSTOPSIG (status));
276 fprintf (stderr, "%s: %schild pid %d (%s) died in a mysterious way!",
277 progname, (verbose_p ? "## " : ""), pid, current_hack_name());
280 fprintf (stderr, "%s: %swaitpid(%d, ...) says there are no kids? (%d)\n",
281 progname, (verbose_p ? "## " : ""), pid, kid);
283 fprintf (stderr, "%s: %swaitpid(%d, ...) says proc %d died, not %d?\n",
284 progname, (verbose_p ? "## " : ""), pid, kid, pid);
286 if (suspended_p != True)
293 static char chn [1024];
294 char *hack = (demo_mode_p ? demo_hack : screenhacks [current_hack]);
296 for (i = 0; hack [i] != 0 && hack [i] != ' ' && hack [i] != '\t'; i++)
304 sigchld_handler (sig)
311 await_child_death (False);
320 if (((int) signal (SIGCHLD, sigchld_handler)) == -1)
323 sprintf (buf, "%s: %scouldn't catch SIGCHLD", progname,
324 (verbose_p ? "## " : ""));
331 extern void raise_window P((Bool inhibit_fade, Bool between_hacks_p));
334 spawn_screenhack (first_time_p)
337 raise_window (first_time_p, True);
340 if (screenhacks_count || demo_mode_p)
352 if (screenhacks_count == 1)
354 else if (next_mode_p == 1)
355 new_hack = (current_hack + 1) % screenhacks_count,
357 else if (next_mode_p == 2)
359 new_hack = ((current_hack + screenhacks_count - 1)
360 % screenhacks_count);
364 while ((new_hack = random () % screenhacks_count) == current_hack)
366 current_hack = new_hack;
367 hack = screenhacks[current_hack];
370 switch (forked = fork ())
373 sprintf (buf, "%s: %scouldn't fork",
374 progname, (verbose_p ? "## " : ""));
376 restore_real_vroot ();
379 exec_screenhack (hack); /* this does not return */
394 if (kill (pid, SIGTERM) < 0)
398 /* Sometimes we don't get a SIGCHLD at all! WTF?
399 It's a race condition. It looks to me like what's happening is
400 something like: a subprocess dies of natural causes. There is a
401 small window between when the process dies and when the SIGCHLD
402 is (would have been) delivered. If we happen to try to kill()
403 the process during that time, the kill() fails, because the
404 process is already dead. But! no SIGCHLD is delivered (perhaps
405 because the failed kill() has reset some state in the kernel?)
406 Anyway, if kill() says "No such process", then we have to wait()
407 for it anyway, because the process has already become a zombie.
410 await_child_death (False);
415 sprintf (buf, "%s: %scouldn't kill child process %d", progname,
416 (verbose_p ? "## " : ""), pid);
423 printf ("%s: killing pid %d.\n", progname, pid);
424 await_child_death (True);
430 suspend_screenhack (suspend_p)
434 suspending = suspend_p;
437 else if (kill (pid, (suspend_p ? SIGSTOP : SIGCONT)) < 0)
440 sprintf (buf, "%s: %scouldn't %s child process %d", progname,
441 (verbose_p ? "## " : ""),
442 (suspend_p ? "suspend" : "resume"),
447 printf ("%s: %s pid %d.\n", progname,
448 (suspend_p ? "suspending" : "resuming"), pid);
452 /* Restarting the xscreensaver process from scratch. */
454 static char **saved_argv;
457 save_argv (argc, argv)
461 saved_argv = (char **) malloc ((argc + 2) * sizeof (char *));
462 saved_argv [argc] = 0;
465 int i = strlen (argv [argc]) + 1;
466 saved_argv [argc] = (char *) malloc (i);
467 memcpy (saved_argv [argc], argv [argc], i);
477 execvp (saved_argv [0], saved_argv);
478 fprintf (stderr, "%s: %scould not restart process: %s (%d)\n",
479 progname, (verbose_p ? "## " : ""),
480 (errno == E2BIG ? "arglist too big" :
481 errno == EACCES ? "could not execute" :
482 errno == EFAULT ? "memory fault" :
483 errno == EIO ? "I/O error" :
484 errno == ENAMETOOLONG ? "name too long" :
485 errno == ELOOP ? "too many symbolic links" :
486 errno == ENOENT ? "file no longer exists" :
487 errno == ENOTDIR ? "directory no longer exists" :
488 errno == ENOEXEC ? "bad executable file" :
489 errno == ENOMEM ? "out of memory" :
490 "execvp() returned unknown error code"),
496 demo_mode_restart_process ()
499 for (i = 0; saved_argv [i]; i++);
500 /* add the -demo switch; save_argv() left room for this. */
501 saved_argv [i] = "-demo";
502 saved_argv [i+1] = 0;
509 /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
510 the spawned processes inherit is the same as the value of -display passed
511 in on our command line (which is not necessarily the same as what our
512 $DISPLAY variable is.)
516 sprintf (buf, "DISPLAY=%s", DisplayString (dpy));
518 s = (char *) malloc (i+1);
519 strncpy (s, buf, i+1);
525 /* Change the uid/gid of the screensaver process, so that it is safe for it
526 to run setuid root (which it needs to do on some systems to read the
527 encrypted passwords from the passwd file.)
529 hack_uid() is run before opening the X connection, so that XAuth works.
530 hack_uid_warn() is called after the connection is opened and the command
531 line arguments are parsed, so that the messages from hack_uid() get
532 printed after we know whether we're in `verbose' mode.
537 static int hack_uid_errno;
538 static char hack_uid_buf [255], *hack_uid_error;
543 /* If we've been run as setuid or setgid to someone else (most likely root)
544 turn off the extra permissions so that random user-specified programs
545 don't get special privileges. (On some systems it might be necessary
546 to install this as setuid root in order to read the passwd file to
547 implement lock-mode...)
555 /* If we're being run as root (as from xdm) then switch the user id
556 to something safe. */
560 /* Locking can't work when running as root, because we have no way of
561 knowing what the user id of the logged in user is (so we don't know
562 whose password to prompt for.)
564 locking_disabled_p = True;
565 nolock_reason = "running as root";
566 p = getpwnam ("nobody");
567 if (! p) p = getpwnam ("daemon");
568 if (! p) p = getpwnam ("bin");
569 if (! p) p = getpwnam ("sys");
572 hack_uid_error = "couldn't find safe uid; running as root.";
577 struct group *g = getgrgid (p->pw_gid);
578 hack_uid_error = hack_uid_buf;
579 sprintf (hack_uid_error, "changing uid/gid to %s/%s (%d/%d).",
580 p->pw_name, (g ? g->gr_name : "???"), p->pw_uid, p->pw_gid);
582 /* Change the gid to be a safe one. If we can't do that, then
583 print a warning. We change the gid before the uid so that we
584 change the gid while still root. */
585 if (setgid (p->pw_gid) != 0)
587 hack_uid_errno = errno;
588 sprintf (hack_uid_error, "couldn't set gid to %s (%d)",
589 (g ? g->gr_name : "???"), p->pw_gid);
592 /* Now change the uid to be a safe one. */
593 if (setuid (p->pw_uid) != 0)
595 hack_uid_errno = errno;
596 sprintf (hack_uid_error, "couldn't set uid to %s (%d)",
597 p->pw_name, p->pw_uid);
602 else /* disable locking if already being run as "someone else" */
604 struct passwd *p = getpwuid (getuid ());
606 !strcmp (p->pw_name, "root") ||
607 !strcmp (p->pw_name, "nobody") ||
608 !strcmp (p->pw_name, "daemon") ||
609 !strcmp (p->pw_name, "bin") ||
610 !strcmp (p->pw_name, "sys"))
612 locking_disabled_p = True;
613 nolock_reason = hack_uid_buf;
614 sprintf (nolock_reason, "running as %s", p->pw_name);
617 #endif /* NO_LOCKING */
623 if (! hack_uid_error)
625 else if (hack_uid_errno == 0)
628 printf ("%s: %s\n", progname, hack_uid_error);
633 sprintf (buf, "%s: %s%s", progname, (verbose_p ? "## " : ""),
635 if (hack_uid_errno == -1)
636 fprintf (stderr, "%s\n", buf);
639 errno = hack_uid_errno;
645 #endif /* !NO_SETUID */