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)
49 # define random() rand()
50 # else /* !totally-losing-SYSV */
51 extern long random(); /* rand() is in stdlib.h... */
52 # endif /* !totally-losing-SYSV */
55 #include "xscreensaver.h"
57 /* this must be `sh', not whatever $SHELL happens to be. */
61 int screenhacks_count;
62 int current_hack = -1;
65 Bool locking_disabled_p = False;
66 char *nolock_reason = 0;
67 int nice_inferior = 0;
69 extern Bool demo_mode_p;
72 exec_screenhack (command)
80 /* Close this fork's version of the display's fd. It will open its own. */
81 close (ConnectionNumber (dpy));
83 /* I don't believe what a sorry excuse for an operating system UNIX is!
85 - I want to spawn a process.
86 - I want to know it's pid so that I can kill it.
87 - I would like to receive a message when it dies of natural causes.
88 - I want the spawned process to have user-specified arguments.
90 The *only way* to parse arguments the way the shells do is to run a
91 shell (or duplicate what they do, which would be a *lot* of code.)
93 The *only way* to know the pid of the process is to fork() and exec()
94 it in the spawned side of the fork.
96 But if you're running a shell to parse your arguments, this gives you
97 the pid of the SHELL, not the pid of the PROCESS that you're actually
98 interested in, which is an *inferior* of the shell. This also means
99 that the SIGCHLD you get applies to the shell, not its inferior.
101 So, the only solution other than implementing an argument parser here
102 is to force the shell to exec() its inferior. What a fucking hack!
103 We prepend "exec " to the command string.
106 command = (char *) malloc (strlen (tmp) + 6);
107 memcpy (command, "exec ", 5);
108 memcpy (command + 5, tmp, strlen (tmp) + 1);
110 /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */
117 printf ("%s: spawning \"%s\" in pid %d.\n", progname, command, getpid ());
119 #if defined(SYSV) || defined(__hpux)
121 int old_nice = nice (0);
122 int n = nice_inferior - old_nice;
124 if (nice (n) == -1 && errno != 0)
126 sprintf (buf, "%s: %snice(%d) failed", progname,
127 (verbose_p ? "## " : ""), n);
133 if (setpriority (PRIO_PROCESS, getpid(), nice_inferior) != 0)
135 sprintf (buf, "%s: %ssetpriority(PRIO_PROCESS, %d, %d) failed",
136 progname, (verbose_p ? "## " : ""), getpid(), nice_inferior);
139 #else /* !PRIO_PROCESS */
140 if (nice_inferior != 0)
142 "%s: %sdon't know how to change process priority on this system.\n",
143 progname, (verbose_p ? "## " : ""));
144 #endif /* !PRIO_PROCESS */
147 /* Now overlay the current process with /bin/sh running the command.
148 If this returns, it's an error.
150 execve (av [0], av, environ);
152 sprintf (buf, "%s: %sexecve() failed", progname, (verbose_p ? "## " : ""));
154 exit (1); /* Note this this only exits a child fork. */
157 /* to avoid a race between the main thread and the SIGCHLD handler */
158 static int killing = 0;
159 static Bool suspending = False;
161 static char *current_hack_name P((void));
164 await_child_death (killed)
167 Bool suspended_p = False;
176 kid = waitpid (pid, &status, WUNTRACED);
178 while (kid == -1 && errno == EINTR);
182 if (WIFEXITED (status))
184 int exit_status = WEXITSTATUS (status);
185 if (exit_status & 0x80)
186 exit_status |= ~0xFF;
187 if (exit_status != 0 && verbose_p)
188 printf ("%s: child pid %d (%s) exited abnormally (code %d).\n",
189 progname, pid, current_hack_name (), exit_status);
191 printf ("%s: child pid %d (%s) exited normally.\n",
192 progname, pid, current_hack_name ());
194 else if (WIFSIGNALED (status))
196 if (!killed || WTERMSIG (status) != SIGTERM)
198 "%s: %schild pid %d (%s) terminated with signal %d!\n",
199 progname, (verbose_p ? "## " : ""),
200 pid, current_hack_name (), WTERMSIG (status));
202 printf ("%s: child pid %d (%s) terminated with SIGTERM.\n",
203 progname, pid, current_hack_name ());
208 suspending = False; /* complain if it happens twice */
210 else if (WIFSTOPPED (status))
213 fprintf (stderr, "%s: %schild pid %d (%s) stopped with signal %d!\n",
214 progname, (verbose_p ? "## " : ""), pid,
215 current_hack_name (), WSTOPSIG (status));
218 fprintf (stderr, "%s: %schild pid %d (%s) died in a mysterious way!",
219 progname, (verbose_p ? "## " : ""), pid, current_hack_name());
222 fprintf (stderr, "%s: %swaitpid(%d, ...) says there are no kids? (%d)\n",
223 progname, (verbose_p ? "## " : ""), pid, kid);
225 fprintf (stderr, "%s: %swaitpid(%d, ...) says proc %d died, not %d?\n",
226 progname, (verbose_p ? "## " : ""), pid, kid, pid);
228 if (suspended_p != True)
235 static char chn [1024];
236 char *hack = (demo_mode_p ? demo_hack : screenhacks [current_hack]);
238 for (i = 0; hack [i] != 0 && hack [i] != ' ' && hack [i] != '\t'; i++)
246 sigchld_handler (sig)
253 await_child_death (False);
262 if (((int) signal (SIGCHLD, sigchld_handler)) == -1)
265 sprintf (buf, "%s: %scouldn't catch SIGCHLD", progname,
266 (verbose_p ? "## " : ""));
273 extern void raise_window P((Bool inhibit_fade, Bool between_hacks_p));
276 spawn_screenhack (first_time_p)
279 raise_window (first_time_p, True);
282 if (screenhacks_count || demo_mode_p)
294 if (screenhacks_count == 1)
296 else if (next_mode_p == 1)
297 new_hack = (current_hack + 1) % screenhacks_count,
299 else if (next_mode_p == 2)
301 new_hack = ((current_hack + screenhacks_count - 1)
302 % screenhacks_count);
306 while ((new_hack = random () % screenhacks_count) == current_hack)
308 current_hack = new_hack;
309 hack = screenhacks[current_hack];
312 switch (forked = fork ())
315 sprintf (buf, "%s: %scouldn't fork",
316 progname, (verbose_p ? "## " : ""));
318 restore_real_vroot ();
321 exec_screenhack (hack); /* this does not return */
336 if (kill (pid, SIGTERM) < 0)
340 /* Sometimes we don't get a SIGCHLD at all! WTF?
341 It's a race condition. It looks to me like what's happening is
342 something like: a subprocess dies of natural causes. There is a
343 small window between when the process dies and when the SIGCHLD
344 is (would have been) delivered. If we happen to try to kill()
345 the process during that time, the kill() fails, because the
346 process is already dead. But! no SIGCHLD is delivered (perhaps
347 because the failed kill() has reset some state in the kernel?)
348 Anyway, if kill() says "No such process", then we have to wait()
349 for it anyway, because the process has already become a zombie.
352 await_child_death (False);
357 sprintf (buf, "%s: %scouldn't kill child process %d", progname,
358 (verbose_p ? "## " : ""), pid);
365 printf ("%s: killing pid %d.\n", progname, pid);
366 await_child_death (True);
372 suspend_screenhack (suspend_p)
376 suspending = suspend_p;
379 else if (kill (pid, (suspend_p ? SIGSTOP : SIGCONT)) < 0)
382 sprintf (buf, "%s: %scouldn't %s child process %d", progname,
383 (verbose_p ? "## " : ""),
384 (suspend_p ? "suspend" : "resume"),
389 printf ("%s: %s pid %d.\n", progname,
390 (suspend_p ? "suspending" : "resuming"), pid);
394 /* Restarting the xscreensaver process from scratch. */
396 static char **saved_argv;
399 save_argv (argc, argv)
403 saved_argv = (char **) malloc ((argc + 2) * sizeof (char *));
404 saved_argv [argc] = 0;
407 int i = strlen (argv [argc]) + 1;
408 saved_argv [argc] = (char *) malloc (i);
409 memcpy (saved_argv [argc], argv [argc], i);
419 execvp (saved_argv [0], saved_argv);
420 fprintf (stderr, "%s: %scould not restart process: %s (%d)\n",
421 progname, (verbose_p ? "## " : ""),
422 (errno == E2BIG ? "arglist too big" :
423 errno == EACCES ? "could not execute" :
424 errno == EFAULT ? "memory fault" :
425 errno == EIO ? "I/O error" :
426 errno == ENAMETOOLONG ? "name too long" :
427 errno == ELOOP ? "too many symbolic links" :
428 errno == ENOENT ? "file no longer exists" :
429 errno == ENOTDIR ? "directory no longer exists" :
430 errno == ENOEXEC ? "bad executable file" :
431 errno == ENOMEM ? "out of memory" :
432 "execvp() returned unknown error code"),
438 demo_mode_restart_process ()
441 for (i = 0; saved_argv [i]; i++);
442 /* add the -demo switch; save_argv() left room for this. */
443 saved_argv [i] = "-demo";
444 saved_argv [i+1] = 0;
451 /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
452 the spawned processes inherit is the same as the value of -display passed
453 in on our command line (which is not necessarily the same as what our
454 $DISPLAY variable is.)
458 sprintf (buf, "DISPLAY=%s", DisplayString (dpy));
460 s = (char *) malloc (i+1);
461 strncpy (s, buf, i+1);
467 /* Change the uid/gid of the screensaver process, so that it is safe for it
468 to run setuid root (which it needs to do on some systems to read the
469 encrypted passwords from the passwd file.)
471 hack_uid() is run before opening the X connection, so that XAuth works.
472 hack_uid_warn() is called after the connection is opened and the command
473 line arguments are parsed, so that the messages from hack_uid() get
474 printed after we know whether we're in `verbose' mode.
479 static int hack_uid_errno;
480 static char hack_uid_buf [255], *hack_uid_error;
485 /* If we've been run as setuid or setgid to someone else (most likely root)
486 turn off the extra permissions so that random user-specified programs
487 don't get special privileges. (On some systems it might be necessary
488 to install this as setuid root in order to read the passwd file to
489 implement lock-mode...)
497 /* If we're being run as root (as from xdm) then switch the user id
498 to something safe. */
502 /* Locking can't work when running as root, because we have no way of
503 knowing what the user id of the logged in user is (so we don't know
504 whose password to prompt for.)
506 locking_disabled_p = True;
507 nolock_reason = "running as root";
508 p = getpwnam ("nobody");
509 if (! p) p = getpwnam ("daemon");
510 if (! p) p = getpwnam ("bin");
511 if (! p) p = getpwnam ("sys");
514 hack_uid_error = "couldn't find safe uid; running as root.";
519 struct group *g = getgrgid (p->pw_gid);
520 hack_uid_error = hack_uid_buf;
521 sprintf (hack_uid_error, "changing uid/gid to %s/%s (%d/%d).",
522 p->pw_name, (g ? g->gr_name : "???"), p->pw_uid, p->pw_gid);
524 /* Change the gid to be a safe one. If we can't do that, then
525 print a warning. We change the gid before the uid so that we
526 change the gid while still root. */
527 if (setgid (p->pw_gid) != 0)
529 hack_uid_errno = errno;
530 sprintf (hack_uid_error, "couldn't set gid to %s (%d)",
531 (g ? g->gr_name : "???"), p->pw_gid);
534 /* Now change the uid to be a safe one. */
535 if (setuid (p->pw_uid) != 0)
537 hack_uid_errno = errno;
538 sprintf (hack_uid_error, "couldn't set uid to %s (%d)",
539 p->pw_name, p->pw_uid);
544 else /* disable locking if already being run as "someone else" */
546 struct passwd *p = getpwuid (getuid ());
548 !strcmp (p->pw_name, "root") ||
549 !strcmp (p->pw_name, "nobody") ||
550 !strcmp (p->pw_name, "daemon") ||
551 !strcmp (p->pw_name, "bin") ||
552 !strcmp (p->pw_name, "sys"))
554 locking_disabled_p = True;
555 nolock_reason = hack_uid_buf;
556 sprintf (nolock_reason, "running as %s", p->pw_name);
559 #endif /* NO_LOCKING */
565 if (! hack_uid_error)
567 else if (hack_uid_errno == 0)
570 printf ("%s: %s\n", progname, hack_uid_error);
575 sprintf (buf, "%s: %s%s", progname, (verbose_p ? "## " : ""),
577 if (hack_uid_errno == -1)
578 fprintf (stderr, "%s\n", buf);
581 errno = hack_uid_errno;
587 #endif /* !NO_SETUID */