You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
6.6 KiB
202 lines
6.6 KiB
commit be349d7042de84c3c5157a5c1fbcad580aed33e1 |
|
Author: Siddhesh Poyarekar <siddhesh@redhat.com> |
|
Date: Thu Dec 4 08:08:37 2014 +0530 |
|
|
|
ftell: seek to end only when there are unflushed bytes (BZ #17647) |
|
|
|
Currently we seek to end of file if there are unflushed writes or the |
|
stream is in write mode, to get the current offset for writing in |
|
append mode, which is the end of file. The latter case (i.e. stream |
|
is in write mode, but no unflushed writes) is unnecessary since it |
|
will only happen when the stream has just been flushed, in which case |
|
the recorded offset ought to be reliable. |
|
|
|
Removing that case lets ftell give the correct offset when it follows |
|
an ftruncate. The latter truncates the file, but does not change the |
|
file position, due to which it is permissible to call ftell without an |
|
intervening fseek call. |
|
|
|
Tested on x86_64 to verify that the added test case fails without the |
|
patch and succeeds with it, and that there are no additional |
|
regressions due to it. |
|
|
|
[BZ #17647] |
|
* libio/fileops.c (do_ftell): Seek only when there are |
|
unflushed writes. |
|
* libio/wfileops.c (do_ftell_wide): Likewise. |
|
* libio/tst-ftell-active-handler.c (do_ftruncate_test): New |
|
test case. |
|
(do_one_test): Call it. |
|
|
|
diff --git glibc-2.17-c758a686/libio/fileops.c glibc-2.17-c758a686/libio/fileops.c |
|
index e0d7b76..1fc5719 100644 |
|
--- glibc-2.17-c758a686/libio/fileops.c |
|
+++ glibc-2.17-c758a686/libio/fileops.c |
|
@@ -943,15 +943,14 @@ do_ftell (_IO_FILE *fp) |
|
yet. */ |
|
if (fp->_IO_buf_base != NULL) |
|
{ |
|
- bool was_writing = (fp->_IO_write_ptr > fp->_IO_write_base |
|
- || _IO_in_put_mode (fp)); |
|
+ bool unflushed_writes = (fp->_IO_write_ptr > fp->_IO_write_base); |
|
|
|
bool append_mode = (fp->_flags & _IO_IS_APPENDING) == _IO_IS_APPENDING; |
|
|
|
/* When we have unflushed writes in append mode, seek to the end of the |
|
file and record that offset. This is the only time we change the file |
|
stream state and it is safe since the file handle is active. */ |
|
- if (was_writing && append_mode) |
|
+ if (unflushed_writes && append_mode) |
|
{ |
|
result = _IO_SYSSEEK (fp, 0, _IO_seek_end); |
|
if (result == _IO_pos_BAD) |
|
@@ -961,7 +960,7 @@ do_ftell (_IO_FILE *fp) |
|
} |
|
|
|
/* Adjust for unflushed data. */ |
|
- if (!was_writing) |
|
+ if (!unflushed_writes) |
|
offset -= fp->_IO_read_end - fp->_IO_read_ptr; |
|
/* We don't trust _IO_read_end to represent the current file offset when |
|
writing in append mode because the value would have to be shifted to |
|
diff --git glibc-2.17-c758a686/libio/tst-ftell-active-handler.c glibc-2.17-c758a686/libio/tst-ftell-active-handler.c |
|
index e9dc7b3..9f23c55 100644 |
|
--- glibc-2.17-c758a686/libio/tst-ftell-active-handler.c |
|
+++ glibc-2.17-c758a686/libio/tst-ftell-active-handler.c |
|
@@ -88,6 +88,95 @@ static size_t file_len; |
|
typedef int (*fputs_func_t) (const void *data, FILE *fp); |
|
fputs_func_t fputs_func; |
|
|
|
+/* This test verifies that the offset reported by ftell is correct after the |
|
+ file is truncated using ftruncate. ftruncate does not change the file |
|
+ offset on truncation and hence, SEEK_CUR should continue to point to the |
|
+ old offset and not be changed to the new offset. */ |
|
+static int |
|
+do_ftruncate_test (const char *filename) |
|
+{ |
|
+ FILE *fp = NULL; |
|
+ int fd; |
|
+ int ret = 0; |
|
+ struct test |
|
+ { |
|
+ const char *mode; |
|
+ int fd_mode; |
|
+ } test_modes[] = { |
|
+ {"r+", O_RDWR}, |
|
+ {"w", O_WRONLY}, |
|
+ {"w+", O_RDWR}, |
|
+ {"a", O_WRONLY}, |
|
+ {"a+", O_RDWR} |
|
+ }; |
|
+ |
|
+ for (int j = 0; j < 2; j++) |
|
+ { |
|
+ for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++) |
|
+ { |
|
+ int fileret; |
|
+ printf ("\tftruncate: %s (file, \"%s\"): ", |
|
+ j == 0 ? "fopen" : "fdopen", |
|
+ test_modes[i].mode); |
|
+ |
|
+ if (j == 0) |
|
+ fileret = get_handles_fopen (filename, fd, fp, test_modes[i].mode); |
|
+ else |
|
+ fileret = get_handles_fdopen (filename, fd, fp, |
|
+ test_modes[i].fd_mode, |
|
+ test_modes[i].mode); |
|
+ |
|
+ if (fileret != 0) |
|
+ return fileret; |
|
+ |
|
+ /* Write some data. */ |
|
+ size_t written = fputs_func (data, fp); |
|
+ |
|
+ if (written == EOF) |
|
+ { |
|
+ printf ("fputs[1] failed to write data\n"); |
|
+ ret |= 1; |
|
+ } |
|
+ |
|
+ /* Record the offset. */ |
|
+ long offset = ftell (fp); |
|
+ |
|
+ /* Flush data to allow switching active handles. */ |
|
+ if (fflush (fp)) |
|
+ { |
|
+ printf ("Flush failed: %m\n"); |
|
+ ret |= 1; |
|
+ } |
|
+ |
|
+ /* Now truncate the file. */ |
|
+ if (ftruncate (fd, 0) != 0) |
|
+ { |
|
+ printf ("Failed to truncate file: %m\n"); |
|
+ ret |= 1; |
|
+ } |
|
+ |
|
+ /* ftruncate does not change the offset, so there is no need to call |
|
+ anything to be able to switch active handles. */ |
|
+ long new_offset = ftell (fp); |
|
+ |
|
+ /* The offset should remain unchanged since ftruncate does not update |
|
+ it. */ |
|
+ if (offset != new_offset) |
|
+ { |
|
+ printf ("Incorrect offset. Expected %zu, but got %ld\n", |
|
+ offset, new_offset); |
|
+ |
|
+ ret |= 1; |
|
+ } |
|
+ else |
|
+ printf ("offset = %ld\n", offset); |
|
+ |
|
+ fclose (fp); |
|
+ } |
|
+ } |
|
+ |
|
+ return ret; |
|
+} |
|
/* Test that ftell output after a rewind is correct. */ |
|
static int |
|
do_rewind_test (const char *filename) |
|
@@ -481,6 +570,7 @@ do_one_test (const char *filename) |
|
ret |= do_write_test (filename); |
|
ret |= do_append_test (filename); |
|
ret |= do_rewind_test (filename); |
|
+ ret |= do_ftruncate_test (filename); |
|
|
|
return ret; |
|
} |
|
diff --git glibc-2.17-c758a686/libio/wfileops.c glibc-2.17-c758a686/libio/wfileops.c |
|
index 6a088b1..71281c1 100644 |
|
--- glibc-2.17-c758a686/libio/wfileops.c |
|
+++ glibc-2.17-c758a686/libio/wfileops.c |
|
@@ -626,16 +626,15 @@ do_ftell_wide (_IO_FILE *fp) |
|
const wchar_t *wide_read_base; |
|
const wchar_t *wide_read_ptr; |
|
const wchar_t *wide_read_end; |
|
- bool was_writing = ((fp->_wide_data->_IO_write_ptr |
|
- > fp->_wide_data->_IO_write_base) |
|
- || _IO_in_put_mode (fp)); |
|
+ bool unflushed_writes = (fp->_wide_data->_IO_write_ptr |
|
+ > fp->_wide_data->_IO_write_base); |
|
|
|
bool append_mode = (fp->_flags & _IO_IS_APPENDING) == _IO_IS_APPENDING; |
|
|
|
/* When we have unflushed writes in append mode, seek to the end of the |
|
file and record that offset. This is the only time we change the file |
|
stream state and it is safe since the file handle is active. */ |
|
- if (was_writing && append_mode) |
|
+ if (unflushed_writes && append_mode) |
|
{ |
|
result = _IO_SYSSEEK (fp, 0, _IO_seek_end); |
|
if (result == _IO_pos_BAD) |
|
@@ -674,7 +673,7 @@ do_ftell_wide (_IO_FILE *fp) |
|
struct _IO_codecvt *cv = fp->_codecvt; |
|
int clen = (*cv->__codecvt_do_encoding) (cv); |
|
|
|
- if (!was_writing) |
|
+ if (!unflushed_writes) |
|
{ |
|
if (clen > 0) |
|
{
|
|
|