write() not retried after EINTR in printf and echo

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

write() not retried after EINTR in printf and echo

Serge van den Boom-3
Configuration Information [Automatically generated, do not change]:
Machine: x86_64
OS: linux-gnu
Compiler: gcc
Compilation CFLAGS:  -DPROGRAM='bash' -DCONF_HOSTTYPE='x86_64' -DCONF_OSTYPE='linux-gnu' -DCONF_MACHTYPE='x86_64-unknown-linux-gnu' -DCONF_VENDOR='unknown' -DLOCALEDIR='/opt/bash-4.4.12/share/locale' -DPACKAGE='bash' -DSHELL -DHAVE_CONFIG_H   -I.  -I. -I./include -I./lib   -g -O2 -Wno-parentheses -Wno-format-security
uname output: Linux test 4.4.0-98-generic #121-Ubuntu SMP Tue Oct 10 14:24:03 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
Machine Type: x86_64-unknown-linux-gnu

Bash Version: 4.4
Patch Level: 12
Release Status: release

Description:
        If during an 'echo' or 'printf', the write() system call returns an
        EINTR error, the write() call is not retried.
        This can can happen when a system call is interrupted when a
        signal is received, which can happen when the user resized the
        terminal while the write() call is blocked.

        As a consequence, the intended output of 'echo' or 'printf' will be
        missing.

Repeat-By:
        1. Run the following command in a terminal (at least xterm works):
                while :; do builtin echo foo; done | sleep 100
        2. Wait a few seconds
        3. Resize the terminal (try again if this does not work immediately)
           This will send a 'SIGWINCH' signal to bash. (You can also send
           it manually.)
        4. "bash: echo: write error: Interrupted system call" will appear.

        The same thing happens with 'printf' instead of 'echo'. There may be
        other bash functionality which suffers from this.

        The pipe, the 'sleep 100', and the waiting in step 2, are to fill up
        the buffer of the pipe, so that the call to write() will block.

        To see that data is actually omitted from the output (and that this
        is not only a warning), you could run something like this instead
        of step 1:
            I=0
            while :; do
                builtin echo "$I"
                I=$(($I + 1))
            done | { sleep 5; cat > /tmp/test; }
        Trigger the sending of the signal in the first 5 seconds, and then let
        it run for a few seconds after. Inspecting /tmp/test will show that
        certain numbers in the sequence are missing.

Fix:
        I have not examined the actual Bash code, but typically, code
        which causes such a problem will look like this:
            int writeResult = write(fd, buf, toWrite);
            if (writeResult == -1) {
                perror("write");
                // Handle error here.
            }

        It should be replaced by something like:
            const char *bufPtr = buf;
            int leftToWrite = toWrite;
            while (leftToWrite > 0) {
            int writeResult = write(fd, bufPtr, leftToWrite);
                if (writeResult == -1) {
                    if (errno == EINTR)
                        continue;

                    perror("write");
                    // Handle error here.
                }
               
                leftToWrite -= writeResult;
                bufPtr += writeResult;
            }


Reply | Threaded
Open this post in threaded view
|

Re: write() not retried after EINTR in printf and echo

Chet Ramey
On 1/12/18 12:05 PM, Serge van den Boom wrote:

> Bash Version: 4.4
> Patch Level: 12
> Release Status: release
>
> Description:
> If during an 'echo' or 'printf', the write() system call returns an
> EINTR error, the write() call is not retried.
> This can can happen when a system call is interrupted when a
> signal is received, which can happen when the user resized the
> terminal while the write() call is blocked.

There are a couple of signals for which this is the appropriate behavior.
The right fix is to install handlers with the SA_RESTART flag set. The
next version of bash will install its SIGWINCH handler with SA_RESTART.

--
``The lyf so short, the craft so long to lerne.'' - Chaucer
                 ``Ars longa, vita brevis'' - Hippocrates
Chet Ramey, UTech, CWRU    [hidden email]    http://tiswww.cwru.edu/~chet/