[PATCH] prevent retries on fclose/fflush after write errors

Denys Vlasenko vda.linux at googlemail.com
Sun Feb 24 17:59:25 UTC 2013


On Monday 18 February 2013 20:58, Bernhard Reutner-Fischer wrote:
> On 25 March 2012 07:54, Mike Frysinger <vapier at gentoo.org> wrote:
> > fixed the style & pushed.  thanks all!  it's nice we have people versed in
> > esoteric low level aspects nowadays.
> 
> hmz, revisiting..
> To recap:
> 
> On 7 February 2011 02:41, Denys Vlasenko <vda.linux at googlemail.com> wrote:
> > Currently, uclibc retains buffered data on stdio write errors,
> > and subsequent fclose and fflush will try to write it out again
> > (in most cases, in vain).
> >
> > Which results in something like this:
> >
> > On Wednesday 26 January 2011 13:21, Baruch Siach wrote:
> >> Hi busybox list,
> >>
> >> I'm running the following command under strace (thanks Rob):
> >>
> >> echo 56 > /sys/class/gpio/export
> >>
> >> and I see the following output:
> >>
> >> write(1, "56\n", 3)                     = -1 EBUSY (Device or resource busy)
> >> write(1, "5", 1)                        = 1
> >>
> >> The first EBUSY is OK, since GPIO 56 is already requested. But the second
> >> write() attempt seems strange, and leads to an unwanted outcome. GPIO 5 gets
> >> exported.
> >
> >
> > This patch prevents that.
> 
> This rather sounds to me that this second write with an odd length is wrong, no?

> IIUC the patch (867bac0c) that went in for this issue is causing
> https://bugs.uclibc.org/5156 (fclose and fflush do not report write failures)
> 
> I am attaching denys.c, i think this is the testcase you had in mind
> in your patch that Mike applied as
> 867bac0c750401d2f429ad6bb066498c3b8b35c1
> The attached fulltest.c is from above mentioned PR5156.
> Also attached is a patch that seems to cure PR5156 (the hunk against
> _wcommit.c is your rephrased patch cited below, not needed for 5156
> though) and that basically reverts your 867bac0c.
> With this patch we would get:
> $ ./denys_uc > /dev/null
> Hi there 1
> Hi there 2
> $ ./denys_uc 2> /dev/null
> $ ./denys_glibc > /dev/null
> Hi there 1
> Hi there 2
> $ ./denys_glibc 2> /dev/null
> $ ./fulltest_glibc
> fwrite: x=4, errno=25
> fflush: y=-1, errno=28
> fputc 1: x=88, errno=25
> fflush: y=-1, errno=28
> fputc 2: x=88, errno=25
> fclose: y=-1, errno=28
> # the ENOTYPEWRITER i dislike
> $ ./fulltest
> fwrite: x=4, errno=0
> fflush: y=-1, errno=28
> fputc 1: x=88, errno=28
> fflush: y=-1, errno=28
> fputc 2: x=88, errno=28
> fclose: y=-1, errno=28
> so 5156 would be fixed, we'd diverge from glibc in the type-writer
> errno (which is imo ok) and (since we fully buffer fwrite) do not
> errno the fwrite.

Testcases? Good! Please put them into test/* when you're done.


> I guess it would not fix your (line-buffered, i assume?) gpio echo, would it?

I don't know whether it will fix that. I don't have the testcase handy.

In my testing after this patch uclibc drops all output after error occurs.

I have a test program which sets stdout to nonblocking mode, writes lines until
printf fails, then resets stdouty to blocking mode, does fflush(NULL),
and prints last printf result and count of lines.

I run it like this:   ./a.out | { sleep 1; cat; }

Before your patch, the result was:
Hi there 000000001
Hi there 000000002
Hi there 000000003
...
Hi there 000002644
Hi there 000002645
Hi there 00000
n:-1
Bye 000002647


After this patch, output just ends:
...
Hi there 000002590
Hi there 000002591
Hi there 000002592 <---no newline char printed here

strace of the above:
...
18:39:29.777493 write(1, "\nHi there 000002539\nHi there 000002540\nHi there 000002541\nHi there 000002542\nHi there 000002543\nHi "..., 1022) = 1022
18:39:29.777550 write(1, "2592", 4)     = 4
18:39:29.777640 write(1, "\nHi there 000002593\nHi there 000002594\nHi there 000002595\nHi there 000002596\nHi there 000002597\nHi "..., 1022) = -1 EAGAIN (Resource temporarily u
navailable)
18:39:29.777702 fcntl(1, F_GETFL)       = 0x801 (flags O_WRONLY|O_NONBLOCK)
18:39:29.777759 fcntl(1, F_SETFL, O_WRONLY) = 0
18:39:29.777814 _exit(0)                = ?
18:39:29.777870 +++ exited with 0 +++

IOW: EAGAIN become a fatal, data-gobbling error,
stream will remain inoperative even after error goes away (!).
The final printf in my program (see source) just didn't do anything.

glibc does this:
...
Hi there 000003449
Hi th
n:-1
Bye 000003666

As you see, glibc doesn't "kill" the stream on EAGAIN.



I think this part of the patch is wrong:

-               } else {
+               } else /* if (errno != EINTR && errno != EAGAIN) */ {
.
                        __STDIO_STREAM_SET_ERROR(stream);
-
+#if 0
                        /* We buffer data on "transient" errors, but discard it
                         * on "hard" ones. Example of a hard error:
                         *
@@ -81,6 +81,7 @@ size_t attribute_hidden __stdio_WRITE(register FILE *stream,
                                /* do we have other "soft" errors? */
                                break;
                        }
+#endif
 #ifdef __STDIO_BUFFERS
                        stodo = __STDIO_STREAM_


it simple drops special handling of EAGAIN.


The test program follows.
-- 
vda

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <fcntl.h>
static void ndelay_on(int fd)
{
        int flags = fcntl(fd, F_GETFL);
        if (flags & O_NONBLOCK)
                return;
        fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
static void ndelay_off(int fd)
{
        int flags = fcntl(fd, F_GETFL);
        if (!(flags & O_NONBLOCK))
                return;
        fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
}
int main(void) {
        int i, n;
        int newfd = fileno(stdout);

        ndelay_on(newfd);
        i = 1;
        while (1) {
                n = printf("Hi there %09d\n", i++);
                if (n != 19)
                        break;
        }
        ndelay_off(newfd);
        fflush(NULL);
        printf("\nn:%d\nBye %09d\n", n, i++);
        return 0;
}


More information about the uClibc mailing list