X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=driver%2Fsubprocs.c;h=88270d6ca0ad448ba55f374d1110faa3fb02ed5c;hp=9070b81bb829537b440baa7e0d492011f9559ec0;hb=6b1c86cf395f59389e4ece4ea8f4bea2c332745b;hpb=6bb727f03bff0389fbb1349d7df4c9d8d7532959 diff --git a/driver/subprocs.c b/driver/subprocs.c index 9070b81b..88270d6c 100644 --- a/driver/subprocs.c +++ b/driver/subprocs.c @@ -1,6 +1,5 @@ /* subprocs.c --- choosing, spawning, and killing screenhacks. - * xscreensaver, Copyright (c) 1991, 1992, 1993, 1995, 1997, 1998 - * Jamie Zawinski + * xscreensaver, Copyright (c) 1991-2008 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -22,44 +21,40 @@ #include /* not used for much... */ #ifndef ESRCH -#include +# include #endif #include /* sys/resource.h needs this for timeval */ +#include /* for PATH_MAX */ -#ifndef VMS - -# include /* for setpriority() and PRIO_PROCESS */ +#ifdef HAVE_SYS_WAIT_H # include /* for waitpid() and associated macros */ +#endif -#else /* VMS */ - -# if __DECC_VER >= 50200000 -# include -# endif +#ifdef HAVE_SETRLIMIT +# include /* for setrlimit() and RLIMIT_AS */ +#endif +#ifdef VMS # include # include /* for close */ # include /* for getpid */ -# define pid_t int -# define fork vfork - +# define pid_t int +# define fork vfork #endif /* VMS */ #include /* for the signal names */ -#ifndef NO_SETUID -#include /* for getpwnam() and struct passwd */ -#include /* 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... */ @@ -71,212 +66,133 @@ extern int kill (pid_t, int); /* signal() is in sys/signal.h... */ #define Widget void* #include "xscreensaver.h" +#include "exec.h" #include "yarandom.h" - +#include "visual.h" /* for id_to_visual() */ 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) +/* Used when printing error/debugging messages from signal handlers. + */ +static const char * +no_malloc_number_to_string (long num) { - if (nice_level == 0) - return; - -#if defined(HAVE_NICE) - { - int old_nice = nice (0); - int n = nice_level - old_nice; - errno = 0; - if (nice (n) == -1 && errno != 0) - { - char buf [512]; - sprintf (buf, "%s: nice(%d) failed", progname, n); - perror (buf); - } - } -#elif defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS) - if (setpriority (PRIO_PROCESS, getpid(), nice_level) != 0) - { - char buf [512]; - sprintf (buf, "%s: setpriority(PRIO_PROCESS, %lu, %d) failed", - progname, (unsigned long) getpid(), nice_level); - perror (buf); - } -#else - fprintf (stderr, - "%s: don't know how to change process priority on this system.\n", - progname); + static char string[128] = ""; + int num_digits; + Bool negative_p = False; -#endif -} + num_digits = 0; + if (num == 0) + return "0"; -#ifndef VMS - -static void -exec_simple_command (const char *command) -{ - char *av[1024]; - int ac = 0; - char *token = strtok (strdup(command), " \t"); - while (token) + if (num < 0) { - av[ac++] = token; - token = strtok(0, " \t"); + negative_p = True; + num = -num; } - av[ac] = 0; - execvp (av[0], av); /* shouldn't return. */ + while ((num > 0) && (num_digits < sizeof(string) - 1)) + { + int digit; + digit = (int) num % 10; + num_digits++; + string[sizeof(string) - 1 - num_digits] = digit + '0'; + num /= 10; + } - { - char buf [512]; - sprintf (buf, "%s: could not execute \"%s\"", progname, av[0]); - perror (buf); + if (negative_p) + { + num_digits++; + string[sizeof(string) - 1 - num_digits] = '-'; + } - if (errno == ENOENT && - (token = getenv("PATH"))) - { -# ifndef PATH_MAX -# ifdef MAXPATHLEN -# define PATH_MAX MAXPATHLEN -# else -# define PATH_MAX 2048 -# endif -# endif - char path[PATH_MAX]; - fprintf (stderr, "\n"); - *path = 0; -# if defined(HAVE_GETCWD) - getcwd (path, sizeof(path)); -# elif defined(HAVE_GETWD) - getwd (path); -# endif - if (*path) - fprintf (stderr, " Current directory is: %s\n", path); - fprintf (stderr, " PATH is:\n"); - token = strtok (strdup(token), ":"); - while (token) - { - fprintf (stderr, " %s\n", token); - token = strtok(0, ":"); - } - fprintf (stderr, "\n"); - } - } - fflush(stderr); - fflush(stdout); - exit (1); /* Note that this only exits a child fork. */ + return string + sizeof(string) - 1 - num_digits; } - -static void -exec_complex_command (const char *shell, const char *command) +/* Like write(), but runs strlen() on the arg to get the length. */ +static int +write_string (int fd, const char *str) { - char *av[5]; - int ac = 0; - char *command2 = (char *) malloc (strlen (command) + 6); - memcpy (command2, "exec ", 5); - memcpy (command2 + 5, command, strlen (command) + 1); + return write (fd, str, strlen (str)); +} - /* Invoke the shell as "/bin/sh -c 'exec prog -arg -arg ...'" */ - av [ac++] = (char *) shell; - av [ac++] = "-c"; - av [ac++] = command2; - av [ac] = 0; +static int +write_long (int fd, long n) +{ + const char *str = no_malloc_number_to_string (n); + return write_string (fd, str); +} - execvp (av[0], av); /* shouldn't return. */ - { - char buf [512]; - sprintf (buf, "%s: execvp(\"%s\") failed", progname, av[0]); - perror (buf); - fflush(stderr); - fflush(stdout); - exit (1); /* Note that this only exits a child fork. */ - } -} +/* RLIMIT_AS (called RLIMIT_VMEM on some systems) controls the maximum size + of a process's address space, i.e., the maximal brk(2) and mmap(2) values. + Setting this lets you put a cap on how much memory a process can allocate. -#else /* VMS */ + Except the "and mmap()" part kinda makes this useless, since many GL + implementations end up using mmap() to pull the whole frame buffer into + memory (or something along those lines) making it appear processes are + using hundreds of megabytes when in fact they're using very little, and + we end up capping their mallocs prematurely. YAY! + */ +#if defined(RLIMIT_VMEM) && !defined(RLIMIT_AS) +# define RLIMIT_AS RLIMIT_VMEM +#endif static void -exec_vms_command (const char *command) +limit_subproc_memory (int address_space_limit, Bool verbose_p) { - system (command); - fflush (stderr); - fflush (stdout); - exit (1); /* Note that this only exits a child fork. */ -} -#endif /* !VMS */ +/* This has caused way more problems than it has solved... + Let's just completely ignore the "memoryLimit" option now. + */ +#undef HAVE_SETRLIMIT +#if defined(HAVE_SETRLIMIT) && defined(RLIMIT_AS) + struct rlimit r; -static void -exec_screenhack (saver_info *si, const char *command) -{ - /* I don't believe what a sorry excuse for an operating system UNIX is! - - - I want to spawn a process. - - I want to know it's pid so that I can kill it. - - I would like to receive a message when it dies of natural causes. - - I want the spawned process to have user-specified arguments. - - If shell metacharacters are present (wildcards, backquotes, etc), the - only way to parse those arguments is to run a shell to do the parsing - for you. - - And the only way to know the pid of the process is to fork() and exec() - it in the spawned side of the fork. - - But if you're running a shell to parse your arguments, this gives you - the pid of the *shell*, not the pid of the *process* that you're - actually interested in, which is an *inferior* of the shell. This also - means that the SIGCHLD you get applies to the shell, not its inferior. - (Why isn't that sufficient? I don't remember any more, but it turns - out that it isn't.) - - So, the only solution, when metacharacters are present, is to force the - shell to exec() its inferior. What a fucking hack! We prepend "exec " - to the command string, and hope it doesn't contain unquoted semicolons - or ampersands (we don't search for them, because we don't want to - prohibit their use in quoted strings (messages, for example) and parsing - out the various quote characters is too much of a pain.) - - (Actually, Clint Wong points out that process groups - might be used to take care of this problem; this may be worth considering - some day, except that, 1: this code works now, so why fix it, and 2: from - what I've seen in Emacs, dealing with process groups isn't especially - portable.) - */ - saver_preferences *p = &si->prefs; + if (address_space_limit < 10 * 1024) /* let's not be crazy */ + return; -#ifndef VMS - Bool hairy_p = !!strpbrk (command, "*?$&!<>[];`'\\\""); + if (getrlimit (RLIMIT_AS, &r) != 0) + { + char buf [512]; + sprintf (buf, "%s: getrlimit(RLIMIT_AS) failed", blurb()); + perror (buf); + return; + } - if (p->verbose_p) - printf ("%s: spawning \"%s\" in pid %lu%s.\n", - progname, command, (unsigned long) getpid (), - (hairy_p ? " (via shell)" : "")); - - if (hairy_p) - /* If it contains any shell metacharacters, do it the hard way, - and fork a shell to parse the arguments for us. */ - exec_complex_command (p->shell, command); - else - /* Otherwise, we can just exec the program directly. */ - exec_simple_command (command); + r.rlim_cur = address_space_limit; -#else /* VMS */ - if (p->verbose_p) - printf ("%s: spawning \"%s\" in pid %lu.\n", progname, command, getpid()); - exec_vms_command (command); -#endif /* VMS */ + if (setrlimit (RLIMIT_AS, &r) != 0) + { + char buf [512]; + sprintf (buf, "%s: setrlimit(RLIMIT_AS, {%lu, %lu}) failed", + blurb(), r.rlim_cur, r.rlim_max); + perror (buf); + return; + } - abort(); /* that shouldn't have returned. */ -} + if (verbose_p) + { + int i = address_space_limit; + char buf[100]; + if (i >= (1<<30) && i == ((i >> 30) << 30)) + sprintf(buf, "%dG", i >> 30); + else if (i >= (1<<20) && i == ((i >> 20) << 20)) + sprintf(buf, "%dM", i >> 20); + else if (i >= (1<<10) && i == ((i >> 10) << 10)) + sprintf(buf, "%dK", i >> 10); + else + sprintf(buf, "%d bytes", i); + fprintf (stderr, "%s: limited pid %lu address space to %s.\n", + blurb(), (unsigned long) getpid (), buf); + } + +#endif /* HAVE_SETRLIMIT && RLIMIT_AS */ +} /* Management of child processes, and de-zombification. @@ -295,21 +211,26 @@ enum job_status { struct screenhack_job { char *name; pid_t pid; + int screen; enum job_status status; struct screenhack_job *next; }; 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); + +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", + fprintf (stderr, " %5ld: %2d: (%s) %s\n", (long) job->pid, + job->screen, (job->status == job_running ? "running" : job->status == job_stopped ? "stopped" : job->status == job_killed ? " killed" : @@ -317,37 +238,44 @@ show_job_list (void) job->name); fprintf (stderr, "\n"); } -#endif static void clean_job_list (void); static struct screenhack_job * -make_job (pid_t pid, const char *cmd) +make_job (pid_t pid, int screen, const char *cmd) { struct screenhack_job *job = (struct screenhack_job *) malloc (sizeof(*job)); static char name [1024]; const char *in = cmd; char *out = name; + int got_eq = 0; + int first = 1; clean_job_list(); + AGAIN: while (isspace(*in)) in++; /* skip whitespace */ - while (!isspace(*in) && *in != ':') + while (!isspace(*in) && *in != ':') { + if (*in == '=') got_eq = 1; *out++ = *in++; /* snarf first token */ - while (isspace(*in)) in++; /* skip whitespace */ - if (*in == ':') /* token was a visual name; skip it. */ - { - in++; + } + + if (got_eq) /* if the first token was FOO=bar */ + { /* then get the next token instead. */ + got_eq = 0; out = name; - while (isspace(*in)) in++; /* skip whitespace */ - while (!isspace(*in)) *out++ = *in++; /* snarf first token */ + first = 0; + goto AGAIN; } + + while (isspace(*in)) in++; /* skip whitespace */ *out = 0; job->name = strdup(name); job->pid = pid; + job->screen = screen; job->status = job_running; job->next = jobs; jobs = job; @@ -424,28 +352,56 @@ static void describe_dead_child (saver_info *, pid_t, int wait_status); static int block_sigchld_handler = 0; -static void +#ifdef HAVE_SIGACTION + sigset_t +#else /* !HAVE_SIGACTION */ + int +#endif /* !HAVE_SIGACTION */ block_sigchld (void) { #ifdef HAVE_SIGACTION + struct sigaction sa; sigset_t child_set; + + memset (&sa, 0, sizeof (sa)); + sa.sa_handler = SIG_IGN; + sigaction (SIGPIPE, &sa, NULL); + sigemptyset (&child_set); sigaddset (&child_set, SIGCHLD); sigprocmask (SIG_BLOCK, &child_set, 0); -#endif /* HAVE_SIGACTION */ + +#else /* !HAVE_SIGACTION */ + signal (SIGPIPE, SIG_IGN); +#endif /* !HAVE_SIGACTION */ block_sigchld_handler++; + +#ifdef HAVE_SIGACTION + return child_set; +#else /* !HAVE_SIGACTION */ + return 0; +#endif /* !HAVE_SIGACTION */ } -static void +void unblock_sigchld (void) { #ifdef HAVE_SIGACTION + struct sigaction sa; sigset_t child_set; + + memset(&sa, 0, sizeof (sa)); + sa.sa_handler = SIG_DFL; + sigaction(SIGPIPE, &sa, NULL); + sigemptyset(&child_set); sigaddset(&child_set, SIGCHLD); sigprocmask(SIG_UNBLOCK, &child_set, 0); -#endif /* HAVE_SIGACTION */ + +#else /* !HAVE_SIGACTION */ + signal(SIGPIPE, SIG_DFL); +#endif /* !HAVE_SIGACTION */ block_sigchld_handler--; } @@ -472,7 +428,7 @@ kill_job (saver_info *si, pid_t pid, int signal) { if (p->verbose_p) fprintf (stderr, "%s: no child %ld to signal!\n", - progname, (long) pid); + blurb(), (long) pid); goto DONE; } @@ -486,31 +442,27 @@ kill_job (saver_info *si, pid_t pid, int signal) default: abort(); } -#ifdef SIGSTOP - if (p->verbose_p) - fprintf (stderr, "%s: %s pid %lu.\n", progname, - (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", - (unsigned long) job->pid); -#endif /* !SIGSTOP */ + fprintf (stderr, "%s: %d: %s pid %lu (%s)\n", + blurb(), job->screen, + (job->status == job_killed ? "killing" : + job->status == job_stopped ? "suspending" : "resuming"), + (unsigned long) job->pid, + job->name); status = kill (job->pid, signal); if (p->verbose_p && status < 0) { if (errno == ESRCH) - fprintf (stderr, "%s: child process %lu (%s) was already dead.\n", - progname, job->pid, job->name); + fprintf (stderr, + "%s: %d: child process %lu (%s) was already dead.\n", + blurb(), job->screen, (unsigned long) job->pid, job->name); else { char buf [1024]; - sprintf (buf, "%s: couldn't kill child process %lu (%s)", - progname, job->pid, job->name); + sprintf (buf, "%s: %d: couldn't kill child process %lu (%s)", + blurb(), job->screen, (unsigned long) job->pid, job->name); perror (buf); } } @@ -533,11 +485,20 @@ sigchld_handler (int sig) { 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, + { + /* Don't call fprintf() from signal handlers, as it might malloc. + fprintf(stderr, "%s: got SIGCHLD%s\n", blurb(), (block_sigchld_handler ? " (blocked)" : "")); -#endif /* DEBUG */ + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": got SIGCHLD"); + + if (block_sigchld_handler) + write_string (STDERR_FILENO, " (blocked)\n"); + else + write_string (STDERR_FILENO, "\n"); + } if (block_sigchld_handler < 0) abort(); @@ -554,6 +515,7 @@ sigchld_handler (int sig) #ifndef VMS + static void await_dying_children (saver_info *si) { @@ -564,14 +526,34 @@ await_dying_children (saver_info *si) 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) + { + /* Don't call fprintf() from signal handlers, as it might malloc. + fprintf (stderr, "%s: waitpid(-1) ==> %ld (%d)\n", blurb(), + (long) kid, errno); + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": waitpid(-1) ==> "); + write_long (STDERR_FILENO, (long) kid); + write_string (STDERR_FILENO, " ("); + write_long (STDERR_FILENO, (long) errno); + write_string (STDERR_FILENO, ")\n"); + } + else + { + /* Don't call fprintf() from signal handlers, as it might malloc. + fprintf (stderr, "%s: waitpid(-1) ==> %ld\n", blurb(), + (long) kid); + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": waitpid(-1) ==> "); + write_long (STDERR_FILENO, (long) kid); + write_string (STDERR_FILENO, "\n"); + } + } /* 0 means no more children to reap. -1 means error -- except "interrupted system call" isn't a "real" @@ -592,6 +574,7 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status) saver_preferences *p = &si->prefs; struct screenhack_job *job = find_job (kid); const char *name = job ? job->name : ""; + int screen_no = job ? job->screen : 0; if (WIFEXITED (wait_status)) { @@ -609,12 +592,38 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status) if (!job || (exit_status != 0 && (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); + { + /* Don't call fprintf() from signal handlers, as it might malloc. + fprintf (stderr, + "%s: %d: child pid %lu (%s) exited abnormally (code %d).\n", + blurb(), screen_no, (unsigned long) kid, name, exit_status); + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": "); + write_long (STDERR_FILENO, (long) screen_no); + write_string (STDERR_FILENO, ": child pid "); + write_long (STDERR_FILENO, (long) kid); + write_string (STDERR_FILENO, " ("); + write_string (STDERR_FILENO, name); + write_string (STDERR_FILENO, ") exited abnormally (code "); + write_long (STDERR_FILENO, (long) exit_status); + write_string (STDERR_FILENO, ").\n"); + } else if (p->verbose_p) - printf ("%s: child pid %lu (%s) exited normally.\n", - progname, (unsigned long) kid, name); + { + /* Don't call fprintf() from signal handlers, as it might malloc. + fprintf (stderr, "%s: %d: child pid %lu (%s) exited normally.\n", + blurb(), screen_no, (unsigned long) kid, name); + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": "); + write_long (STDERR_FILENO, (long) screen_no); + write_string (STDERR_FILENO, ": child pid "); + write_long (STDERR_FILENO, (long) kid); + write_string (STDERR_FILENO, " ("); + write_string (STDERR_FILENO, name); + write_string (STDERR_FILENO, ") exited normally.\n"); + } if (job) job->status = job_dead; @@ -625,9 +634,23 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status) !job || job->status != job_killed || WTERMSIG (wait_status) != SIGTERM) - fprintf (stderr, "%s: child pid %lu (%s) terminated with %s.\n", - progname, (unsigned long) kid, name, - signal_name (WTERMSIG(wait_status))); + { + /* Don't call fprintf() from signal handlers, as it might malloc. + fprintf (stderr, "%s: %d: child pid %lu (%s) terminated with %s.\n", + blurb(), screen_no, (unsigned long) kid, name, + signal_name (WTERMSIG(wait_status))); + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": "); + write_long (STDERR_FILENO, (long) screen_no); + write_string (STDERR_FILENO, ": child pid "); + write_long (STDERR_FILENO, (long) kid); + write_string (STDERR_FILENO, " ("); + write_string (STDERR_FILENO, name); + write_string (STDERR_FILENO, ") terminated with signal "); + write_long (STDERR_FILENO, WTERMSIG(wait_status)); + write_string (STDERR_FILENO, ".\n"); + } if (job) job->status = job_dead; @@ -635,17 +658,41 @@ describe_dead_child (saver_info *si, pid_t kid, int wait_status) else if (WIFSTOPPED (wait_status)) { if (p->verbose_p) - fprintf (stderr, "%s: child pid %lu (%s) stopped with %s.\n", - progname, (unsigned long) kid, name, - signal_name (WSTOPSIG (wait_status))); + { + /* Don't call fprintf() from signal handlers, as it might malloc. + fprintf (stderr, "%s: child pid %lu (%s) stopped with %s.\n", + blurb(), (unsigned long) kid, name, + signal_name (WSTOPSIG (wait_status))); + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": "); + write_long (STDERR_FILENO, (long) screen_no); + write_string (STDERR_FILENO, ": child pid "); + write_long (STDERR_FILENO, (long) kid); + write_string (STDERR_FILENO, " ("); + write_string (STDERR_FILENO, name); + write_string (STDERR_FILENO, ") stopped with signal "); + write_long (STDERR_FILENO, WSTOPSIG(wait_status)); + write_string (STDERR_FILENO, ".\n"); + } if (job) job->status = job_stopped; } else { + /* Don't call fprintf() from signal handlers, as it might malloc. fprintf (stderr, "%s: child pid %lu (%s) died in a mysterious way!", - progname, (unsigned long) kid, name); + blurb(), (unsigned long) kid, name); + */ + write_string (STDERR_FILENO, blurb()); + write_string (STDERR_FILENO, ": "); + write_long (STDERR_FILENO, (long) screen_no); + write_string (STDERR_FILENO, ": child pid "); + write_long (STDERR_FILENO, (long) kid); + write_string (STDERR_FILENO, " ("); + write_string (STDERR_FILENO, name); + write_string (STDERR_FILENO, ") died in a mysterious way!"); if (job) job->status = job_dead; } @@ -685,7 +732,7 @@ init_sigchld (void) 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; @@ -696,7 +743,7 @@ init_sigchld (void) 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 */ @@ -708,37 +755,126 @@ init_sigchld (void) static Bool -select_visual_of_hack (saver_screen_info *ssi, const char *hack) +select_visual_of_hack (saver_screen_info *ssi, screenhack *hack) { saver_info *si = ssi->global; saver_preferences *p = &si->prefs; Bool selected; - static char vis [1024]; - const char *in = hack; - char *out = vis; - while (isspace(*in)) in++; /* skip whitespace */ - while (!isspace(*in) && *in != ':') - *out++ = *in++; /* snarf first token */ - while (isspace(*in)) in++; /* skip whitespace */ - *out = 0; - if (*in == ':') - selected = select_visual(ssi, vis); + if (hack->visual && *hack->visual) + selected = select_visual(ssi, hack->visual); else selected = select_visual(ssi, 0); - if (!selected && (p->verbose_p || si->demo_mode_p)) + if (!selected && (p->verbose_p || si->demoing_p)) + fprintf (stderr, + (si->demoing_p + ? "%s: warning, no \"%s\" visual for \"%s\".\n" + : "%s: no \"%s\" visual; skipping \"%s\".\n"), + blurb(), + (hack->visual && *hack->visual ? hack->visual : "???"), + hack->command); + + return selected; +} + + +static void +print_path_error (const char *program) +{ + char buf [512]; + char *cmd = strdup (program); + char *token = strchr (cmd, ' '); + + if (token) *token = 0; + sprintf (buf, "%s: could not execute \"%.100s\"", blurb(), cmd); + free (cmd); + perror (buf); + + if (errno == ENOENT && + (token = getenv("PATH"))) + { +# ifndef PATH_MAX +# ifdef MAXPATHLEN +# define PATH_MAX MAXPATHLEN +# else +# define PATH_MAX 2048 +# endif +# endif + char path[PATH_MAX]; + fprintf (stderr, "\n"); + *path = 0; +# if defined(HAVE_GETCWD) + if (! getcwd (path, sizeof(path))) + *path = 0; +# elif defined(HAVE_GETWD) + getwd (path); +# endif + if (*path) + fprintf (stderr, " Current directory is: %s\n", path); + fprintf (stderr, " PATH is:\n"); + token = strtok (strdup(token), ":"); + while (token) + { + fprintf (stderr, " %s\n", token); + token = strtok(0, ":"); + } + fprintf (stderr, "\n"); + } +} + + +/* Executes the command in another process. + Command may be any single command acceptable to /bin/sh. + It may include wildcards, but no semicolons. + If successful, the pid of the other process is returned. + Otherwise, -1 is returned and an error may have been + printed to stderr. + */ +pid_t +fork_and_exec (saver_screen_info *ssi, const char *command) +{ + saver_info *si = ssi->global; + saver_preferences *p = &si->prefs; + pid_t forked; + + switch ((int) (forked = fork ())) { - if (*in == ':') in++; - while (isspace(*in)) in++; - fprintf (stderr, - (si->demo_mode_p - ? "%s: warning, no \"%s\" visual for \"%s\".\n" - : "%s: no \"%s\" visual; skipping \"%s\".\n"), - progname, (vis ? vis : "???"), in); + case -1: + { + char buf [255]; + sprintf (buf, "%s: couldn't fork", blurb()); + perror (buf); + break; + } + + case 0: + close (ConnectionNumber (si->dpy)); /* close display fd */ + limit_subproc_memory (p->inferior_memory_limit, p->verbose_p); + hack_subproc_environment (ssi); /* set $DISPLAY */ + + if (p->verbose_p) + fprintf (stderr, "%s: %d: spawning \"%s\" in pid %lu.\n", + blurb(), ssi->number, command, + (unsigned long) getpid ()); + + exec_command (p->shell, command, p->nice_inferior); + + /* If that returned, we were unable to exec the subprocess. + Print an error message, if desired. + */ + if (! p->ignore_uninstalled_p) + print_path_error (command); + + exit (1); /* exits child fork */ + break; + + default: /* parent */ + (void) make_job (forked, ssi->number, command); + break; } - return selected; + return forked; } @@ -750,96 +886,134 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p) 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; + screenhack *hack; pid_t forked; char buf [255]; - int new_hack; - - if (si->demo_mode_p) + int new_hack = -1; + int retry_count = 0; + Bool force = False; + + AGAIN: + + if (p->screenhacks_count < 1) + { + /* No hacks at all */ + new_hack = -1; + } + else if (p->screenhacks_count == 1) + { + /* Exactly one hack in the list */ + new_hack = 0; + } + 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. */ + if (ssi->current_hack < 0) + new_hack = p->screenhacks_count - 1; + else + new_hack = ((ssi->current_hack + p->screenhacks_count - 1) + % p->screenhacks_count); + } + else if (si->selection_mode > 0) { - hack = si->demo_hack; - - /* Ignore visual-selection failure if in demo mode. */ - (void) select_visual_of_hack (ssi, hack); + /* Select a specific hack, by number (via the ACTIVATE command.) */ + new_hack = ((si->selection_mode - 1) % p->screenhacks_count); + force = True; } - else + else if (p->mode == ONE_HACK && + p->selected_hack >= 0) { - 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 specific hack, by number (via "One Saver" mode.) */ + new_hack = p->selected_hack; + force = True; + } + else if (p->mode == BLANK_ONLY || p->mode == DONT_BLANK) + { + new_hack = -1; + } + else if (p->mode == RANDOM_HACKS_SAME && + ssi->number != 0) + { + /* Use the same hack that's running on screen 0. + (Assumes this function was called on screen 0 first.) + */ + new_hack = si->screens[0].current_hack; + } + else /* (p->mode == RANDOM_HACKS) */ + { + /* Select a random hack (but not the one we just ran.) */ + while ((new_hack = random () % p->screenhacks_count) + == ssi->current_hack) + ; + } - if (!select_visual_of_hack (ssi, hack)) + if (new_hack < 0) /* don't run a hack */ + { + ssi->current_hack = -1; + if (si->selection_mode < 0) + si->selection_mode = 0; + return; + } + + ssi->current_hack = new_hack; + hack = p->screenhacks[ssi->current_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) + select_visual_of_hack (ssi, hack); + + if (!force && + (!hack->enabled_p || + !on_path_p (hack->command) || + !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: %d: no programs enabled, or no suitable visuals.\n", + blurb(), ssi->number); + return; } + else + goto AGAIN; } - si->next_mode_p = 0; - - /* If there's a visual description on the front of the command, nuke it. + /* Turn off "next" and "prev" modes now, but "demo" mode is only + turned off by explicit action. */ - { - char *in = hack; - while (isspace(*in)) in++; /* skip whitespace */ - hack = in; - while (!isspace(*in) && *in != ':') in++; /* snarf first token */ - while (isspace(*in)) in++; /* skip whitespace */ - if (*in == ':') - { - in++; - while (isspace(*in)) in++; - hack = in; - } - } + if (si->selection_mode < 0) + si->selection_mode = 0; - switch ((int) (forked = fork ())) + forked = fork_and_exec (ssi, hack->command); + switch ((int) forked) { - case -1: - sprintf (buf, "%s: couldn't fork", progname); + case -1: /* fork failed */ + case 0: /* child fork (can't happen) */ + sprintf (buf, "%s: couldn't fork", blurb()); perror (buf); restore_real_vroot (si); - saver_exit (si, 1); - - case 0: - close (ConnectionNumber (si->dpy)); /* close display fd */ - nice_subproc (p->nice_inferior); /* change process priority */ - hack_environment (ssi); /* set $DISPLAY */ - exec_screenhack (si, hack); /* this does not return */ - abort(); + saver_exit (si, 1, "couldn't fork"); break; default: ssi->pid = forked; - (void) make_job (forked, hack); break; } } @@ -849,12 +1023,21 @@ spawn_screenhack_1 (saver_screen_info *ssi, Bool first_time_p) void spawn_screenhack (saver_info *si, Bool first_time_p) { - int i; - for (i = 0; i < si->nscreens; i++) + if (monitor_powered_on_p (si)) { - saver_screen_info *ssi = &si->screens[i]; - spawn_screenhack_1 (ssi, first_time_p); + int i; + for (i = 0; i < si->nscreens; i++) + { + saver_screen_info *ssi = &si->screens[i]; + spawn_screenhack_1 (ssi, first_time_p); + } } + else if (si->prefs.verbose_p) + fprintf (stderr, + "%s: X says monitor has powered down; " + "not launching a hack.\n", blurb()); + + store_saver_status (si); /* store current hack numbers */ } @@ -910,66 +1093,51 @@ emergency_kill_subproc (saver_info *si) Bool screenhack_running_p (saver_info *si) { - Bool result = True; + Bool any_running_p = False; int i; for (i = 0; i < si->nscreens; i++) { saver_screen_info *ssi = &si->screens[i]; - if (!ssi->pid) - result = False; + if (ssi->pid) any_running_p = True; } - return result; + return any_running_p; } -/* 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 (); + + /* don't free (npath) -- some implementations of putenv (BSD 4.4, + glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2) + do not. So we must leak it (and/or the previous setting). Yay. + */ } +#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 @@ -979,161 +1147,211 @@ hack_environment (saver_screen_info *ssi) be the screen on which this particular hack is running -- not the display specification which the driver itself is using, since the driver ignores its screen number and manages all existing screens. + + Likewise, store a window ID in $XSCREENSAVER_WINDOW -- this will allow + us to (eventually) run multiple hacks in Xinerama mode, where each hack + has the same $DISPLAY but a different piece of glass. */ saver_info *si = ssi->global; const char *odpy = DisplayString (si->dpy); - char *ndpy = (char *) malloc(strlen(odpy) + 20); - int screen_number; - char *s; - - for (screen_number = 0; screen_number < si->nscreens; screen_number++) - if (ssi == &si->screens[screen_number]) - break; - if (screen_number >= si->nscreens) abort(); + char *ndpy = (char *) malloc (strlen(odpy) + 20); + char *nssw = (char *) malloc (40); + char *s, *c; strcpy (ndpy, "DISPLAY="); s = ndpy + strlen(ndpy); strcpy (s, odpy); - while (*s && *s != ':') s++; /* skip to colon */ - while (*s == ':') s++; /* skip over colons */ + /* We have to find the last colon since it is the boundary between + hostname & screen - IPv6 numeric format addresses may have many + colons before that point, and DECnet addresses always have two colons */ + c = strrchr(s,':'); /* skip to last colon */ + if (c != NULL) s = c+1; while (isdigit(*s)) s++; /* skip over dpy number */ while (*s == '.') s++; /* skip over dot */ if (s[-1] != '.') *s++ = '.'; /* put on a dot */ - sprintf(s, "%d", screen_number); /* put on screen number */ + sprintf(s, "%d", ssi->real_screen_number); /* put on screen number */ + + sprintf (nssw, "XSCREENSAVER_WINDOW=0x%lX", + (unsigned long) ssi->screensaver_window); /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems any more, right? It's not Posix, but everyone seems to have it. */ #ifdef HAVE_PUTENV if (putenv (ndpy)) abort (); + if (putenv (nssw)) + abort (); + + /* don't free ndpy/nssw -- some implementations of putenv (BSD 4.4, + glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2) + do not. So we must leak it (and/or the previous setting). Yay. + */ #endif /* HAVE_PUTENV */ } -/* 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. - */ +/* GL crap */ -#ifndef NO_SETUID +Visual * +get_best_gl_visual (saver_screen_info *ssi) +{ + saver_info *si = ssi->global; + pid_t forked; + int fds [2]; + int in, out; + char buf[1024]; -static int hack_uid_errno; -static char hack_uid_buf [255], *hack_uid_error; + char *av[10]; + int ac = 0; -void -hack_uid (saver_info *si) -{ + av[ac++] = "xscreensaver-gl-helper"; + av[ac] = 0; - /* 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) + if (pipe (fds)) { - 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); - } + perror ("error creating pipe:"); + return 0; + } - /* 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); - } - } + in = fds [0]; + out = fds [1]; + + switch ((int) (forked = fork ())) + { + case -1: + { + sprintf (buf, "%s: couldn't fork", blurb()); + perror (buf); + saver_exit (si, 1, 0); + } + case 0: + { + int stdout_fd = 1; + + close (in); /* don't need this one */ + close (ConnectionNumber (si->dpy)); /* close display fd */ + + if (dup2 (out, stdout_fd) < 0) /* pipe stdout */ + { + perror ("could not dup() a new stdout:"); + return 0; + } + hack_subproc_environment (ssi); /* set $DISPLAY */ + + execvp (av[0], av); /* shouldn't return. */ + + if (errno != ENOENT /* || si->prefs.verbose_p */ ) + { + /* Ignore "no such file or directory" errors. + Issue all other exec errors, though. */ + sprintf (buf, "%s: running %s", blurb(), av[0]); + perror (buf); + } + exit (1); /* exits fork */ + break; + } + default: + { + int result = 0; + int wait_status = 0; + + FILE *f = fdopen (in, "r"); + unsigned long v = 0; + char c; + + close (out); /* don't need this one */ + + *buf = 0; + if (! fgets (buf, sizeof(buf)-1, f)) + *buf = 0; + fclose (f); + + /* Wait for the child to die. */ + waitpid (-1, &wait_status, 0); + + if (1 == sscanf (buf, "0x%lx %c", &v, &c)) + result = (int) v; + + if (result == 0) + { + if (si->prefs.verbose_p) + { + int L = strlen(buf); + fprintf (stderr, "%s: %s did not report a GL visual!\n", + blurb(), av[0]); + + if (L && buf[L-1] == '\n') + buf[--L] = 0; + if (*buf) + fprintf (stderr, "%s: %s said: \"%s\"\n", + blurb(), av[0], buf); + } + return 0; + } + else + { + Visual *v = id_to_visual (ssi->screen, result); + if (si->prefs.verbose_p) + fprintf (stderr, "%s: %d: %s: GL visual is 0x%X%s.\n", + blurb(), ssi->number, + av[0], result, + (v == ssi->default_visual ? " (default)" : "")); + return v; + } + } } -# 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 */ + + abort(); } + + +/* Restarting the xscreensaver process from scratch. */ + +static char **saved_argv; + void -hack_uid_warn (saver_info *si) +save_argv (int argc, char **argv) { - saver_preferences *p = &si->prefs; - - if (! hack_uid_error) - ; - else if (hack_uid_errno == 0) + saved_argv = (char **) calloc (argc+2, sizeof (char *)); + saved_argv [argc] = 0; + while (argc--) { - if (p->verbose_p) - printf ("%s: %s\n", progname, hack_uid_error); + int i = strlen (argv [argc]) + 1; + saved_argv [argc] = (char *) malloc (i); + memcpy (saved_argv [argc], argv [argc], i); } - else +} + + +/* Re-execs the process with the arguments in saved_argv. Does not return. + */ +void +restart_process (saver_info *si) +{ + fflush (stdout); + fflush (stderr); + shutdown_stderr (si); + if (si->prefs.verbose_p) { - 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 (stderr, "%s: re-executing", blurb()); + for (i = 0; saved_argv[i]; i++) + fprintf (stderr, " %s", saved_argv[i]); + fprintf (stderr, "\n"); } -} + describe_uids (si, stderr); + fprintf (stderr, "\n"); -#endif /* !NO_SETUID */ + fflush (stdout); + fflush (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); + abort(); + } +}