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
20 #include <X11/Xlib.h> /* not used for much... */
26 #include <sys/time.h> /* sys/resource.h needs this for timeval */
27 #include <sys/resource.h> /* for setpriority() and PRIO_PROCESS */
28 #include <sys/wait.h> /* for waitpid() and associated macros */
29 #include <signal.h> /* for the signal names */
31 extern char **environ; /* why isn't this in some header file? */
34 #include <pwd.h> /* for getpwnam() and struct passwd */
35 #include <grp.h> /* for getgrgid() and struct group */
36 #endif /* NO_SETUID */
38 #if !defined(SIGCHLD) && defined(SIGCLD)
39 #define SIGCHLD SIGCLD
43 extern int putenv (const char *); /* getenv() is in stdlib.h... */
44 extern int kill (pid_t, int); /* signal() is in sys/signal.h... */
47 # if defined(SVR4) || defined(SYSV)
48 # define random() rand()
49 # else /* !totally-losing-SYSV */
50 extern long random(); /* rand() is in stdlib.h... */
51 # endif /* !totally-losing-SYSV */
54 #include "xscreensaver.h"
56 /* this must be `sh', not whatever $SHELL happens to be. */
60 int screenhacks_count;
61 int current_hack = -1;
64 Bool locking_disabled_p = False;
65 int nice_inferior = 0;
67 extern Bool demo_mode_p;
70 exec_screenhack (command)
78 /* Close this fork's version of the display's fd. It will open its own. */
79 close (ConnectionNumber (dpy));
81 /* I don't believe what a sorry excuse for an operating system UNIX is!
83 - I want to spawn a process.
84 - I want to know it's pid so that I can kill it.
85 - I would like to receive a message when it dies of natural causes.
86 - I want the spawned process to have user-specified arguments.
88 The *only way* to parse arguments the way the shells do is to run a
89 shell (or duplicate what they do, which would be a *lot* of code.)
91 The *only way* to know the pid of the process is to fork() and exec()
92 it in the spawned side of the fork.
94 But if you're running a shell to parse your arguments, this gives you
95 the pid of the SHELL, not the pid of the PROCESS that you're actually
96 interested in, which is an *inferior* of the shell. This also means
97 that the SIGCHLD you get applies to the shell, not its inferior.
99 So, the only solution other than implementing an argument parser here
100 is to force the shell to exec() its inferior. What a fucking hack!
101 We prepend "exec " to the command string.
104 command = (char *) malloc (strlen (tmp) + 6);
105 memcpy (command, "exec ", 5);
106 memcpy (command + 5, tmp, strlen (tmp) + 1);
108 /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
115 printf ("%s: spawning \"%s\" in pid %d.\n", progname, command, getpid ());
117 #if defined(SYSV) || defined(__hpux)
119 int old_nice = nice (0);
120 int n = nice_inferior - old_nice;
122 if (nice (n) == -1 && errno != 0)
124 sprintf (buf, "%s: %snice(%d) failed", progname,
125 (verbose_p ? "## " : ""), n);
131 if (setpriority (PRIO_PROCESS, getpid(), nice_inferior) != 0)
133 sprintf (buf, "%s: %ssetpriority(PRIO_PROCESS, %d, %d) failed",
134 progname, (verbose_p ? "## " : ""), getpid(), nice_inferior);
137 #else /* !PRIO_PROCESS */
138 if (nice_inferior != 0)
140 "%s: %sdon't know how to change process priority on this system.\n",
141 progname, (verbose_p ? "## " : ""));
142 #endif /* !PRIO_PROCESS */
145 /* Now overlay the current process with /bin/sh running the command.
146 If this returns, it's an error.
148 execve (av [0], av, environ);
150 sprintf (buf, "%s: %sexecve() failed", progname, (verbose_p ? "## " : ""));
152 exit (1); /* Note this this only exits a child fork. */
155 /* to avoid a race between the main thread and the SIGCHLD handler */
156 static int killing = 0;
157 static Bool suspending = False;
159 static char *current_hack_name P((void));
162 await_child_death (killed)
165 Bool suspended_p = False;
174 kid = waitpid (pid, &status, WUNTRACED);
176 while (kid == -1 && errno == EINTR);
180 if (WIFEXITED (status))
182 int exit_status = WEXITSTATUS (status);
183 if (exit_status & 0x80)
184 exit_status |= ~0xFF;
185 if (exit_status != 0 && verbose_p)
186 printf ("%s: child pid %d (%s) exited abnormally (code %d).\n",
187 progname, pid, current_hack_name (), exit_status);
189 printf ("%s: child pid %d (%s) exited normally.\n",
190 progname, pid, current_hack_name ());
192 else if (WIFSIGNALED (status))
194 if (!killed || WTERMSIG (status) != SIGTERM)
196 "%s: %schild pid %d (%s) terminated with signal %d!\n",
197 progname, (verbose_p ? "## " : ""),
198 pid, current_hack_name (), WTERMSIG (status));
200 printf ("%s: child pid %d (%s) terminated with SIGTERM.\n",
201 progname, pid, current_hack_name ());
206 suspending = False; /* complain if it happens twice */
208 else if (WIFSTOPPED (status))
211 fprintf (stderr, "%s: %schild pid %d (%s) stopped with signal %d!\n",
212 progname, (verbose_p ? "## " : ""), pid,
213 current_hack_name (), WSTOPSIG (status));
216 fprintf (stderr, "%s: %schild pid %d (%s) died in a mysterious way!",
217 progname, (verbose_p ? "## " : ""), pid, current_hack_name());
220 fprintf (stderr, "%s: %swaitpid(%d, ...) says there are no kids? (%d)\n",
221 progname, (verbose_p ? "## " : ""), pid, kid);
223 fprintf (stderr, "%s: %swaitpid(%d, ...) says proc %d died, not %d?\n",
224 progname, (verbose_p ? "## " : ""), pid, kid, pid);
226 if (suspended_p != True)
233 static char chn [1024];
234 char *hack = (demo_mode_p ? demo_hack : screenhacks [current_hack]);
236 for (i = 0; hack [i] != 0 && hack [i] != ' ' && hack [i] != '\t'; i++)
244 sigchld_handler (sig)
251 await_child_death (False);
260 if (((int) signal (SIGCHLD, sigchld_handler)) == -1)
263 sprintf (buf, "%s: %scouldn't catch SIGCHLD", progname,
264 (verbose_p ? "## " : ""));
271 extern void raise_window P((Bool inhibit_fade, Bool between_hacks_p));
274 spawn_screenhack (first_time_p)
277 raise_window (first_time_p, True);
280 if (screenhacks_count || demo_mode_p)
292 if (screenhacks_count == 1)
294 else if (next_mode_p == 1)
295 new_hack = (current_hack + 1) % screenhacks_count,
297 else if (next_mode_p == 2)
299 new_hack = ((current_hack + screenhacks_count - 1)
300 % screenhacks_count);
304 while ((new_hack = random () % screenhacks_count) == current_hack)
306 current_hack = new_hack;
307 hack = screenhacks[current_hack];
310 switch (forked = fork ())
313 sprintf (buf, "%s: %scouldn't fork",
314 progname, (verbose_p ? "## " : ""));
316 restore_real_vroot ();
319 exec_screenhack (hack); /* this does not return */
334 if (kill (pid, SIGTERM) < 0)
338 /* Sometimes we don't get a SIGCHLD at all! WTF?
339 It's a race condition. It looks to me like what's happening is
340 something like: a subprocess dies of natural causes. There is a
341 small window between when the process dies and when the SIGCHLD
342 is (would have been) delivered. If we happen to try to kill()
343 the process during that time, the kill() fails, because the
344 process is already dead. But! no SIGCHLD is delivered (perhaps
345 because the failed kill() has reset some state in the kernel?)
346 Anyway, if kill() says "No such process", then we have to wait()
347 for it anyway, because the process has already become a zombie.
350 await_child_death (False);
355 sprintf (buf, "%s: %scouldn't kill child process %d", progname,
356 (verbose_p ? "## " : ""), pid);
363 printf ("%s: killing pid %d.\n", progname, pid);
364 await_child_death (True);
370 suspend_screenhack (suspend_p)
374 suspending = suspend_p;
377 else if (kill (pid, (suspend_p ? SIGSTOP : SIGCONT)) < 0)
380 sprintf (buf, "%s: %scouldn't %s child process %d", progname,
381 (verbose_p ? "## " : ""),
382 (suspend_p ? "suspend" : "resume"),
387 printf ("%s: %s pid %d.\n", progname,
388 (suspend_p ? "suspending" : "resuming"), pid);
392 /* Restarting the xscreensaver process from scratch. */
394 static char **saved_argv;
397 save_argv (argc, argv)
401 saved_argv = (char **) malloc ((argc + 2) * sizeof (char *));
402 saved_argv [argc] = 0;
405 int i = strlen (argv [argc]) + 1;
406 saved_argv [argc] = (char *) malloc (i);
407 memcpy (saved_argv [argc], argv [argc], i);
417 execvp (saved_argv [0], saved_argv);
418 fprintf (stderr, "%s: %scould not restart process: %s (%d)\n",
419 progname, (verbose_p ? "## " : ""),
420 (errno == E2BIG ? "arglist too big" :
421 errno == EACCES ? "could not execute" :
422 errno == EFAULT ? "memory fault" :
423 errno == EIO ? "I/O error" :
424 errno == ENAMETOOLONG ? "name too long" :
425 errno == ELOOP ? "too many symbolic links" :
426 errno == ENOENT ? "file no longer exists" :
427 errno == ENOTDIR ? "directory no longer exists" :
428 errno == ENOEXEC ? "bad executable file" :
429 errno == ENOMEM ? "out of memory" :
430 "execvp() returned unknown error code"),
436 demo_mode_restart_process ()
439 for (i = 0; saved_argv [i]; i++);
440 /* add the -demo switch; save_argv() left room for this. */
441 saved_argv [i] = "-demo";
442 saved_argv [i+1] = 0;
449 /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
450 the spawned processes inherit is the same as the value of -display passed
451 in on our command line (which is not necessarily the same as what our
452 $DISPLAY variable is.)
456 sprintf (buf, "DISPLAY=%s", DisplayString (dpy));
458 s = (char *) malloc (i+1);
459 strncpy (s, buf, i+1);
465 /* Change the uid/gid of the screensaver process, so that it is safe for it
466 to run setuid root (which it needs to do on some systems to read the
467 encrypted passwords from the passwd file.)
469 hack_uid() is run before opening the X connection, so that XAuth works.
470 hack_uid_warn() is called after the connection is opened and the command
471 line arguments are parsed, so that the messages from hack_uid() get
472 printed after we know whether we're in `verbose' mode.
477 static int hack_uid_errno;
478 static char hack_uid_buf [255], *hack_uid_error;
483 /* If we've been run as setuid or setgid to someone else (most likely root)
484 turn off the extra permissions so that random user-specified programs
485 don't get special privileges. (On some systems it might be necessary
486 to install this as setuid root in order to read the passwd file to
487 implement lock-mode...)
495 /* If we're being run as root (as from xdm) then switch the user id
496 to something safe. */
499 struct passwd *p = getpwnam ("nobody");
500 locking_disabled_p = True;
501 if (! p) p = getpwnam ("daemon");
502 if (! p) p = getpwnam ("bin");
503 if (! p) p = getpwnam ("sys");
506 hack_uid_error = "couldn't find safe uid; running as root.";
511 struct group *g = getgrgid (p->pw_gid);
512 hack_uid_error = hack_uid_buf;
513 sprintf (hack_uid_error, "changing uid/gid to %s/%s (%d/%d).",
514 p->pw_name, (g ? g->gr_name : "???"), p->pw_uid, p->pw_gid);
516 /* Change the gid to be a safe one. If we can't do that, then
517 print a warning. We change the gid before the uid so that we
518 change the gid while still root. */
519 if (setgid (p->pw_gid) != 0)
521 hack_uid_errno = errno;
522 sprintf (hack_uid_error, "couldn't set gid to %s (%d)",
523 (g ? g->gr_name : "???"), p->pw_gid);
526 /* Now change the uid to be a safe one. */
527 if (setuid (p->pw_uid) != 0)
529 hack_uid_errno = errno;
530 sprintf (hack_uid_error, "couldn't set uid to %s (%d)",
531 p->pw_name, p->pw_uid);
536 else /* disable locking if already being run as "someone else" */
538 struct passwd *p = getpwuid (getuid ());
540 !strcmp (p->pw_name, "root") ||
541 !strcmp (p->pw_name, "nobody") ||
542 !strcmp (p->pw_name, "daemon") ||
543 !strcmp (p->pw_name, "bin") ||
544 !strcmp (p->pw_name, "sys"))
545 locking_disabled_p = True;
547 #endif /* NO_LOCKING */
553 if (! hack_uid_error)
555 else if (hack_uid_errno == 0)
558 printf ("%s: %s\n", progname, hack_uid_error);
563 sprintf (buf, "%s: %s%s", progname, (verbose_p ? "## " : ""),
565 if (hack_uid_errno == -1)
566 fprintf (stderr, "%s\n", buf);
569 errno = hack_uid_errno;
575 #endif /* !NO_SETUID */