From: Karel Zak Date: Tue, 3 Dec 2013 12:32:55 +0000 (+0100) Subject: script: script input redirection / eof handling X-Git-Tag: v2.25-rc1~696 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3822032da3b8431afb9ee2c0a66c210dd2223f8c;p=thirdparty%2Futil-linux.git script: script input redirection / eof handling echo "ps uf" | script does not work because script assume that stdin is terminal and it does not forward EOF to the pty. This patch: * make non-tty use-case more robust (don't call tty ioclts to non-tty file descriptors. * send EOF (CTL('D') control char) to the master channel when detected eof by read() on stdin * wait for empty master and slave file descriptors to be sure that we don't miss date for typescript. This is also necessary to be sure that slave channel (shell) is completely initialized otherwise EOF is ignored. Reported-by: Phillip Susi Signed-off-by: Karel Zak --- diff --git a/term-utils/script.c b/term-utils/script.c index c18274c1c2..277499d5b9 100644 --- a/term-utils/script.c +++ b/term-utils/script.c @@ -58,10 +58,13 @@ #include #include #include +#include +#include #include "closestream.h" #include "nls.h" #include "c.h" +#include "ttyutils.h" #if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H) # include @@ -107,6 +110,7 @@ int fflg = 0; int qflg = 0; int tflg = 0; int forceflg = 0; +int isterm; int die; int resized; @@ -230,9 +234,6 @@ main(int argc, char **argv) { die_if_link(fname); } - if (!isatty(STDIN_FILENO)) - errx(EXIT_FAILURE, _("The stdin is not a terminal.")); - if ((fscript = fopen(fname, aflg ? "a" : "w")) == NULL) { warn(_("cannot open %s"), fname); fail(); @@ -295,9 +296,18 @@ main(int argc, char **argv) { return EXIT_SUCCESS; } +static void wait_for_empty_fd(int fd) +{ + struct pollfd fds[] = { + { .fd = fd, .events = POLLIN } + }; + + while (poll(fds, 1, 50) == 1); +} + void doinput(void) { - ssize_t cc; + ssize_t cc = 0; char ibuf[BUFSIZ]; if (close_stream(fscript) != 0) @@ -314,19 +324,46 @@ doinput(void) { else if (cc < 0 && errno == EINTR && resized) { /* transmit window change information to the child */ - ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&win); - ioctl(slave, TIOCSWINSZ, (char *)&win); + if (isterm) { + ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&win); + ioctl(slave, TIOCSWINSZ, (char *)&win); + } resized = 0; - } - else + + } else break; } + /* To be sure that we don't miss any data */ + wait_for_empty_fd(slave); + wait_for_empty_fd(master); + + if (cc == 0 && errno == 0) { + /* + * Forward EOF from stdin (detected by read() above) to slave + * (shell) to correctly terminate the session. It seems we have + * to wait for empty terminal FDs otherwise EOF maybe ignored + * (why?) and typescript is incomplete. -- kzak Dec-2013 + * + * We usually use this when stdin is not a tty, for example: + * echo "ps" | script + */ + int c = DEF_EOF; + + if (write(master, &c, 1) < 0) { + warn (_("write failed")); + fail(); + } + + /* wait for "exit" message from shell before we print "Script + * done" in done() */ + wait_for_empty_fd(master); + } + + finish(0); /* wait for childern */ done(); } -#include - void finish(int dummy __attribute__ ((__unused__))) { int status; @@ -473,6 +510,9 @@ void fixtty(void) { struct termios rtt; + if (!isterm) + return; + rtt = tt; cfmakeraw(&rtt); rtt.c_lflag &= ~ECHO; @@ -491,6 +531,7 @@ done(void) { time_t tvec; if (subchild) { + /* output process */ if (!qflg) { char buf[BUFSIZ]; tvec = time((time_t *)NULL); @@ -503,13 +544,16 @@ done(void) { master = -1; } else { - tcsetattr(STDIN_FILENO, TCSADRAIN, &tt); + /* input process */ + if (isterm) + tcsetattr(STDIN_FILENO, TCSADRAIN, &tt); if (!qflg) printf(_("Script done, file is %s\n"), fname); #ifdef HAVE_LIBUTEMPTER if (master >= 0) utempter_remove_record(master); #endif + kill(child, SIGTERM); /* make sure we don't create orphans */ } if(eflg) { @@ -524,9 +568,19 @@ done(void) { void getmaster(void) { #if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H) - tcgetattr(STDIN_FILENO, &tt); - ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&win); - if (openpty(&master, &slave, NULL, &tt, &win) < 0) { + int rc; + + isterm = isatty(STDIN_FILENO); + + if (isterm) { + if (tcgetattr(STDIN_FILENO, &tt) != 0) + err(EXIT_FAILURE, _("failed to get terminal attributes")); + ioctl(STDIN_FILENO, TIOCGWINSZ, (char *) &win); + rc = openpty(&master, &slave, NULL, &tt, &win); + } else + rc = openpty(&master, &slave, NULL, NULL, NULL); + + if (rc < 0) { warn(_("openpty failed")); fail(); } @@ -534,6 +588,8 @@ getmaster(void) { char *pty, *bank, *cp; struct stat stb; + isterm = isatty(STDIN_FILENO); + pty = &line[strlen("/dev/ptyp")]; for (bank = "pqrs"; *bank; bank++) { line[strlen("/dev/pty")] = *bank; @@ -552,9 +608,11 @@ getmaster(void) { ok = access(line, R_OK|W_OK) == 0; *tp = 'p'; if (ok) { - tcgetattr(STDIN_FILENO, &tt); - ioctl(STDIN_FILENO, TIOCGWINSZ, - (char *)&win); + if (isterm) { + tcgetattr(STDIN_FILENO, &tt); + ioctl(STDIN_FILENO, TIOCGWINSZ, + (char *)&win); + } return; } close(master); @@ -577,8 +635,10 @@ getslave(void) { warn(_("cannot open %s"), line); fail(); } - tcsetattr(slave, TCSANOW, &tt); - ioctl(slave, TIOCSWINSZ, (char *)&win); + if (isterm) { + tcsetattr(slave, TCSANOW, &tt); + ioctl(slave, TIOCSWINSZ, (char *)&win); + } #endif setsid(); ioctl(slave, TIOCSCTTY, 0);