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.
This commit is contained in:
Roy Marples 2007-09-21 08:49:43 +00:00
parent ca58877ed0
commit 45bd125dcc
5 changed files with 123 additions and 137 deletions

View File

@ -1,6 +1,18 @@
# ChangeLog for Gentoo System Intialization ("rc") scripts # ChangeLog for Gentoo System Intialization ("rc") scripts
# Copyright 1999-2007 Gentoo Foundation; Distributed under the GPLv2 # Copyright 1999-2007 Gentoo Foundation; Distributed under the GPLv2
21 Sep 2007; Roy Marples <uberlord@gentoo.org>:
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 <uberlord@gentoo.org>: 20 Sep 2007; Roy Marples <uberlord@gentoo.org>:
libeinfo now works out the number of columns from stdout rather than libeinfo now works out the number of columns from stdout rather than

View File

@ -59,7 +59,7 @@ LDLIBS_LIBRC = -leinfo
RCOBJS = checkown.o env-update.o fstabinfo.o mountinfo.o \ RCOBJS = checkown.o env-update.o fstabinfo.o mountinfo.o \
rc-depend.o rc-plugin.o rc-status.o rc-update.o runscript.o \ rc-depend.o rc-plugin.o rc-status.o rc-update.o runscript.o \
start-stop-daemon.o rc.o start-stop-daemon.o rc.o
LDLIBS_RC = $(LDLIBS_LIBRC) -lrc LDLIBS_RC = $(LDLIBS_LIBRC) -lrc -lutil
LIB_TARGETS = $(LIBEINFOSO) $(LIBRCSO) LIB_TARGETS = $(LIBEINFOSO) $(LIBRCSO)
SBIN_TARGETS = rc SBIN_TARGETS = rc

View File

@ -175,17 +175,10 @@ static bool colour_terminal (void)
static int get_term_columns (FILE *stream) 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; struct winsize ws;
if (ioctl (fileno (stream), TIOCGWINSZ, &ws) == 0) if (ioctl (fileno (stream), TIOCGWINSZ, &ws) == 0)
return (ws.ws_col); return (ws.ws_col);
#endif
return (DEFAULT_COLS); return (DEFAULT_COLS);
} }

View File

@ -558,18 +558,15 @@ static pid_t _exec_service (const char *service, const char *arg)
int rc_waitpid (pid_t pid) { int rc_waitpid (pid_t pid) {
int status = 0; int status = 0;
pid_t savedpid = pid; pid_t savedpid = pid;
int retval = -1;
errno = 0; errno = 0;
do { while ((pid = waitpid (savedpid, &status, 0)) > 0) {
pid = waitpid (savedpid, &status, 0); if (pid == savedpid)
if (pid < 0) { retval = WIFEXITED (status) ? WEXITSTATUS (status) : EXIT_FAILURE;
if (errno != ECHILD) }
eerror ("waitpid %d: %s", savedpid, strerror (errno));
return (-1); return (retval);
}
} while (! WIFEXITED (status) && ! WIFSIGNALED (status));
return (WIFEXITED (status) ? WEXITSTATUS (status) : EXIT_FAILURE);
} }
pid_t rc_stop_service (const char *service) pid_t rc_stop_service (const char *service)

View File

@ -10,6 +10,7 @@
#include <sys/select.h> #include <sys/select.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/param.h> #include <sys/param.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/wait.h> #include <sys/wait.h>
@ -23,8 +24,15 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <termios.h>
#include <unistd.h> #include <unistd.h>
#ifdef __linux__
# include <pty.h>
#else
# include <libutil.h>
#endif
#include "builtins.h" #include "builtins.h"
#include "einfo.h" #include "einfo.h"
#include "rc.h" #include "rc.h"
@ -60,6 +68,8 @@ static rc_hook_t hook_out = 0;
static pid_t service_pid = 0; static pid_t service_pid = 0;
static char *prefix = NULL; static char *prefix = NULL;
static bool prefix_locked = false; static bool prefix_locked = false;
static int signal_pipe[2] = { -1, -1 };
static int master_tty = -1;
extern char **environ; extern char **environ;
@ -102,10 +112,9 @@ static void setup_selinux (int argc, char **argv)
static void handle_signal (int sig) static void handle_signal (int sig)
{ {
pid_t pid;
int status;
int serrno = errno; int serrno = errno;
char signame[10] = { '\0' }; char signame[10] = { '\0' };
struct winsize ws;
switch (sig) { switch (sig) {
case SIGHUP: case SIGHUP:
@ -113,16 +122,19 @@ static void handle_signal (int sig)
break; break;
case SIGCHLD: case SIGCHLD:
do { if (signal_pipe[1] > -1) {
pid = waitpid (-1, &status, WNOHANG); if (write (signal_pipe[1], &sig, sizeof (sig)) == -1)
if (pid < 0) { eerror ("%s: send: %s", service, strerror (errno));
if (errno != ECHILD) } else {
eerror ("waitpid: %s", strerror (errno)); wait (0);
return; }
} break;
} while (! WIFEXITED (status) && ! WIFSIGNALED (status));
if (pid == service_pid) case SIGWINCH:
service_pid = 0; if (master_tty >= 0) {
ioctl (fileno (stdout), TIOCGWINSZ, &ws);
ioctl (master_tty, TIOCSWINSZ, &ws);
}
break; break;
case SIGINT: case SIGINT:
@ -297,16 +309,12 @@ static void cleanup (void)
free (service); 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; unsigned int i;
const char *ec; const char *ec = ecolor (ecolor_hilite);
const char *ec_normal = ecolor (ecolor_normal); const char *ec_normal = ecolor (ecolor_normal);
ssize_t ret = 0; ssize_t ret = 0;
int fd = fileno (stdout);
if (fd == fileno (stdout))
ec = ecolor (ecolor_hilite);
else
ec = ecolor (ecolor_bad);
for (i = 0; i < bytes; i++) { for (i = 0; i < bytes; i++) {
/* We don't prefix escape codes, like eend */ /* 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) static bool svc_exec (const char *arg1, const char *arg2)
{ {
bool execok; bool execok;
int stdout_pipes[2]; int fdout = fileno (stdout);
int stderr_pipes[2]; 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 */ /* Setup our signal pipe */
if (prefix) { if (pipe (signal_pipe) == -1)
if (pipe (stdout_pipes)) eerrorx ("%s: pipe: %s", service, applet);
eerror ("pipe: %s", strerror (errno)); for (i = 0; i < 2; i++)
if (pipe (stderr_pipes)) if ((flags = fcntl (signal_pipe[i], F_GETFD, 0) == -1 ||
eerror ("pipe: %s", strerror (errno)); 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(); service_pid = vfork();
if (service_pid == -1) if (service_pid == -1)
eerrorx ("%s: vfork: %s", service, strerror (errno)); eerrorx ("%s: vfork: %s", service, strerror (errno));
if (service_pid == 0) { if (service_pid == 0) {
if (prefix) { if (slave_tty >= 0) {
int flags; /* Hmmm, this shouldn't work in a vfork, but it does which is
* good for us */
if (dup2 (stdout_pipes[1], fileno (stdout)) == -1) close (master_tty);
eerror ("dup2 stdout: %s", strerror (errno));
close (stdout_pipes[0]); dup2 (slave_tty, 0);
if (dup2 (stderr_pipes[1], fileno (stderr)) == -1) dup2 (slave_tty, 1);
eerror ("dup2 stderr: %s", strerror (errno)); dup2 (slave_tty, 2);
close (stderr_pipes[0]); if (slave_tty > 2)
close (slave_tty);
/* 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 (rc_exists (RC_SVCDIR "/runscript.sh")) { 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 */ /* We need to notify the child of window resizes now */
if (prefix) { if (master_tty >= 0)
bool stdout_done = false; signal (SIGWINCH, handle_signal);
bool stdout_prefix_shown = false;
bool stderr_done = false;
bool stderr_prefix_shown = false;
char buffer[RC_LINEBUFFER];
close (stdout_pipes[1]); selfd = MAX (master_tty, signal_pipe[0]) + 1;
close (stderr_pipes[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); if ((s = select (selfd, &rset, NULL, NULL, NULL)) == -1) {
while (! stdout_done && ! stderr_done) { if (errno != EINTR) {
fd_set fds; eerror ("%s: select: %s", service, strerror (errno));
int retval; break;
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 > 0) {
/* Only SIGCHLD signals come down this pipe */
if (FD_ISSET (signal_pipe[0], &rset))
break;
/* Done now, so close the pipes */ if (master_tty >= 0 && FD_ISSET (master_tty, &rset)) {
close(stdout_pipes[0]); bytes = read (master_tty, buffer, sizeof (buffer));
close(stderr_pipes[0]); 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; execok = rc_waitpid (service_pid) == 0 ? true : false;
service_pid = 0; service_pid = 0;
/* Done, so restore the signal handler */
signal (SIGCHLD, handle_signal);
return (execok); return (execok);
} }