aboutsummaryrefslogtreecommitdiff
path: root/libio/fileops.c
diff options
context:
space:
mode:
Diffstat (limited to 'libio/fileops.c')
-rw-r--r--libio/fileops.c34
1 files changed, 28 insertions, 6 deletions
diff --git a/libio/fileops.c b/libio/fileops.c
index 500629564a..2e7bc8dad9 100644
--- a/libio/fileops.c
+++ b/libio/fileops.c
@@ -964,12 +964,8 @@ get_file_offset (_IO_FILE *fp)
static _IO_off64_t
do_ftell (_IO_FILE *fp)
{
- _IO_off64_t result;
-
- result = get_file_offset (fp);
-
- if (result == EOF)
- return result;
+ _IO_off64_t result = 0;
+ bool use_cached_offset = false;
/* No point looking at unflushed data if we haven't allocated buffers
yet. */
@@ -983,8 +979,34 @@ do_ftell (_IO_FILE *fp)
result -= fp->_IO_read_end - fp->_IO_read_ptr;
else
result += fp->_IO_write_ptr - fp->_IO_read_end;
+
+ /* It is safe to use the cached offset when available if there is
+ unbuffered data (indicating that the file handle is active) and the
+ handle is not for a file open in a+ mode. The latter condition is
+ because there could be a scenario where there is a switch from read
+ mode to write mode using an fseek to an arbitrary position. In this
+ case, there would be unbuffered data due to be appended to the end of
+ the file, but the offset may not necessarily be the end of the
+ file. It is fine to use the cached offset when the a+ stream is in
+ read mode though, since the offset is maintained correctly in that
+ case. Note that this is not a comprehensive set of cases when the
+ offset is reliable. The offset may be reliable even in some cases
+ where there is no unflushed input and the handle is active, but it's
+ just that we don't have a way to identify that condition reliably. */
+ use_cached_offset = (result != 0 && fp->_offset != _IO_pos_BAD
+ && ((fp->_flags & (_IO_IS_APPENDING | _IO_NO_READS))
+ == (_IO_IS_APPENDING | _IO_NO_READS)
+ && was_writing));
}
+ if (use_cached_offset)
+ result += fp->_offset;
+ else
+ result += get_file_offset (fp);
+
+ if (result == EOF)
+ return result;
+
if (result < 0)
{
__set_errno (EINVAL);