]> err.no Git - util-linux/commitdiff
script: fix race conditions
authorKarel Zak <kzak@redhat.com>
Mon, 6 Aug 2007 22:36:31 +0000 (00:36 +0200)
committerKarel Zak <kzak@redhat.com>
Mon, 6 Aug 2007 22:36:31 +0000 (00:36 +0200)
script(1) uses three processes (doinput, dooutput and doshell).  It's
possible that the shell process is finished before the input and
output processes are completely initialized. For example:

  $ script -c "printf Bingo"

In particular case the output and input processes read/write data from
shell process in time when the shell process is already done -- so it
hangs on read().

The second problem is that the output process can finish although
there are unread data from finished shell process -- an output in
the typescript file and on terminal is incomplete!

script(1) has to pass:

 $ for i in `seq 1 1000`; do script -q -c "printf 'Bingo\n'"; done | grep -c Bingo
 1000

without problems.

Signed-off-by: Karel Zak <kzak@redhat.com>
misc-utils/script.c

index 03dd93285ba1a559aba6d5e11c0a67660966e618..d3272df51dd5f33949ce9ceb0501e0146ea35494 100644 (file)
@@ -52,6 +52,7 @@
 #include <sys/time.h>
 #include <sys/file.h>
 #include <sys/signal.h>
+#include <errno.h>
 #include "nls.h"
 
 #ifdef __linux__
@@ -97,6 +98,8 @@ int   tflg = 0;
 
 static char *progname;
 
+int die;
+
 static void
 die_if_link(char *fn) {
        struct stat s;
@@ -123,6 +126,7 @@ die_if_link(char *fn) {
 
 int
 main(int argc, char **argv) {
+       struct sigaction sa;
        extern int optind;
        char *p;
        int ch;
@@ -191,7 +195,12 @@ main(int argc, char **argv) {
                printf(_("Script started, file is %s\n"), fname);
        fixtty();
 
-       (void) signal(SIGCHLD, finish);
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = 0;
+
+       sa.sa_handler = finish;
+       sigaction(SIGCHLD, &sa, NULL);
+
        child = fork();
        if (child < 0) {
                perror("fork");
@@ -207,8 +216,10 @@ main(int argc, char **argv) {
                        dooutput();
                else
                        doshell();
-       } else
-               (void) signal(SIGWINCH, resize);
+       } else {
+               sa.sa_handler = resize;
+               sigaction(SIGWINCH, &sa, NULL);
+       }
        doinput();
 
        return 0;
@@ -221,8 +232,12 @@ doinput() {
 
        (void) fclose(fscript);
 
-       while ((cc = read(0, ibuf, BUFSIZ)) > 0)
+       if (die == 0 && child && kill(child, 0) == -1 && errno == ESRCH)
+               die = 1;
+
+       while (die == 0 && (cc = read(0, ibuf, BUFSIZ)) > 0)
                (void) write(master, ibuf, cc);
+
        done();
 }
 
@@ -232,14 +247,10 @@ void
 finish(int dummy) {
        int status;
        register int pid;
-       register int die = 0;
 
        while ((pid = wait3(&status, WNOHANG, 0)) > 0)
                if (pid == child)
                        die = 1;
-
-       if (die)
-               done();
 }
 
 void
@@ -267,6 +278,7 @@ dooutput() {
        char obuf[BUFSIZ];
        struct timeval tv;
        double oldtime=time(NULL), newtime;
+       int flgs = 0;
 
        (void) close(0);
 #ifdef HAVE_LIBUTIL
@@ -277,10 +289,37 @@ dooutput() {
        if (!qflg)
                fprintf(fscript, _("Script started on %s"), obuf);
 
-       for (;;) {
+       if (die == 0 && child && kill(child, 0) == -1 && errno == ESRCH)
+               /*
+                * the SIGCHLD handler could be executed when the "child"
+                * variable is not set yet. It means that the "die" is zero
+                * althought the child process is already done. We have to
+                * check this thing now. Now we have the "child" variable
+                * already initialized. For more details see main() and
+                * finish().  --kzak 07-Aug-2007
+                */
+               die = 1;
+
+       do {
+               if (die && flgs == 0) {
+                       /* ..child is dead, but it doesn't mean that there is
+                        * nothing in buffers.
+                        */
+                       flgs = fcntl(master, F_GETFL, 0);
+                       if (fcntl(master, F_SETFL, (flgs | O_NONBLOCK)) == -1)
+                               break;
+               }
                if (tflg)
                        gettimeofday(&tv, NULL);
+
+               errno = 0;
                cc = read(master, obuf, sizeof (obuf));
+
+               if (die && errno == EINTR && cc <= 0)
+                       /* read() has been interrupted by SIGCHLD, try it again
+                        * with O_NONBLOCK
+                        */
+                       continue;
                if (cc <= 0)
                        break;
                if (tflg) {
@@ -292,7 +331,10 @@ dooutput() {
                (void) fwrite(obuf, 1, cc, fscript);
                if (fflg)
                        (void) fflush(fscript);
-       }
+       } while(1);
+
+       if (flgs)
+               fcntl(master, F_SETFL, flgs);
        done();
 }