1
0
mirror of https://sourceware.org/git/glibc.git synced 2025-08-08 17:42:12 +03:00

libio: Start to return errors when flushing fwrite's buffer [BZ #29459]

When an error happens, fwrite is expected to return a value that is less
than nmemb.  If this error happens while flushing its internal buffer,
fwrite is in a complex scenario: all the data might have been written to
the buffer, indicating a successful copy, but the buffer is expected to
be flushed and it was not.

POSIX.1-2024 states the following about errors on fwrite:

    If an error occurs, the resulting value of the file-position indicator
    for the stream is unspecified.

    The fwrite() function shall return the number of elements successfully
    written, which may be less than nitems if a write error is encountered.

With that in mind, this commit modifies _IO_new_file_write in order to
return the total number of bytes written via the file pointer.  It also
modifies fwrite in order to use the new information and return the
correct number of bytes written even when sputn returns EOF.

Add 2 tests:

1. tst-fwrite-bz29459: This test is based on the reproducer attached to
   bug 29459.  In order to work, it requires to pipe stdout to another
   process making it hard to reuse test-driver.c.  This code is more
   specific to the issue reported.
2. tst-fwrite-pipe: Recreates the issue by creating a pipe that is shared
   with a child process.  Reuses test-driver.c.  Evaluates a more generic
   scenario.

Co-authored-by: Florian Weimer <fweimer@redhat.com>
Reviewed-by: DJ Delorie <dj@redhat.com>
This commit is contained in:
Tulio Magno Quites Machado Filho
2025-01-28 15:37:44 -03:00
parent 45c42b65c2
commit 596a61cf6b
7 changed files with 304 additions and 6 deletions

View File

@@ -97,8 +97,15 @@ struct _IO_FILE_complete
void *_freeres_buf;
struct _IO_FILE **_prevchain;
int _mode;
#ifdef __LP64__
int _unused3;
#endif
__uint64_t _total_written;
#ifndef __LP64__
int _unused3;
#endif
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 5 * sizeof (void *)];
char _unused2[12 * sizeof (int) - 5 * sizeof (void *)];
};
/* These macros are used by bits/stdio.h and internal headers. */

View File

@@ -113,6 +113,7 @@ _IO_new_file_init_internal (struct _IO_FILE_plus *fp)
_IO_link_in (fp);
fp->file._fileno = -1;
fp->file._total_written = 0;
}
/* External version of _IO_new_file_init_internal which switches off
@@ -1185,6 +1186,7 @@ _IO_new_file_write (FILE *f, const void *data, ssize_t n)
f->_flags |= _IO_ERR_SEEN;
break;
}
f->_total_written += count;
to_do -= count;
data = (void *) ((char *) data + count);
}

View File

@@ -36,13 +36,42 @@ _IO_fwrite (const void *buf, size_t size, size_t count, FILE *fp)
return 0;
_IO_acquire_lock (fp);
if (_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1)
written = _IO_sputn (fp, (const char *) buf, request);
{
/* Compute actually written bytes plus pending buffer
contents. */
uint64_t original_total_written
= fp->_total_written + (fp->_IO_write_ptr - fp->_IO_write_base);
written = _IO_sputn (fp, (const char *) buf, request);
if (written == EOF)
{
/* An error happened and we need to find the appropriate return
value. There 3 possible scenarios:
1. If the number of bytes written is between 0..[buffer content],
we need to return 0 because none of the bytes from this
request have been written;
2. If the number of bytes written is between
[buffer content]+1..request-1, that means we managed to write
data requested in this fwrite call;
3. We might have written all the requested data and got an error
anyway. We can't return success, which means we still have to
return less than request. */
if (fp->_total_written > original_total_written)
{
written = fp->_total_written - original_total_written;
/* If everything was reported as written and somehow an
error occurred afterwards, avoid reporting success. */
if (written == request)
--written;
}
else
/* Only already-pending buffer contents was written. */
written = 0;
}
}
_IO_release_lock (fp);
/* We have written all of the input in case the return value indicates
this or EOF is returned. The latter is a special case where we
simply did not manage to flush the buffer. But the data is in the
buffer and therefore written as far as fwrite is concerned. */
if (written == request || written == EOF)
this. */
if (written == request)
return count;
else
return written / size;