libc6's printf and fprintf don't return -1 on closed stdout/stderr.

Bug #20775 reported by Ralph Corderoy
8
Affects Status Importance Assigned to Milestone
GLibC
Fix Released
Medium
glibc (Ubuntu)
Fix Released
Medium
Unassigned

Bug Description

Following a thread on the <email address hidden> mailing list,

    http://lists.gnu.org/archive/html/bug-tar/2005-08/msg00036.html

I've found that libc doesn't return error indicators from functions
dealing with stdout or stderr FILE pointers as described in the man
pages.

Consider

    $ cat closed.c
    #include <stdlib.h>
    #include <stdio.h>

    int main(void)
    {
        FILE *fp;

        fp = fdopen(4, "w");
        if (getenv("LINEBUF")) setlinebuf(stdout);

        fprintf(fp, "getchar=%d\n", getchar());
        fprintf(fp, "puts=%d\n", puts("test puts"));
        fprintf(fp, "printf=%d\n", printf("test printf\n"));
        fprintf(fp, "fprintf=%d\n", fprintf(stdout, "test fprintf out\n"));
        fprintf(fp, "fprintf=%d\n", fprintf(stderr, "test fprintf err\n"));

        return 0;
    }
    $ echo x | ./closed 4>/dev/tty
    getchar=120
    test puts
    puts=10
    test printf
    printf=12
    test fprintf out
    fprintf=17
    test fprintf err
    fprintf=17
    $

OK so far.

    $ ./closed 4>&1 <&- >&- 2>&-
    getchar=-1
    puts=10
    printf=12
    fprintf=17
    fprintf=-1
    $

stdin and stderr accesses correctly return errors. stdout doesn't because
strace(1) shows no write(2)s occur until the buffer's flushed on exit. Then
the write fails.

    write(1, "puts\nprintf\nfprintf\n", 20) = -1 EBADF (Bad file descriptor)

That's why closed.c has the setlinebuf().

    $ LINEBUF= ./closed 4>&1 <&- >&- 2>&-
    getchar=-1
    puts=-1
    printf=12
    fprintf=17
    fprintf=-1
    $

Now puts() fails, good. But not printf() or fprintf() to stdout.
That's despite the writes happening straight away and returning -1.

    write(1, "test puts\n", 10) = -1 EBADF (Bad file descriptor)
    write(4, "puts=-1\n", 8) = 8
    write(1, "test printf\n", 12) = -1 EBADF (Bad file descriptor)
    write(4, "printf=12\n", 10) = 10
    write(1, "test fprintf out\n", 17) = -1 EBADF (Bad file descriptor)
    write(4, "fprintf=17\n", 11) = 11
    write(2, "test fprintf err\n", 17) = -1 EBADF (Bad file descriptor)
    write(4, "fprintf=-1\n", 11) = 11

The fine man page says

    Return value
        Upon successful return, these functions return the number of
        characters printed (not including the trailing ’\0’ used to end
        output to strings). The functions snprintf and vsnprintf do not
        write more than size bytes (including the trailing ’\0’). If
        the output was truncated due to this limit then the return value
        is the number of characters (not including the trailing ’\0’)
        which would have been written to the final string if enough
        space had been available. Thus, a return value of size or more
        means that the output was truncated. (See also below under
        NOTES.) If an output error is encountered, a negative value is
        returned.

yet no negative value is returned. This could cause programs that
carefully check they're creating output to get the wrong end of the
stick.

Jim Meyering, in private email, confirms Solaris 5.9 works as expected.

Platform details:
    $ ldd closed
                    libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7eab000)
            /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0xb7feb000)
    $ dpkg -S /lib/tls/i686/cmov/libc.so.6
    libc6-i686: /lib/tls/i686/cmov/libc.so.6
    $ apt-show-versions -p libc6-i686
    libc6-i686/unknown uptodate 2.3.2.ds1-20ubuntu14

Revision history for this message
In , Karl-xiph (karl-xiph) wrote :

checked of Fedora core 4

A recent report of an app we develope caused me to check the fprintf call. My
understanding is that on an error (like EFBIG from the write syscall) the
fprintf should return a negative value. Instead I'm seeing values greater than 0
returned, which would match the length of the string returned.

It may only be that 1 errno value, I haven't checked all possiblilities but I
thought I should let you know.

karl.

Revision history for this message
In , Ralph Corderoy (ralph-inputplus) wrote :

This looks like it may be similar to an Ubuntu bug that has a
test program. http://bugzilla.ubuntu.com/show_bug.cgi?id=14542

Revision history for this message
Ralph Corderoy (ralph-inputplus) wrote :

glibc's bugzilla has a bug that looks like the same root cause, but
without a test case. I've added pointer to this bug.

    http://sources.redhat.com/bugzilla/show_bug.cgi?id=1146

Jim Meyering, in private email, reports that Debian unstable's
libc-2.3.5 has the same problem.

Can someone please confirm this bug and get it reported upstream.
(glibc request that bugs detected in a distribution are reported against
the distribution.)

Revision history for this message
Carl Henrik Lunde (chlunde+bugs) wrote :

fprintf(3posix):

  ERRORS
         For the conditions under which fprintf() and printf() fail and may
         fail, refer to fputc() or fputwc() .

fputc(3posix):

  ERRORS
         The fputc() function shall fail if either the stream is unbuffered or
         the stream’s buffer needs to be flushed, and:
  ...
         EBADF The file descriptor underlying stream is not a valid file
                descriptor open for writing.
  ...

So it seems to be correct to only fail when the stream's buffer is flushed.

Revision history for this message
Ralph Corderoy (ralph-inputplus) wrote :

(In reply to comment #2)
> ERRORS
> The fputc() function shall fail if either the stream is unbuffered or
> the stream’s buffer needs to be flushed, and:
> ...
> So it seems to be correct to only fail when the stream's buffer is flushed.

I'm a bit unclear what your point is. I agree that without the
setlinebuf(stdout) that the write(2) can occur when the buffer's flushed
on program exit.

    write(1, "puts\nprintf\nfprintf\n", 20) = -1 EBADF

In that case, the routines can't return an error. (However, I'd expect
an explicit fclose(stdout) to return an error; I haven't tested this.)

But with the setlinebuf(stdout) the failing writes are clearly happening before
the routine called by closed.c yet an error hasn't been returned, e.g. by
printf().

    write(1, "test puts\n", 10) = -1 EBADF (Bad file descriptor)
    write(4, "puts=-1\n", 8) = 8
    write(1, "test printf\n", 12) = -1 EBADF (Bad file descriptor)
    write(4, "printf=12\n", 10) = 10
    write(1, "test fprintf out\n", 17) = -1 EBADF (Bad file descriptor)
    write(4, "fprintf=17\n", 11) = 11
    write(2, "test fprintf err\n", 17) = -1 EBADF (Bad file descriptor)
    write(4, "fprintf=-1\n", 11) = 11

Do you agree this is against what the man page describes?

Revision history for this message
Carl Henrik Lunde (chlunde+bugs) wrote :

(In reply to comment #3)
> Do you agree this is against what the man page describes?
>

Yes indeed, sorry, I didn't read your message properly.

Revision history for this message
In , Ralph Corderoy (ralph-inputplus) wrote :

I think Ulrich Drepper may have fixed this in CVS having been told of the
problem by Jim Meyering.

2005-09-04 Ulrich Drepper <email address hidden>
        ...
        * stdio-common/Makefile (tests): Add tst-put-error.
        * stdio-common/tst-put-error.c: New file.
        * libio/fileops.c (_IO_new_file_xsputn): If overflow fails and no more
        data would have to be written signal error.
        * libio/oldfileops.c (_IO_old_file_xsputn): Likewise.

Revision history for this message
Ralph Corderoy (ralph-inputplus) wrote :

Private email from Jim Meyering suggest Ulrich Drepper may have fixed this
in CVS having had this bug report pointed out to him.

2005-09-04 Ulrich Drepper <email address hidden>
        ...
        * stdio-common/Makefile (tests): Add tst-put-error.
        * stdio-common/tst-put-error.c: New file.
        * libio/fileops.c (_IO_new_file_xsputn): If overflow fails and no more
        data would have to be written signal error.
        * libio/oldfileops.c (_IO_old_file_xsputn): Likewise.

Revision history for this message
Jeff Bailey (jbailey) wrote :

(In reply to comment #5)
> Private email from Jim Meyering suggest Ulrich Drepper may have fixed this
> in CVS having had this bug report pointed out to him.

Thanks for the pointer to the commit. Given that this isn't a regression, I'm
inclined to wait until Breezy + 1 for this fix. I don't want to risk changing
the behaviour of printf and fprintf from what's expected this late in the game.

Revision history for this message
In , Andreas Jaeger (jaegerandi) wrote :

Should be fixed by Ulrich's patch.

Changed in glibc:
status: Unknown → Fix Released
Revision history for this message
Ralph Corderoy (ralph-inputplus) wrote :

Same results as above obtained on Ubuntu 6.06.

    $ ldd closed
            linux-gate.so.1 => (0xffffe000)
            libc.so.6 => /lib/tls/libc.so.6 (0xb7e83000)
            /lib/ld-linux.so.2 (0xb7fbe000)
    $ dpkg -S /lib/tls/libc.so.6
    libc6: /lib/tls/libc.so.6
    $ apt-show-versions -p libc6
    libc6/dapper uptodate 2.3.6-0ubuntu20
    $

Changed in glibc:
status: Unconfirmed → Confirmed
Revision history for this message
Mark Reitblatt (mark-reitblatt) wrote :

Test case still fails on Feisty.

Jeff Bailey (jbailey)
Changed in glibc:
assignee: jbailey → nobody
Revision history for this message
Mark Reitblatt (mark-reitblatt) wrote :

Can this be put on the "Definite" list for Feisty + 1? If there are any programs that assume this behavior, we should be able to catch them by fixing it early on.

Revision history for this message
Wouter Stomp (wouterstomp-deactivatedaccount) wrote :

Is this still an issue on gutsy and/or hardy?

Revision history for this message
Ralph Corderoy (ralph-inputplus) wrote : Re: [Bug 20775] Re: libc6's printf and fprintf don't return -1 on closed stdout/stderr.

> Is this still an issue on gutsy and/or hardy?

Seems fixed in AMD64 7.10.

    $ LINEBUF= ./closed 4>&1 <&- >&- 2>&-
    getchar=-1
    puts=-1
    printf=-1
    fprintf=-1
    fprintf=-1
    $ ldd closed
            libc.so.6 => /lib/libc.so.6 (0x00002b9d1e191000)
            /lib64/ld-linux-x86-64.so.2 (0x00002b9d1df73000)
    $ apt-show-versions -p libc6
    libc6/gutsy uptodate 2.6.1-1ubuntu10

Revision history for this message
Matthias Klose (doko) wrote :

> Seems fixed in AMD64 7.10.

closing.

Changed in glibc:
status: Confirmed → Fix Released
Changed in glibc:
importance: Unknown → Medium
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.