diff options
author | Brooks Moses <bmoses@google.com> | 2014-06-12 20:55:39 -0700 |
---|---|---|
committer | Brooks Moses <bmoses@google.com> | 2014-06-12 20:55:39 -0700 |
commit | c88182c60891873c535d2e18386826f1fa76398b (patch) | |
tree | 9da61eccf747f535219312a7de6a6c6799f8bbcb | |
parent | 85a9363da48a11c0a8bf7e1ebe581bf8f92d023a (diff) | |
download | glibc-c88182c60891873c535d2e18386826f1fa76398b.tar.gz glibc-c88182c60891873c535d2e18386826f1fa76398b.tar.xz glibc-c88182c60891873c535d2e18386826f1fa76398b.zip |
For b/15017950, backport patches to fix offset computation for append+ mode on switching from read.
-rw-r--r-- | README.google | 15 | ||||
-rw-r--r-- | libio/Makefile | 6 | ||||
-rw-r--r-- | libio/fileops.c | 114 | ||||
-rw-r--r-- | libio/iofdopen.c | 9 | ||||
-rw-r--r-- | libio/tst-ftell-active-handler.c | 112 | ||||
-rw-r--r-- | libio/tst-ftell-append.c | 169 | ||||
-rw-r--r-- | libio/wfileops.c | 60 |
7 files changed, 388 insertions, 97 deletions
diff --git a/README.google b/README.google index 60afc988a3..48930ee5d1 100644 --- a/README.google +++ b/README.google @@ -343,3 +343,18 @@ tst-spawn.c posix_spawn_file_actions_addopen. https://sourceware.org/bugzilla/show_bug.cgi?id=17048 (bmoses, backport) + +libio/Makefile +libio/iofdopen.c +libio/fileops.c +libio/tst-ftell-active-handler.c +libio/tst-ftell-append.c +libio/wfileops.c + For b/15017950, backport patch to fix offset computation for append+ + mode on switching from read, and preceeding patches to fix another + issue with append files and ftell, and to adjust test environments. + https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=ea33158c96c53a64402a772186956c1f5cb556ae + https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=003e49ed5d2034d73bfcf5324c461785687b7e88 + https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=2482ae433a4249495859343ae1fba408300f2c2e + https://sourceware.org/bugzilla/show_bug.cgi?id=16724 + (bmoses, backport) diff --git a/libio/Makefile b/libio/Makefile index 8da33ea423..ec977877ce 100644 --- a/libio/Makefile +++ b/libio/Makefile @@ -60,7 +60,8 @@ tests = tst_swprintf tst_wprintf tst_swscanf tst_wscanf tst_getwc tst_putwc \ tst-wmemstream1 tst-wmemstream2 \ bug-memstream1 bug-wmemstream1 \ tst-setvbuf1 tst-popen1 tst-fgetwc bug-wsetpos tst-fseek \ - tst-fwrite-error tst-ftell-partial-wide tst-ftell-active-handler + tst-fwrite-error tst-ftell-partial-wide tst-ftell-active-handler \ + tst-ftell-append ifeq (yes,$(build-shared)) # Add test-fopenloc only if shared library is enabled since it depends on # shared localedata objects. @@ -160,6 +161,9 @@ tst-swscanf-ENV = LOCPATH=$(common-objpfx)localedata bug-ftell-ENV = LOCPATH=$(common-objpfx)localedata tst-fgetwc-ENV = LOCPATH=$(common-objpfx)localedata tst-fseek-ENV = LOCPATH=$(common-objpfx)localedata +tst-ftell-partial-wide-ENV = LOCPATH=$(common-objpfx)localedata +tst-ftell-active-handler-ENV = LOCPATH=$(common-objpfx)localedata +tst-ftell-append-ENV = LOCPATH=$(common-objpfx)localedata generated = tst-fopenloc.mtrace tst-fopenloc.check diff --git a/libio/fileops.c b/libio/fileops.c index f5be66ff10..f041347fb0 100644 --- a/libio/fileops.c +++ b/libio/fileops.c @@ -91,7 +91,9 @@ extern struct __gconv_trans_data __libio_translit attribute_hidden; The position in the buffer that corresponds to the position in external file system is normally _IO_read_end, except in putback - mode, when it is _IO_save_end. + mode, when it is _IO_save_end and also when the file is in append mode, + since switching from read to write mode automatically sends the position in + the external file system to the end of file. If the field _fb._offset is >= 0, it gives the offset in the file as a whole corresponding to eGptr(). (?) @@ -232,13 +234,18 @@ _IO_file_open (fp, filename, posix_mode, prot, read_write, is32not64) return NULL; fp->_fileno = fdesc; _IO_mask_flags (fp, read_write,_IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING); - if ((read_write & _IO_IS_APPENDING) && (read_write & _IO_NO_READS)) - if (_IO_SEEKOFF (fp, (_IO_off64_t)0, _IO_seek_end, _IOS_INPUT|_IOS_OUTPUT) - == _IO_pos_BAD && errno != ESPIPE) - { - close_not_cancel (fdesc); - return NULL; - } + /* For append mode, send the file offset to the end of the file. Don't + update the offset cache though, since the file handle is not active. */ + if ((read_write & (_IO_IS_APPENDING | _IO_NO_READS)) + == (_IO_IS_APPENDING | _IO_NO_READS)) + { + _IO_off64_t new_pos = _IO_SYSSEEK (fp, 0, _IO_seek_end); + if (new_pos == _IO_pos_BAD && errno != ESPIPE) + { + close_not_cancel (fdesc); + return NULL; + } + } _IO_link_in ((struct _IO_FILE_plus *) fp); return fp; } @@ -929,43 +936,13 @@ _IO_file_sync_mmap (_IO_FILE *fp) return 0; } -/* Get the current file offset using a system call. This is the safest method - to get the current file offset, since we are sure that we get the current - state of the file. Before the stream handle is activated (by using fread, - fwrite, etc.), an application may alter the state of the file descriptor - underlying it by calling read/write/lseek on it. Using a cached offset at - this point will result in returning the incorrect value. Same is the case - when one switches from reading in a+ mode to writing, where the buffer has - not been flushed - the cached offset would reflect the reading position - while the actual write position would be at the end of the file. - - do_ftell and do_ftell_wide may resort to using the cached offset in some - special cases instead of calling get_file_offset, but those cases should be - thoroughly described. */ -_IO_off64_t -get_file_offset (_IO_FILE *fp) -{ - if ((fp->_flags & _IO_IS_APPENDING) == _IO_IS_APPENDING) - { - struct stat64 st; - bool ret = (_IO_SYSSTAT (fp, &st) == 0 && S_ISREG (st.st_mode)); - if (ret) - return st.st_size; - else - return EOF; - } - else - return _IO_SYSSEEK (fp, 0, _IO_seek_cur); -} - - -/* ftell{,o} implementation. Don't modify any state of the file pointer while - we try to get the current state of the stream. */ +/* ftell{,o} implementation. The only time we modify the state of the stream + is when we have unflushed writes. In that case we seek to the end and + record that offset in the stream object. */ static _IO_off64_t do_ftell (_IO_FILE *fp) { - _IO_off64_t result = 0; - bool use_cached_offset = false; + _IO_off64_t result, offset = 0; /* No point looking at unflushed data if we haven't allocated buffers yet. */ @@ -974,39 +951,45 @@ do_ftell (_IO_FILE *fp) bool was_writing = (fp->_IO_write_ptr > fp->_IO_write_base || _IO_in_put_mode (fp)); + 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) + { + result = _IO_SYSSEEK (fp, 0, _IO_seek_end); + if (result == _IO_pos_BAD) + return EOF; + else + fp->_offset = result; + } + /* Adjust for unflushed data. */ if (!was_writing) - result -= fp->_IO_read_end - fp->_IO_read_ptr; + 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 + the end of the file during a flush. Use the write base instead, along + with the new offset we got above when we did a seek to the end of the + file. */ + else if (append_mode) + offset += fp->_IO_write_ptr - fp->_IO_write_base; + /* For all other modes, _IO_read_end represents the file offset. */ 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)); + offset += fp->_IO_write_ptr - fp->_IO_read_end; } - if (use_cached_offset) - result += fp->_offset; + if (fp->_offset != _IO_pos_BAD) + result = fp->_offset; else - result += get_file_offset (fp); + result = _IO_SYSSEEK (fp, 0, _IO_seek_cur); if (result == EOF) return result; + result += offset; + if (result < 0) { __set_errno (EINVAL); @@ -1016,7 +999,6 @@ do_ftell (_IO_FILE *fp) return result; } - _IO_off64_t _IO_new_file_seekoff (fp, offset, dir, mode) _IO_FILE *fp; diff --git a/libio/iofdopen.c b/libio/iofdopen.c index 3f266f7288..843a4fa65c 100644 --- a/libio/iofdopen.c +++ b/libio/iofdopen.c @@ -167,6 +167,15 @@ _IO_new_fdopen (fd, mode) _IO_mask_flags (&new_f->fp.file, read_write, _IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING); + /* For append mode, set the file offset to the end of the file. Don't + update the offset cache though, since the file handle is not active. */ + if ((read_write & (_IO_IS_APPENDING | _IO_NO_READS)) + == (_IO_IS_APPENDING | _IO_NO_READS)) + { + _IO_off64_t new_pos = _IO_SYSSEEK (&new_f->fp.file, 0, _IO_seek_end); + if (new_pos == _IO_pos_BAD && errno != ESPIPE) + return NULL; + } return &new_f->fp.file; } libc_hidden_ver (_IO_new_fdopen, _IO_fdopen) diff --git a/libio/tst-ftell-active-handler.c b/libio/tst-ftell-active-handler.c index 54bfe6380f..d5a2df4dd4 100644 --- a/libio/tst-ftell-active-handler.c +++ b/libio/tst-ftell-active-handler.c @@ -88,6 +88,107 @@ static size_t file_len; typedef int (*fputs_func_t) (const void *data, FILE *fp); fputs_func_t fputs_func; +/* Test that ftell output after a rewind is correct. */ +static int +do_rewind_test (const char *filename) +{ + int ret = 0; + struct test + { + const char *mode; + int fd_mode; + size_t old_off; + size_t new_off; + } test_modes[] = { + {"w", O_WRONLY, 0, data_len}, + {"w+", O_RDWR, 0, data_len}, + {"r+", O_RDWR, 0, data_len}, + /* The new offsets for 'a' and 'a+' modes have to factor in the + previous writes since they always append to the end of the + file. */ + {"a", O_WRONLY, 0, 3 * data_len}, + {"a+", O_RDWR, 0, 4 * data_len}, + }; + + /* Empty the file before the test so that our offsets are simple to + calculate. */ + FILE *fp = fopen (filename, "w"); + if (fp == NULL) + { + printf ("Failed to open file for emptying\n"); + return 1; + } + fclose (fp); + + for (int j = 0; j < 2; j++) + { + for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++) + { + FILE *fp; + int fd; + int fileret; + + printf ("\trewind: %s (file, \"%s\"): ", j == 0 ? "fdopen" : "fopen", + test_modes[i].mode); + + if (j == 0) + fileret = get_handles_fdopen (filename, fd, fp, + test_modes[i].fd_mode, + test_modes[i].mode); + else + fileret = get_handles_fopen (filename, fd, fp, test_modes[i].mode); + + if (fileret != 0) + return fileret; + + /* Write some content to the file, rewind and ensure that the ftell + output after the rewind is 0. POSIX does not specify what the + behavior is when a file is rewound in 'a' mode, so we retain + current behavior, which is to keep the 0 offset. */ + size_t written = fputs_func (data, fp); + + if (written == EOF) + { + printf ("fputs[1] failed to write data\n"); + ret |= 1; + } + + rewind (fp); + long offset = ftell (fp); + + if (offset != test_modes[i].old_off) + { + printf ("Incorrect old offset. Expected %zu, but got %ld, ", + test_modes[i].old_off, offset); + ret |= 1; + } + else + printf ("old offset = %ld, ", offset); + + written = fputs_func (data, fp); + + if (written == EOF) + { + printf ("fputs[1] failed to write data\n"); + ret |= 1; + } + + /* After this write, the offset in append modes should factor in the + implicit lseek to the end of file. */ + offset = ftell (fp); + if (offset != test_modes[i].new_off) + { + printf ("Incorrect new offset. Expected %zu, but got %ld\n", + test_modes[i].new_off, offset); + ret |= 1; + } + else + printf ("new offset = %ld\n", offset); + } + } + return ret; +} + /* Test that the value of ftell is not cached when the stream handle is not active. */ static int @@ -107,11 +208,13 @@ do_ftell_test (const char *filename) {"w", O_WRONLY, 0, data_len}, {"w+", O_RDWR, 0, data_len}, {"r+", O_RDWR, 0, data_len}, - /* For 'a' and 'a+' modes, the initial file position should be the + /* For the 'a' mode, the initial file position should be the current end of file. After the write, the offset has data_len - added to the old value. */ + added to the old value. For a+ mode however, the initial file + position is the file position of the underlying file descriptor, + since it is initially assumed to be in read mode. */ {"a", O_WRONLY, data_len, 2 * data_len}, - {"a+", O_RDWR, 2 * data_len, 3 * data_len}, + {"a+", O_RDWR, 0, 3 * data_len}, }; for (int j = 0; j < 2; j++) { @@ -149,7 +252,7 @@ do_ftell_test (const char *filename) if (off != test_modes[i].new_off) { printf ("Incorrect new offset. Expected %zu but got %ld\n", - test_modes[i].old_off, off); + test_modes[i].new_off, off); ret |= 1; } else @@ -309,6 +412,7 @@ do_one_test (const char *filename) ret |= do_ftell_test (filename); ret |= do_write_test (filename); ret |= do_append_test (filename); + ret |= do_rewind_test (filename); return ret; } diff --git a/libio/tst-ftell-append.c b/libio/tst-ftell-append.c new file mode 100644 index 0000000000..604dc0342c --- /dev/null +++ b/libio/tst-ftell-append.c @@ -0,0 +1,169 @@ +/* Verify that ftell returns the correct value after a read and a write on a + file opened in a+ mode. + Copyright (C) 2014 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <locale.h> +#include <wchar.h> + +/* data points to either char_data or wide_data, depending on whether we're + testing regular file mode or wide mode respectively. Similarly, + fputs_func points to either fputs or fputws. data_len keeps track of the + length of the current data and file_len maintains the current file + length. */ +#define BUF_LEN 4 +static void *buf; +static char char_buf[BUF_LEN]; +static wchar_t wide_buf[BUF_LEN]; +static const void *data; +static const char *char_data = "abcdefghijklmnopqrstuvwxyz"; +static const wchar_t *wide_data = L"abcdefghijklmnopqrstuvwxyz"; +static size_t data_len; +static size_t file_len; + +typedef int (*fputs_func_t) (const void *data, FILE *fp); +fputs_func_t fputs_func; + +typedef void *(*fgets_func_t) (void *s, int size, FILE *stream); +fgets_func_t fgets_func; + +static int do_test (void); + +#define TEST_FUNCTION do_test () +#include "../test-skeleton.c" + +static FILE * +init_file (const char *filename) +{ + FILE *fp = fopen (filename, "w"); + if (fp == NULL) + { + printf ("fopen: %m\n"); + return NULL; + } + + int written = fputs_func (data, fp); + + if (written == EOF) + { + printf ("fputs failed to write data\n"); + fclose (fp); + return NULL; + } + + file_len = data_len; + + fclose (fp); + + fp = fopen (filename, "a+"); + if (fp == NULL) + { + printf ("fopen(a+): %m\n"); + return NULL; + } + + return fp; +} + +static int +do_one_test (const char *filename) +{ + FILE *fp = init_file (filename); + + if (fp == NULL) + return 1; + + void *ret = fgets_func (buf, BUF_LEN, fp); + + if (ret == NULL) + { + printf ("read failed: %m\n"); + fclose (fp); + return 1; + } + + int written = fputs_func (data, fp); + + if (written == EOF) + { + printf ("fputs failed to write data\n"); + fclose (fp); + return 1; + } + + file_len += data_len; + + long off = ftell (fp); + + if (off != file_len) + { + printf ("Incorrect offset %ld, expected %zu\n", off, file_len); + fclose (fp); + return 1; + } + else + printf ("Correct offset %ld after write.\n", off); + + return 0; +} + +/* Run the tests for regular files and wide mode files. */ +static int +do_test (void) +{ + int ret = 0; + char *filename; + int fd = create_temp_file ("tst-ftell-append-tmp.", &filename); + + if (fd == -1) + { + printf ("create_temp_file: %m\n"); + return 1; + } + + close (fd); + + /* Tests for regular files. */ + puts ("Regular mode:"); + fputs_func = (fputs_func_t) fputs; + fgets_func = (fgets_func_t) fgets; + data = char_data; + buf = char_buf; + data_len = strlen (char_data); + ret |= do_one_test (filename); + + /* Tests for wide files. */ + puts ("Wide mode:"); + if (setlocale (LC_ALL, "en_US.UTF-8") == NULL) + { + printf ("Cannot set en_US.UTF-8 locale.\n"); + return 1; + } + fputs_func = (fputs_func_t) fputws; + fgets_func = (fgets_func_t) fgetws; + data = wide_data; + buf = wide_buf; + data_len = wcslen (wide_data); + ret |= do_one_test (filename); + + return ret; +} diff --git a/libio/wfileops.c b/libio/wfileops.c index b5e2b7d8df..911f6ae56f 100644 --- a/libio/wfileops.c +++ b/libio/wfileops.c @@ -597,12 +597,12 @@ done: } /* ftell{,o} implementation for wide mode. Don't modify any state of the file - pointer while we try to get the current state of the stream. */ + pointer while we try to get the current state of the stream except in one + case, which is when we have unflushed writes in append mode. */ static _IO_off64_t do_ftell_wide (_IO_FILE *fp) { _IO_off64_t result, offset = 0; - bool use_cached_offset = false; /* No point looking for offsets in the buffer if it hasn't even been allocated. */ @@ -615,6 +615,20 @@ do_ftell_wide (_IO_FILE *fp) > fp->_wide_data->_IO_write_base) || _IO_in_put_mode (fp)); + 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) + { + result = _IO_SYSSEEK (fp, 0, _IO_seek_end); + if (result == _IO_pos_BAD) + return EOF; + else + fp->_offset = result; + } + /* XXX For wide stream with backup store it is not very reasonable to determine the offset. The pushed-back character might require a state change and we need not be @@ -699,41 +713,35 @@ do_ftell_wide (_IO_FILE *fp) offset += outstop - out; } - /* _IO_read_end coincides with fp._offset, so the actual file - position is fp._offset - (_IO_read_end - new_write_ptr). */ - offset -= fp->_IO_read_end - fp->_IO_write_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 the end of the file during a flush. Use the write base + instead, along with the new offset we got above when we did a seek + to the end of the file. */ + if (append_mode) + offset += fp->_IO_write_ptr - fp->_IO_write_base; + /* For all other modes, _IO_read_end represents the file offset. */ + else + offset += 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 = (offset != 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) + if (fp->_offset != _IO_pos_BAD) result = fp->_offset; else - result = get_file_offset (fp); + result = _IO_SYSSEEK (fp, 0, _IO_seek_cur); if (result == EOF) return result; result += offset; + if (result < 0) + { + __set_errno (EINVAL); + return EOF; + } + return result; } |