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
# 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>:
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 \
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

View File

@ -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);
}

View File

@ -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)

View File

@ -10,6 +10,7 @@
#include <sys/select.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/wait.h>
@ -23,8 +24,15 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#ifdef __linux__
# include <pty.h>
#else
# include <libutil.h>
#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);
}