/* subprocs.c --- choosing, spawning, and killing screenhacks.
* xscreensaver, Copyright (c) 1991, 1992, 1993, 1995, 1997, 1998
- * Jamie Zawinski <jwz@netscape.com>
+ * Jamie Zawinski <jwz@jwz.org>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
#include <X11/Xlib.h> /* not used for much... */
#ifndef ESRCH
-#include <errno.h>
+# include <errno.h>
#endif
#include <sys/time.h> /* sys/resource.h needs this for timeval */
-#ifndef VMS
-
-# include <sys/resource.h> /* for setpriority() and PRIO_PROCESS */
+#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h> /* for waitpid() and associated macros */
+#endif
-#else /* VMS */
-
-# if __DECC_VER >= 50200000
-# include <sys/wait.h>
-# endif
+#if defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
+# include <sys/resource.h> /* for setpriority() and PRIO_PROCESS */
+#endif
+#ifdef VMS
# include <processes.h>
# include <unixio.h> /* for close */
# include <unixlib.h> /* for getpid */
-# define pid_t int
-# define fork vfork
-
+# define pid_t int
+# define fork vfork
#endif /* VMS */
#include <signal.h> /* for the signal names */
-#ifndef NO_SETUID
-#include <pwd.h> /* for getpwnam() and struct passwd */
-#include <grp.h> /* for getgrgid() and struct group */
-#endif /* NO_SETUID */
-
#if !defined(SIGCHLD) && defined(SIGCLD)
-#define SIGCHLD SIGCLD
+# define SIGCHLD SIGCLD
#endif
+#if 0 /* putenv() is declared in stdlib.h on modern linux systems. */
#ifdef HAVE_PUTENV
extern int putenv (/* const char * */); /* getenv() is in stdlib.h... */
#endif
+#endif
+
extern int kill (pid_t, int); /* signal() is in sys/signal.h... */
/* This file doesn't need the Xt headers, so stub these types out... */
extern saver_info *global_si_kludge; /* I hate C so much... */
-static void hack_environment (saver_screen_info *ssi);
-
-
static void
nice_subproc (int nice_level)
{
if (nice (n) == -1 && errno != 0)
{
char buf [512];
- sprintf (buf, "%s: nice(%d) failed", progname, n);
+ sprintf (buf, "%s: nice(%d) failed", blurb(), n);
perror (buf);
}
}
{
char buf [512];
sprintf (buf, "%s: setpriority(PRIO_PROCESS, %lu, %d) failed",
- progname, (unsigned long) getpid(), nice_level);
+ blurb(), (unsigned long) getpid(), nice_level);
perror (buf);
}
#else
fprintf (stderr,
"%s: don't know how to change process priority on this system.\n",
- progname);
+ blurb());
#endif
}
{
char buf [512];
- sprintf (buf, "%s: could not execute \"%s\"", progname, av[0]);
+ sprintf (buf, "%s: could not execute \"%s\"", blurb(), av[0]);
perror (buf);
if (errno == ENOENT &&
{
char buf [512];
- sprintf (buf, "%s: execvp(\"%s\") failed", progname, av[0]);
+ sprintf (buf, "%s: execvp(\"%s\") failed", blurb(), av[0]);
perror (buf);
fflush(stderr);
fflush(stdout);
saver_preferences *p = &si->prefs;
#ifndef VMS
- Bool hairy_p = !!strpbrk (command, "*?$&!<>[];`'\\\"");
+ Bool hairy_p = !!strpbrk (command, "*?$&!<>[];`'\\\"=");
+ /* note: = is in the above because of the sh syntax "FOO=bar cmd". */
if (p->verbose_p)
- printf ("%s: spawning \"%s\" in pid %lu%s.\n",
- progname, command, (unsigned long) getpid (),
- (hairy_p ? " (via shell)" : ""));
+ fprintf (stderr, "%s: spawning \"%s\" in pid %lu%s.\n",
+ blurb(), command, (unsigned long) getpid (),
+ (hairy_p ? " (via shell)" : ""));
if (hairy_p)
/* If it contains any shell metacharacters, do it the hard way,
#else /* VMS */
if (p->verbose_p)
- printf ("%s: spawning \"%s\" in pid %lu.\n", progname, command, getpid());
+ fprintf (stderr, "%s: spawning \"%s\" in pid %lu.\n",
+ blurb(), command, getpid());
exec_vms_command (command);
#endif /* VMS */
static struct screenhack_job *jobs = 0;
-#ifdef DEBUG
-static void
+/* for debugging -- nothing calls this, but it's useful to invoke from gdb. */
+void
show_job_list (void)
{
struct screenhack_job *job;
- fprintf(stderr, "%s: job list:\n", progname);
+ fprintf(stderr, "%s: job list:\n", blurb());
for (job = jobs; job; job = job->next)
fprintf (stderr, " %5ld: (%s) %s\n",
(long) job->pid,
job->name);
fprintf (stderr, "\n");
}
-#endif
static void clean_job_list (void);
{
if (p->verbose_p)
fprintf (stderr, "%s: no child %ld to signal!\n",
- progname, (long) pid);
+ blurb(), (long) pid);
goto DONE;
}
#ifdef SIGSTOP
if (p->verbose_p)
- fprintf (stderr, "%s: %s pid %lu.\n", progname,
+ fprintf (stderr, "%s: %s pid %lu.\n", blurb(),
(signal == SIGTERM ? "killing" :
signal == SIGSTOP ? "suspending" :
signal == SIGCONT ? "resuming" : "signalling"),
(unsigned long) job->pid);
#else /* !SIGSTOP */
if (p->verbose_p)
- fprintf (stderr, "%s: %s pid %lu.\n", progname, "killing",
+ fprintf (stderr, "%s: %s pid %lu.\n", blurb(), "killing",
(unsigned long) job->pid);
#endif /* !SIGSTOP */
{
if (errno == ESRCH)
fprintf (stderr, "%s: child process %lu (%s) was already dead.\n",
- progname, job->pid, job->name);
+ blurb(), job->pid, job->name);
else
{
char buf [1024];
sprintf (buf, "%s: couldn't kill child process %lu (%s)",
- progname, job->pid, job->name);
+ blurb(), job->pid, job->name);
perror (buf);
}
}
{
saver_info *si = global_si_kludge; /* I hate C so much... */
-#ifdef DEBUG
if (si->prefs.debug_p)
- fprintf(stderr, "%s: got SIGCHLD%s\n", progname,
+ fprintf(stderr, "%s: got SIGCHLD%s\n", blurb(),
(block_sigchld_handler ? " (blocked)" : ""));
-#endif /* DEBUG */
if (block_sigchld_handler < 0)
abort();
errno = 0;
kid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED);
-#ifdef DEBUG
+
if (si->prefs.debug_p)
- if (kid < 0 && errno)
- fprintf (stderr, "%s: waitpid(-1) ==> %ld (%d)\n", progname,
- (long) kid, errno);
- else
- fprintf (stderr, "%s: waitpid(-1) ==> %ld\n", progname, (long) kid);
-#endif /* DEBUG */
+ {
+ if (kid < 0 && errno)
+ fprintf (stderr, "%s: waitpid(-1) ==> %ld (%d)\n", blurb(),
+ (long) kid, errno);
+ else
+ fprintf (stderr, "%s: waitpid(-1) ==> %ld\n", blurb(),
+ (long) kid);
+ }
/* 0 means no more children to reap.
-1 means error -- except "interrupted system call" isn't a "real"
(p->verbose_p || job->status != job_killed)))
fprintf (stderr,
"%s: child pid %lu (%s) exited abnormally (code %d).\n",
- progname, (unsigned long) kid, name, exit_status);
+ blurb(), (unsigned long) kid, name, exit_status);
else if (p->verbose_p)
- printf ("%s: child pid %lu (%s) exited normally.\n",
- progname, (unsigned long) kid, name);
+ fprintf (stderr, "%s: child pid %lu (%s) exited normally.\n",
+ blurb(), (unsigned long) kid, name);
if (job)
job->status = job_dead;
job->status != job_killed ||
WTERMSIG (wait_status) != SIGTERM)
fprintf (stderr, "%s: child pid %lu (%s) terminated with %s.\n",
- progname, (unsigned long) kid, name,
+ blurb(), (unsigned long) kid, name,
signal_name (WTERMSIG(wait_status)));
if (job)
{
if (p->verbose_p)
fprintf (stderr, "%s: child pid %lu (%s) stopped with %s.\n",
- progname, (unsigned long) kid, name,
+ blurb(), (unsigned long) kid, name,
signal_name (WSTOPSIG (wait_status)));
if (job)
else
{
fprintf (stderr, "%s: child pid %lu (%s) died in a mysterious way!",
- progname, (unsigned long) kid, name);
+ blurb(), (unsigned long) kid, name);
if (job)
job->status = job_dead;
}
if (sigaction(SIGCHLD, &action, &old) < 0)
{
char buf [255];
- sprintf (buf, "%s: couldn't catch SIGCHLD", progname);
+ sprintf (buf, "%s: couldn't catch SIGCHLD", blurb());
perror (buf);
}
sigchld_initialized_p = True;
if (((long) signal (SIGCHLD, sigchld_handler)) == -1L)
{
char buf [255];
- sprintf (buf, "%s: couldn't catch SIGCHLD", progname);
+ sprintf (buf, "%s: couldn't catch SIGCHLD", blurb());
perror (buf);
}
# endif /* !HAVE_SIGACTION */
\f
+static Bool
+hack_enabled_p (const char *hack)
+{
+ const char *s = hack;
+ while (isspace(*s)) s++;
+ return (*s != '-');
+}
+
static Bool
select_visual_of_hack (saver_screen_info *ssi, const char *hack)
{
const char *in = hack;
char *out = vis;
while (isspace(*in)) in++; /* skip whitespace */
+ if (*in == '-') in++; /* skip optional "-" */
+ while (isspace(*in)) in++; /* skip whitespace */
+
while (!isspace(*in) && *in != ':')
*out++ = *in++; /* snarf first token */
while (isspace(*in)) in++; /* skip whitespace */
else
selected = select_visual(ssi, 0);
- if (!selected && (p->verbose_p || si->demo_mode_p))
+ if (!selected && (p->verbose_p || si->demoing_p))
{
if (*in == ':') in++;
while (isspace(*in)) in++;
fprintf (stderr,
- (si->demo_mode_p
+ (si->demoing_p
? "%s: warning, no \"%s\" visual for \"%s\".\n"
: "%s: no \"%s\" visual; skipping \"%s\".\n"),
- progname, (vis ? vis : "???"), in);
+ blurb(), (vis ? vis : "???"), in);
}
return selected;
raise_window (si, first_time_p, True, False);
XFlush (si->dpy);
- if (p->screenhacks_count || si->demo_mode_p)
+ if (p->screenhacks_count)
{
char *hack;
pid_t forked;
char buf [255];
int new_hack;
+ int retry_count = 0;
+ Bool force = False;
- if (si->demo_mode_p)
- {
- hack = si->demo_hack;
+ AGAIN:
+
+ if (p->screenhacks_count == 1)
+ /* If there is only one hack in the list, there is no choice. */
+ new_hack = 0;
- /* Ignore visual-selection failure if in demo mode. */
- (void) select_visual_of_hack (ssi, hack);
+ else if (si->selection_mode == -1)
+ /* Select the next hack, wrapping. */
+ new_hack = (ssi->current_hack + 1) % p->screenhacks_count;
+
+ else if (si->selection_mode == -2)
+ /* Select the previous hack, wrapping. */
+ new_hack = ((ssi->current_hack + p->screenhacks_count - 1)
+ % p->screenhacks_count);
+
+ else if (si->selection_mode > 0)
+ /* Select a specific hack, by number. No negotiation. */
+ {
+ new_hack = ((si->selection_mode - 1) % p->screenhacks_count);
+ force = True;
}
else
{
- int retry_count = 0;
-
- AGAIN:
- if (p->screenhacks_count == 1)
- new_hack = 0;
- else if (si->next_mode_p == 1)
- new_hack = (ssi->current_hack + 1) % p->screenhacks_count;
- else if (si->next_mode_p == 2)
- new_hack = ((ssi->current_hack + p->screenhacks_count - 1)
- % p->screenhacks_count);
- else
- while ((new_hack = random () % p->screenhacks_count)
- == ssi->current_hack)
- ;
- ssi->current_hack = new_hack;
- hack = p->screenhacks[ssi->current_hack];
+ /* Select a random hack (but not the one we just ran.) */
+ while ((new_hack = random () % p->screenhacks_count)
+ == ssi->current_hack)
+ ;
+ }
+
+ ssi->current_hack = new_hack;
+ hack = p->screenhacks[ssi->current_hack];
- if (!select_visual_of_hack (ssi, hack))
+ /* If the hack is disabled, or there is no visual for this hack,
+ then try again (move forward, or backward, or re-randomize.)
+ Unless this hack was specified explicitly, in which case,
+ use it regardless.
+ */
+ if (!force &&
+ (!hack_enabled_p (hack) ||
+ !select_visual_of_hack (ssi, hack)))
+ {
+ if (++retry_count > (p->screenhacks_count*4))
{
- if (++retry_count > (p->screenhacks_count*4))
- {
- /* Uh, oops. Odds are, there are no suitable visuals,
- and we're looping. Give up. (This is totally lame,
- what we should do is make a list of suitable hacks at
- the beginning, then only loop over them.)
- */
- if (p->verbose_p)
- fprintf(stderr,
- "%s: no suitable visuals for these programs.\n",
- progname);
- return;
- }
- else
- goto AGAIN;
+ /* Uh, oops. Odds are, there are no suitable visuals,
+ and we're looping. Give up. (This is totally lame,
+ what we should do is make a list of suitable hacks at
+ the beginning, then only loop over them.)
+ */
+ if (p->verbose_p)
+ fprintf(stderr,
+ "%s: no suitable visuals for these programs.\n",
+ blurb());
+ return;
}
+ else
+ goto AGAIN;
}
- si->next_mode_p = 0;
+
+ /* Turn off "next" and "prev" modes now, but "demo" mode is only
+ turned off by explicit action.
+ */
+ if (si->selection_mode < 0)
+ si->selection_mode = 0;
/* If there's a visual description on the front of the command, nuke it.
{
char *in = hack;
while (isspace(*in)) in++; /* skip whitespace */
+ if (*in == '-') in++; /* skip optional "-" */
+ while (isspace(*in)) in++; /* skip whitespace */
hack = in;
while (!isspace(*in) && *in != ':') in++; /* snarf first token */
while (isspace(*in)) in++; /* skip whitespace */
switch ((int) (forked = fork ()))
{
case -1:
- sprintf (buf, "%s: couldn't fork", progname);
+ sprintf (buf, "%s: couldn't fork", blurb());
perror (buf);
restore_real_vroot (si);
- saver_exit (si, 1);
+ saver_exit (si, 1, 0);
case 0:
close (ConnectionNumber (si->dpy)); /* close display fd */
nice_subproc (p->nice_inferior); /* change process priority */
- hack_environment (ssi); /* set $DISPLAY */
+ hack_subproc_environment (ssi); /* set $DISPLAY */
exec_screenhack (si, hack); /* this does not return */
abort();
break;
spawn_screenhack (saver_info *si, Bool first_time_p)
{
int i;
+
+ if (!monitor_powered_on_p (si))
+ {
+ if (si->prefs.verbose_p)
+ fprintf (stderr,
+ "%s: server reports that monitor has powered down; "
+ "not launching a new hack.\n", blurb());
+ return;
+ }
+
for (i = 0; i < si->nscreens; i++)
{
saver_screen_info *ssi = &si->screens[i];
}
\f
-/* Restarting the xscreensaver process from scratch. */
+/* Environment variables. */
-static char **saved_argv;
+/* Modifies $PATH in the current environment, so that if DEFAULT_PATH_PREFIX
+ is defined, the xscreensaver daemon will search that directory for hacks.
+ */
void
-save_argv (int argc, char **argv)
+hack_environment (saver_info *si)
{
- saved_argv = (char **) malloc ((argc + 2) * sizeof (char *));
- saved_argv [argc] = 0;
- while (argc--)
+#if defined(HAVE_PUTENV) && defined(DEFAULT_PATH_PREFIX)
+ static const char *def_path = DEFAULT_PATH_PREFIX;
+ if (def_path && *def_path)
{
- int i = strlen (argv [argc]) + 1;
- saved_argv [argc] = (char *) malloc (i);
- memcpy (saved_argv [argc], argv [argc], i);
+ const char *opath = getenv("PATH");
+ char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
+ strcpy (npath, "PATH=");
+ strcat (npath, def_path);
+ strcat (npath, ":");
+ strcat (npath, opath);
+
+ if (putenv (npath))
+ abort ();
}
+#endif /* HAVE_PUTENV && DEFAULT_PATH_PREFIX */
}
-void
-restart_process (saver_info *si)
-{
- fflush (real_stdout);
- fflush (real_stderr);
- execvp (saved_argv [0], saved_argv); /* shouldn't return */
- {
- char buf [512];
- sprintf (buf, "%s: could not restart process", progname);
- perror(buf);
- fflush(stderr);
- }
-}
-/* Like restart_process(), but ensures that when it restarts,
- it comes up in demo-mode. */
void
-demo_mode_restart_process (saver_info *si)
-{
- int i;
- for (i = 0; saved_argv [i]; i++);
- /* add the -demo switch; save_argv() left room for this. */
- saved_argv [i] = "-demo";
- saved_argv [i+1] = 0;
- restart_process (si); /* shouldn't return */
- saved_argv [i] = 0;
- XBell(si->dpy, 0);
-}
-
-static void
-hack_environment (saver_screen_info *ssi)
+hack_subproc_environment (saver_screen_info *ssi)
{
/* Store $DISPLAY into the environment, so that the $DISPLAY variable that
the spawned processes inherit is correct. First, it must be on the same
for (screen_number = 0; screen_number < si->nscreens; screen_number++)
if (ssi == &si->screens[screen_number])
break;
- if (screen_number >= si->nscreens) abort();
strcpy (ndpy, "DISPLAY=");
s = ndpy + strlen(ndpy);
}
\f
-/* Change the uid/gid of the screensaver process, so that it is safe for it
- to run setuid root (which it needs to do on some systems to read the
- encrypted passwords from the passwd file.)
-
- hack_uid() is run before opening the X connection, so that XAuth works.
- hack_uid_warn() is called after the connection is opened and the command
- line arguments are parsed, so that the messages from hack_uid() get
- printed after we know whether we're in `verbose' mode.
- */
-
-#ifndef NO_SETUID
+/* Restarting the xscreensaver process from scratch. */
-static int hack_uid_errno;
-static char hack_uid_buf [255], *hack_uid_error;
+static char **saved_argv;
void
-hack_uid (saver_info *si)
+save_argv (int argc, char **argv)
{
-
- /* If we've been run as setuid or setgid to someone else (most likely root)
- turn off the extra permissions so that random user-specified programs
- don't get special privileges. (On some systems it might be necessary
- to install this as setuid root in order to read the passwd file to
- implement lock-mode...)
- */
- setgid (getgid ());
- setuid (getuid ());
-
- hack_uid_errno = 0;
- hack_uid_error = 0;
-
- /* If we're being run as root (as from xdm) then switch the user id
- to something safe. */
- if (getuid () == 0)
+ saved_argv = (char **) calloc (argc+2, sizeof (char *));
+ saved_argv [argc] = 0;
+ while (argc--)
{
- struct passwd *p;
- /* Locking can't work when running as root, because we have no way of
- knowing what the user id of the logged in user is (so we don't know
- whose password to prompt for.)
- */
- si->locking_disabled_p = True;
- si->nolock_reason = "running as root";
- p = getpwnam ("nobody");
- if (! p) p = getpwnam ("noaccess");
- if (! p) p = getpwnam ("daemon");
- if (! p) p = getpwnam ("bin");
- if (! p) p = getpwnam ("sys");
- if (! p)
- {
- hack_uid_error = "couldn't find safe uid; running as root.";
- hack_uid_errno = -1;
- }
- else
- {
- struct group *g = getgrgid (p->pw_gid);
- hack_uid_error = hack_uid_buf;
- sprintf (hack_uid_error, "changing uid/gid to %s/%s (%ld/%ld).",
- p->pw_name, (g ? g->gr_name : "???"),
- (long) p->pw_uid, (long) p->pw_gid);
-
- /* Change the gid to be a safe one. If we can't do that, then
- print a warning. We change the gid before the uid so that we
- change the gid while still root. */
- if (setgid (p->pw_gid) != 0)
- {
- hack_uid_errno = errno;
- sprintf (hack_uid_error, "couldn't set gid to %s (%ld)",
- (g ? g->gr_name : "???"), (long) p->pw_gid);
- }
-
- /* Now change the uid to be a safe one. */
- if (setuid (p->pw_uid) != 0)
- {
- hack_uid_errno = errno;
- sprintf (hack_uid_error, "couldn't set uid to %s (%ld)",
- p->pw_name, (long) p->pw_uid);
- }
- }
+ int i = strlen (argv [argc]) + 1;
+ saved_argv [argc] = (char *) malloc (i);
+ memcpy (saved_argv [argc], argv [argc], i);
}
-# ifndef NO_LOCKING
- else /* disable locking if already being run as "someone else" */
- {
- struct passwd *p = getpwuid (getuid ());
- if (!p ||
- !strcmp (p->pw_name, "root") ||
- !strcmp (p->pw_name, "nobody") ||
- !strcmp (p->pw_name, "noaccess") ||
- !strcmp (p->pw_name, "daemon") ||
- !strcmp (p->pw_name, "bin") ||
- !strcmp (p->pw_name, "sys"))
- {
- si->locking_disabled_p = True;
- si->nolock_reason = hack_uid_buf;
- sprintf (si->nolock_reason, "running as %s", p->pw_name);
- }
- }
-# endif /* !NO_LOCKING */
}
+
+/* Re-execs the process with the arguments in saved_argv.
+ Does not return unless there was an error.
+ */
void
-hack_uid_warn (saver_info *si)
+restart_process (saver_info *si)
{
- saver_preferences *p = &si->prefs;
-
- if (! hack_uid_error)
- ;
- else if (hack_uid_errno == 0)
+ if (si->prefs.verbose_p)
{
- if (p->verbose_p)
- printf ("%s: %s\n", progname, hack_uid_error);
- }
- else
- {
- char buf [255];
- sprintf (buf, "%s: %s", progname, hack_uid_error);
- if (hack_uid_errno == -1)
- fprintf (stderr, "%s\n", buf);
- else
- {
- errno = hack_uid_errno;
- perror (buf);
- }
+ int i;
+ fprintf (real_stderr, "%s: re-executing", blurb());
+ for (i = 0; saved_argv[i]; i++)
+ fprintf (real_stderr, " %s", saved_argv[i]);
+ fprintf (real_stderr, "\n");
}
-}
+ describe_uids (si, real_stderr);
+ fprintf (real_stderr, "\n");
-#endif /* !NO_SETUID */
+ fflush (real_stdout);
+ fflush (real_stderr);
+ execvp (saved_argv [0], saved_argv); /* shouldn't return */
+ {
+ char buf [512];
+ sprintf (buf, "%s: could not restart process", blurb());
+ perror(buf);
+ fflush(stderr);
+ }
+ XBell(si->dpy, 0);
+}