]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
script: script input redirection / eof handling
authorKarel Zak <kzak@redhat.com>
Tue, 3 Dec 2013 12:32:55 +0000 (13:32 +0100)
committerKarel Zak <kzak@redhat.com>
Tue, 3 Dec 2013 12:32:55 +0000 (13:32 +0100)
  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 <psusi@ubuntu.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
term-utils/script.c

index c18274c1c2631ae5ce179d55024f53e17d2450ef..277499d5b94de8f133adf7cf90dba6edf0c200c8 100644 (file)
 #include <limits.h>
 #include <locale.h>
 #include <stddef.h>
+#include <sys/wait.h>
+#include <poll.h>
 
 #include "closestream.h"
 #include "nls.h"
 #include "c.h"
+#include "ttyutils.h"
 
 #if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H)
 # include <pty.h>
@@ -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 <sys/wait.h>
-
 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);