diff --git a/ChangeLog b/ChangeLog index 1b4307ec..6ff533f3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,18 @@ # ChangeLog for Gentoo System Intialization ("rc") scripts # Copyright 1999-2007 Gentoo Foundation; Distributed under the GPLv2 + 21 Sep 2007; Roy Marples : + + Use a pty for prefixed output instead of pipes for stdout/stderr. This + is so that programs can get information about the controlling terminal. + This change was triggered by bug #188506 where it's possible that + stdin, stdout and stderr didn't point to a terminal but ended up on one + via our pipes. Using a pty means that stdout and stderr always point to + a terminal, but we lose the ability to tell them apart. + If there is not a pty available then we use un-prefixed output as normal. + This change has also introduced the need for a signal pipe so that + SIGCHLD can exit the loop cleanly. + 20 Sep 2007; Roy Marples : libeinfo now works out the number of columns from stdout rather than diff --git a/src/Makefile b/src/Makefile index 96799aaa..fd79b187 100644 --- a/src/Makefile +++ b/src/Makefile @@ -59,7 +59,7 @@ LDLIBS_LIBRC = -leinfo RCOBJS = checkown.o env-update.o fstabinfo.o mountinfo.o \ rc-depend.o rc-plugin.o rc-status.o rc-update.o runscript.o \ start-stop-daemon.o rc.o -LDLIBS_RC = $(LDLIBS_LIBRC) -lrc +LDLIBS_RC = $(LDLIBS_LIBRC) -lrc -lutil LIB_TARGETS = $(LIBEINFOSO) $(LIBRCSO) SBIN_TARGETS = rc diff --git a/src/libeinfo.c b/src/libeinfo.c index b85b2f42..5025f339 100644 --- a/src/libeinfo.c +++ b/src/libeinfo.c @@ -175,17 +175,10 @@ static bool colour_terminal (void) static int get_term_columns (FILE *stream) { -#if defined(TIOCGSIZE) /* BSD */ - struct ttysize ts; - - if (ioctl (fileno (stream), TIOCGSIZE, &ts) == 0) - return (ts.ts_cols); -#elif defined(TIOCGWINSZ) /* Linux */ struct winsize ws; if (ioctl (fileno (stream), TIOCGWINSZ, &ws) == 0) return (ws.ws_col); -#endif return (DEFAULT_COLS); } diff --git a/src/librc.c b/src/librc.c index 728f1c45..f501719e 100644 --- a/src/librc.c +++ b/src/librc.c @@ -558,18 +558,15 @@ static pid_t _exec_service (const char *service, const char *arg) int rc_waitpid (pid_t pid) { int status = 0; pid_t savedpid = pid; + int retval = -1; errno = 0; - do { - pid = waitpid (savedpid, &status, 0); - if (pid < 0) { - if (errno != ECHILD) - eerror ("waitpid %d: %s", savedpid, strerror (errno)); - return (-1); - } - } while (! WIFEXITED (status) && ! WIFSIGNALED (status)); - - return (WIFEXITED (status) ? WEXITSTATUS (status) : EXIT_FAILURE); + while ((pid = waitpid (savedpid, &status, 0)) > 0) { + if (pid == savedpid) + retval = WIFEXITED (status) ? WEXITSTATUS (status) : EXIT_FAILURE; + } + + return (retval); } pid_t rc_stop_service (const char *service) diff --git a/src/runscript.c b/src/runscript.c index d80d1b81..200d8d88 100644 --- a/src/runscript.c +++ b/src/runscript.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -23,8 +24,15 @@ #include #include #include +#include #include +#ifdef __linux__ +# include +#else +# include +#endif + #include "builtins.h" #include "einfo.h" #include "rc.h" @@ -60,6 +68,8 @@ static rc_hook_t hook_out = 0; static pid_t service_pid = 0; static char *prefix = NULL; static bool prefix_locked = false; +static int signal_pipe[2] = { -1, -1 }; +static int master_tty = -1; extern char **environ; @@ -102,10 +112,9 @@ static void setup_selinux (int argc, char **argv) static void handle_signal (int sig) { - pid_t pid; - int status; int serrno = errno; char signame[10] = { '\0' }; + struct winsize ws; switch (sig) { case SIGHUP: @@ -113,16 +122,19 @@ static void handle_signal (int sig) break; case SIGCHLD: - do { - pid = waitpid (-1, &status, WNOHANG); - if (pid < 0) { - if (errno != ECHILD) - eerror ("waitpid: %s", strerror (errno)); - return; - } - } while (! WIFEXITED (status) && ! WIFSIGNALED (status)); - if (pid == service_pid) - service_pid = 0; + if (signal_pipe[1] > -1) { + if (write (signal_pipe[1], &sig, sizeof (sig)) == -1) + eerror ("%s: send: %s", service, strerror (errno)); + } else { + wait (0); + } + break; + + case SIGWINCH: + if (master_tty >= 0) { + ioctl (fileno (stdout), TIOCGWINSZ, &ws); + ioctl (master_tty, TIOCSWINSZ, &ws); + } break; case SIGINT: @@ -297,16 +309,12 @@ static void cleanup (void) free (service); } -static int write_prefix (int fd, const char *buffer, size_t bytes, bool *prefixed) { +static int write_prefix (const char *buffer, size_t bytes, bool *prefixed) { unsigned int i; - const char *ec; + const char *ec = ecolor (ecolor_hilite); const char *ec_normal = ecolor (ecolor_normal); ssize_t ret = 0; - - if (fd == fileno (stdout)) - ec = ecolor (ecolor_hilite); - else - ec = ecolor (ecolor_bad); + int fd = fileno (stdout); for (i = 0; i < bytes; i++) { /* We don't prefix escape codes, like eend */ @@ -332,43 +340,56 @@ static int write_prefix (int fd, const char *buffer, size_t bytes, bool *prefixe static bool svc_exec (const char *arg1, const char *arg2) { bool execok; - int stdout_pipes[2]; - int stderr_pipes[2]; + int fdout = fileno (stdout); + struct termios tt; + struct winsize ws; + int i; + int flags; + fd_set rset; + int s; + char buffer[RC_LINEBUFFER]; + size_t bytes; + bool prefixed = false; + int selfd; + int slave_tty; - /* Setup our pipes for prefixed output */ - if (prefix) { - if (pipe (stdout_pipes)) - eerror ("pipe: %s", strerror (errno)); - if (pipe (stderr_pipes)) - eerror ("pipe: %s", strerror (errno)); + /* Setup our signal pipe */ + if (pipe (signal_pipe) == -1) + eerrorx ("%s: pipe: %s", service, applet); + for (i = 0; i < 2; i++) + if ((flags = fcntl (signal_pipe[i], F_GETFD, 0) == -1 || + fcntl (signal_pipe[i], F_SETFD, flags | FD_CLOEXEC) == -1)) + eerrorx ("%s: fcntl: %s", service, strerror (errno)); + + /* Open a pty for our prefixed output + * We do this instead of mapping pipes to stdout, stderr so that + * programs can tell if they're attached to a tty or not. + * The only loss is that we can no longer tell the difference + * between the childs stdout or stderr */ + master_tty = slave_tty = -1; + if (prefix && isatty (fdout)) { + tcgetattr (fdout, &tt); + ioctl (fdout, TIOCGWINSZ, &ws); + + /* If the below call fails due to not enough ptys then we don't + * prefix the output, but we still work */ + openpty (&master_tty, &slave_tty, NULL, &tt, &ws); } - /* We need to disable our child signal handler now so we block - until our script returns. */ - signal (SIGCHLD, NULL); - service_pid = vfork(); - if (service_pid == -1) eerrorx ("%s: vfork: %s", service, strerror (errno)); if (service_pid == 0) { - if (prefix) { - int flags; - - if (dup2 (stdout_pipes[1], fileno (stdout)) == -1) - eerror ("dup2 stdout: %s", strerror (errno)); - close (stdout_pipes[0]); - if (dup2 (stderr_pipes[1], fileno (stderr)) == -1) - eerror ("dup2 stderr: %s", strerror (errno)); - close (stderr_pipes[0]); - - /* Stop any scripts from inheriting us */ - if ((flags = fcntl (stdout_pipes[1], F_GETFD, 0)) < 0 || - fcntl (stdout_pipes[1], F_SETFD, flags | FD_CLOEXEC) < 0) - eerror ("fcntl: %s", strerror (errno)); - if ((flags = fcntl (stderr_pipes[1], F_GETFD, 0)) < 0 || - fcntl (stderr_pipes[1], F_SETFD, flags | FD_CLOEXEC) < 0) - eerror ("fcntl: %s", strerror (errno)); + if (slave_tty >= 0) { + /* Hmmm, this shouldn't work in a vfork, but it does which is + * good for us */ + close (master_tty); + + dup2 (slave_tty, 0); + dup2 (slave_tty, 1); + dup2 (slave_tty, 2); + if (slave_tty > 2) + close (slave_tty); } if (rc_exists (RC_SVCDIR "/runscript.sh")) { @@ -386,86 +407,49 @@ static bool svc_exec (const char *arg1, const char *arg2) } } - /* Prefix our piped output */ - if (prefix) { - bool stdout_done = false; - bool stdout_prefix_shown = false; - bool stderr_done = false; - bool stderr_prefix_shown = false; - char buffer[RC_LINEBUFFER]; + /* We need to notify the child of window resizes now */ + if (master_tty >= 0) + signal (SIGWINCH, handle_signal); - close (stdout_pipes[1]); - close (stderr_pipes[1]); + selfd = MAX (master_tty, signal_pipe[0]) + 1; + while (1) { + FD_ZERO (&rset); + FD_SET (signal_pipe[0], &rset); + if (master_tty >= 0) + FD_SET (master_tty, &rset); - memset (buffer, 0, RC_LINEBUFFER); - while (! stdout_done && ! stderr_done) { - fd_set fds; - int retval; - - FD_ZERO (&fds); - FD_SET (stdout_pipes[0], &fds); - FD_SET (stderr_pipes[0], &fds); - retval = select (MAX (stdout_pipes[0], stderr_pipes[0]) + 1, - &fds, 0, 0, 0); - if (retval < 0) { - if (errno != EINTR) { - eerror ("select: %s", strerror (errno)); - break; - } - } else if (retval) { - ssize_t nr; - - /* Wait until we get a lock */ - while (true) { - struct timeval tv; - - if (mkfifo (PREFIX_LOCK, 0700) == 0) { - prefix_locked = true; - break; - } - - if (errno != EEXIST) - eerror ("mkfifo `%s': %s\n", PREFIX_LOCK, strerror (errno)); - tv.tv_sec = 0; - tv.tv_usec = 20000; - select (0, NULL, NULL, NULL, &tv); - } - - if (FD_ISSET (stdout_pipes[0], &fds)) { - if ((nr = read (stdout_pipes[0], buffer, - sizeof (buffer))) <= 0) - stdout_done = true; - else - write_prefix (fileno (stdout), buffer, nr, - &stdout_prefix_shown); - } - - if (FD_ISSET (stderr_pipes[0], &fds)) { - if ((nr = read (stderr_pipes[0], buffer, - sizeof (buffer))) <= 0) - stderr_done = true; - else - write_prefix (fileno (stderr), buffer, nr, - &stderr_prefix_shown); - } - - /* Clear the lock */ - unlink (PREFIX_LOCK); - prefix_locked = false; + if ((s = select (selfd, &rset, NULL, NULL, NULL)) == -1) { + if (errno != EINTR) { + eerror ("%s: select: %s", service, strerror (errno)); + break; } } + + if (s > 0) { + /* Only SIGCHLD signals come down this pipe */ + if (FD_ISSET (signal_pipe[0], &rset)) + break; - /* Done now, so close the pipes */ - close(stdout_pipes[0]); - close(stderr_pipes[0]); + if (master_tty >= 0 && FD_ISSET (master_tty, &rset)) { + bytes = read (master_tty, buffer, sizeof (buffer)); + write_prefix (buffer, bytes, &prefixed); + } + } + } + + close (signal_pipe[0]); + close (signal_pipe[1]); + signal_pipe[0] = signal_pipe[1] = -1; + + if (master_tty >= 0) { + signal (SIGWINCH, SIG_IGN); + close (master_tty); + master_tty = -1; } execok = rc_waitpid (service_pid) == 0 ? true : false; service_pid = 0; - /* Done, so restore the signal handler */ - signal (SIGCHLD, handle_signal); - return (execok); }