diff --git a/libio/Makefile b/libio/Makefile index 22dbcae..488ee51 100644 --- a/libio/Makefile +++ b/libio/Makefile @@ -60,7 +60,7 @@ 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-fwrite-error tst-ftell-active-handler ifeq (yes,$(build-shared)) # Add test-fopenloc only if shared library is enabled since it depends on # shared localedata objects. diff --git a/libio/fileops.c b/libio/fileops.c index a3499be..2e7bc8d 100644 --- a/libio/fileops.c +++ b/libio/fileops.c @@ -929,6 +929,93 @@ _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. */ +static _IO_off64_t +do_ftell (_IO_FILE *fp) +{ + _IO_off64_t result = 0; + bool use_cached_offset = false; + + /* No point looking at unflushed data if we haven't allocated buffers + yet. */ + if (fp->_IO_buf_base != NULL) + { + bool was_writing = (fp->_IO_write_ptr > fp->_IO_write_base + || _IO_in_put_mode (fp)); + + /* Adjust for unflushed data. */ + if (!was_writing) + 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); + return EOF; + } + + return result; +} + _IO_off64_t _IO_new_file_seekoff (fp, offset, dir, mode) @@ -940,6 +1027,13 @@ _IO_new_file_seekoff (fp, offset, dir, mode) _IO_off64_t result; _IO_off64_t delta, new_offset; long count; + + /* Short-circuit into a separate function. We don't want to mix any + functionality and we don't want to touch anything inside the FILE + object. */ + if (mode == 0) + return do_ftell (fp); + /* POSIX.1 8.2.3.7 says that after a call the fflush() the file offset of the underlying file must be exact. */ int must_be_exact = (fp->_IO_read_base == fp->_IO_read_end @@ -948,9 +1042,6 @@ _IO_new_file_seekoff (fp, offset, dir, mode) bool was_writing = (fp->_IO_write_ptr > fp->_IO_write_base || _IO_in_put_mode (fp)); - if (mode == 0) - dir = _IO_seek_cur, offset = 0; /* Don't move any pointers. */ - /* Flush unwritten characters. (This may do an unneeded write if we seek within the buffer. But to be able to switch to reading, we would need to set @@ -958,7 +1049,7 @@ _IO_new_file_seekoff (fp, offset, dir, mode) which assumes file_ptr() is eGptr. Anyway, since we probably end up flushing when we close(), it doesn't make much difference.) FIXME: simulate mem-mapped files. */ - else if (was_writing && _IO_switch_to_get_mode (fp)) + if (was_writing && _IO_switch_to_get_mode (fp)) return EOF; if (fp->_IO_buf_base == NULL) @@ -978,30 +1069,10 @@ _IO_new_file_seekoff (fp, offset, dir, mode) { case _IO_seek_cur: /* Adjust for read-ahead (bytes is buffer). */ - if (mode != 0 || !was_writing) - offset -= fp->_IO_read_end - fp->_IO_read_ptr; - else - { - /* _IO_read_end coincides with fp._offset, so the actual file position - is fp._offset - (_IO_read_end - new_write_ptr). This is fine - even if fp._offset is not set, since fp->_IO_read_end is then at - _IO_buf_base and this adjustment is for unbuffered output. */ - offset -= fp->_IO_read_end - fp->_IO_write_ptr; - } + offset -= fp->_IO_read_end - fp->_IO_read_ptr; if (fp->_offset == _IO_pos_BAD) - { - if (mode != 0) - goto dumb; - else - { - result = _IO_SYSSEEK (fp, 0, dir); - if (result == EOF) - return result; - - fp->_offset = result; - } - } + goto dumb; /* Make offset absolute, assuming current pointer is file_ptr(). */ offset += fp->_offset; if (offset < 0) @@ -1028,10 +1099,6 @@ _IO_new_file_seekoff (fp, offset, dir, mode) } /* At this point, dir==_IO_seek_set. */ - /* If we are only interested in the current position we've found it now. */ - if (mode == 0) - return offset; - /* If destination is within current buffer, optimize: */ if (fp->_offset != _IO_pos_BAD && fp->_IO_read_base != NULL && !_IO_in_backup (fp)) diff --git a/libio/iofdopen.c b/libio/iofdopen.c index 066ff19..3f266f7 100644 --- a/libio/iofdopen.c +++ b/libio/iofdopen.c @@ -141,9 +141,6 @@ _IO_new_fdopen (fd, mode) #ifdef _IO_MTSAFE_IO new_f->fp.file._lock = &new_f->lock; #endif - /* Set up initially to use the `maybe_mmap' jump tables rather than using - __fopen_maybe_mmap to do it, because we need them in place before we - call _IO_file_attach or else it will allocate a buffer immediately. */ _IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, #ifdef _G_HAVE_MMAP (use_mmap && (read_write & _IO_NO_WRITES)) @@ -159,13 +156,12 @@ _IO_new_fdopen (fd, mode) #if !_IO_UNIFIED_JUMPTABLES new_f->fp.vtable = NULL; #endif - if (_IO_file_attach ((_IO_FILE *) &new_f->fp, fd) == NULL) - { - _IO_setb (&new_f->fp.file, NULL, NULL, 0); - _IO_un_link (&new_f->fp); - free (new_f); - return NULL; - } + /* We only need to record the fd because _IO_file_init will have unset the + offset. It is important to unset the cached offset because the real + offset in the file could change between now and when the handle is + activated and we would then mislead ftell into believing that we have a + valid offset. */ + new_f->fp.file._fileno = fd; new_f->fp.file._flags &= ~_IO_DELETE_DONT_CLOSE; _IO_mask_flags (&new_f->fp.file, read_write, diff --git a/libio/iofwide.c b/libio/iofwide.c index 5cff632..64187e4 100644 --- a/libio/iofwide.c +++ b/libio/iofwide.c @@ -199,12 +199,6 @@ _IO_fwide (fp, mode) /* From now on use the wide character callback functions. */ ((struct _IO_FILE_plus *) fp)->vtable = fp->_wide_data->_wide_vtable; - - /* One last twist: we get the current stream position. The wide - char streams have much more problems with not knowing the - current position and so we should disable the optimization - which allows the functions without knowing the position. */ - fp->_offset = _IO_SYSSEEK (fp, 0, _IO_seek_cur); } /* Set the mode now. */ diff --git a/libio/libioP.h b/libio/libioP.h index 4ca723c..8a7b85b 100644 --- a/libio/libioP.h +++ b/libio/libioP.h @@ -397,6 +397,7 @@ extern void _IO_wdoallocbuf (_IO_FILE *) __THROW; libc_hidden_proto (_IO_wdoallocbuf) extern void _IO_unsave_wmarkers (_IO_FILE *) __THROW; extern unsigned _IO_adjust_wcolumn (unsigned, const wchar_t *, int) __THROW; +extern _IO_off64_t get_file_offset (_IO_FILE *fp); /* Marker-related function. */ diff --git a/libio/tst-ftell-active-handler.c b/libio/tst-ftell-active-handler.c new file mode 100644 index 0000000..175e904 --- /dev/null +++ b/libio/tst-ftell-active-handler.c @@ -0,0 +1,384 @@ +/* Verify that ftell returns the correct value at various points before and + after the handler on which it is called becomes active. + 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 + . */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static int do_test (void); + +#define TEST_FUNCTION do_test () +#include "../test-skeleton.c" + +#define get_handles_fdopen(filename, fd, fp, fd_mode, mode) \ +({ \ + int ret = 0; \ + (fd) = open ((filename), (fd_mode), 0); \ + if ((fd) == -1) \ + { \ + printf ("open failed: %m\n"); \ + ret = 1; \ + } \ + else \ + { \ + (fp) = fdopen ((fd), (mode)); \ + if ((fp) == NULL) \ + { \ + printf ("fdopen failed: %m\n"); \ + close (fd); \ + ret = 1; \ + } \ + } \ + ret; \ +}) + +#define get_handles_fopen(filename, fd, fp, mode) \ +({ \ + int ret = 0; \ + (fp) = fopen ((filename), (mode)); \ + if ((fp) == NULL) \ + { \ + printf ("fopen failed: %m\n"); \ + ret = 1; \ + } \ + else \ + { \ + (fd) = fileno (fp); \ + if ((fd) == -1) \ + { \ + printf ("fileno failed: %m\n"); \ + ret = 1; \ + } \ + } \ + ret; \ +}) + +/* 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. */ +static const void *data; +static const char *char_data = "abcdef"; +static const wchar_t *wide_data = L"abcdef"; +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; + +/* Test that the value of ftell is not cached when the stream handle is not + active. */ +static int +do_ftell_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[] = { + /* In w, w+ and r+ modes, the file position should be at the + beginning of the file. After the write, the offset should be + updated to data_len. */ + {"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 + current end of file. After the write, the offset has data_len + added to the old value. */ + {"a", O_WRONLY, data_len, 2 * data_len}, + {"a+", O_RDWR, 2 * data_len, 3 * data_len}, + }; + for (int j = 0; j < 2; j++) + { + for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++) + { + FILE *fp; + int fd; + printf ("\tftell: %s (file, \"%s\"): ", j == 0 ? "fdopen" : "fopen", + test_modes[i].mode); + + if (j == 0) + ret = get_handles_fdopen (filename, fd, fp, test_modes[i].fd_mode, + test_modes[i].mode); + else + ret = get_handles_fopen (filename, fd, fp, test_modes[i].mode); + + if (ret != 0) + return ret; + + long off = ftell (fp); + if (off != test_modes[i].old_off) + { + printf ("Incorrect old offset. Expected %zu but got %ld, ", + test_modes[i].old_off, off); + ret |= 1; + } + else + printf ("old offset = %ld, ", off); + + /* The effect of this write on the offset should be seen in the ftell + call that follows it. */ + int ret = write (fd, data, data_len); + off = ftell (fp); + + if (off != test_modes[i].new_off) + { + printf ("Incorrect new offset. Expected %zu but got %ld\n", + test_modes[i].old_off, off); + ret |= 1; + } + else + printf ("new offset = %ld\n", off); + + fclose (fp); + } + } + + return ret; +} + +/* This test opens the file for writing, moves the file offset of the + underlying file, writes out data and then checks if ftell trips on it. */ +static int +do_write_test (const char *filename) +{ + FILE *fp = NULL; + int fd; + int ret = 0; + struct test + { + const char *mode; + int fd_mode; + } test_modes[] = { + {"w", O_WRONLY}, + {"w+", O_RDWR}, + {"r+", O_RDWR} + }; + + for (int j = 0; j < 2; j++) + { + for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++) + { + printf ("\twrite: %s (file, \"%s\"): ", j == 0 ? "fopen" : "fdopen", + test_modes[i].mode); + + if (j == 0) + ret = get_handles_fopen (filename, fd, fp, test_modes[i].mode); + else + ret = get_handles_fdopen (filename, fd, fp, test_modes[i].fd_mode, + test_modes[i].mode); + + if (ret != 0) + return ret; + + /* Move offset to just before the end of the file. */ + off_t ret = lseek (fd, file_len - 1, SEEK_SET); + if (ret == -1) + { + printf ("lseek failed: %m\n"); + ret |= 1; + } + + /* Write some data. */ + size_t written = fputs_func (data, fp); + + if (written == EOF) + { + printf ("fputs[1] failed to write data\n"); + ret |= 1; + } + + /* Verify that the offset points to the end of the file. The length + of the file would be the original length + the length of data + written to it - the amount by which we moved the offset using + lseek. */ + long offset = ftell (fp); + file_len = file_len - 1 + data_len; + + if (offset != file_len) + { + printf ("Incorrect offset. Expected %zu, but got %ld\n", + file_len, offset); + + ret |= 1; + } + + printf ("offset = %ld\n", offset); + fclose (fp); + } + } + + return ret; +} + +/* This test opens a file in append mode, writes some data, and then verifies + that ftell does not trip over it. */ +static int +do_append_test (const char *filename) +{ + FILE *fp = NULL; + int ret = 0; + int fd; + + struct test + { + const char *mode; + int fd_mode; + } test_modes[] = { + {"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++) + { + printf ("\tappend: %s (file, \"%s\"): ", j == 0 ? "fopen" : "fdopen", + test_modes[i].mode); + + if (j == 0) + ret = get_handles_fopen (filename, fd, fp, test_modes[i].mode); + else + ret = get_handles_fdopen (filename, fd, fp, test_modes[i].fd_mode, + test_modes[i].mode); + + if (ret != 0) + return ret; + + /* Write some data. */ + size_t written = fputs_func (data, fp); + + if (written == EOF) + { + printf ("fputs[1] failed to write all data\n"); + ret |= 1; + } + + /* Verify that the offset points to the end of the file. The file + len is maintained by adding data_len each time to reflect the data + written to it. */ + long offset = ftell (fp); + file_len += data_len; + + if (offset != file_len) + { + printf ("Incorrect offset. Expected %zu, but got %ld\n", + file_len, offset); + + ret |= 1; + } + + printf ("offset = %ld\n", offset); + fclose (fp); + } + } + + return ret; +} + +static int +do_one_test (const char *filename) +{ + int ret = 0; + + ret |= do_ftell_test (filename); + ret |= do_write_test (filename); + ret |= do_append_test (filename); + + return ret; +} + +/* Run a set of tests for ftell for regular files and wide mode files. */ +static int +do_test (void) +{ + int ret = 0; + FILE *fp = NULL; + char *filename; + size_t written; + int fd = create_temp_file ("tst-active-handler-tmp.", &filename); + + if (fd == -1) + { + printf ("create_temp_file: %m\n"); + return 1; + } + + fp = fdopen (fd, "w"); + if (fp == NULL) + { + printf ("fdopen[0]: %m\n"); + close (fd); + return 1; + } + + data = char_data; + data_len = strlen (char_data); + file_len = strlen (char_data); + written = fputs (data, fp); + + if (written == EOF) + { + printf ("fputs[1] failed to write data\n"); + ret = 1; + } + + fclose (fp); + if (ret) + return ret; + + /* Tests for regular files. */ + puts ("Regular mode:"); + fputs_func = (fputs_func_t) fputs; + data = char_data; + data_len = strlen (char_data); + ret |= do_one_test (filename); + + /* Truncate the file before repeating the tests in wide mode. */ + fp = fopen (filename, "w"); + if (fp == NULL) + { + printf ("fopen failed %m\n"); + return 1; + } + fclose (fp); + + /* 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; + data = wide_data; + data_len = wcslen (wide_data); + ret |= do_one_test (filename); + + return ret; +} diff --git a/libio/wfileops.c b/libio/wfileops.c index 9cebe77..8b2e108 100644 --- a/libio/wfileops.c +++ b/libio/wfileops.c @@ -596,29 +596,25 @@ done: return 0; } -_IO_off64_t -_IO_wfile_seekoff (fp, offset, dir, mode) - _IO_FILE *fp; - _IO_off64_t offset; - int dir; - int mode; +/* 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. */ +static _IO_off64_t +do_ftell_wide (_IO_FILE *fp) { - _IO_off64_t result; - _IO_off64_t delta, new_offset; - long int count; - /* POSIX.1 8.2.3.7 says that after a call the fflush() the file - offset of the underlying file must be exact. */ - int must_be_exact = ((fp->_wide_data->_IO_read_base - == fp->_wide_data->_IO_read_end) - && (fp->_wide_data->_IO_write_base - == fp->_wide_data->_IO_write_ptr)); + _IO_off64_t result, offset = 0; + bool use_cached_offset = false; - bool was_writing = ((fp->_wide_data->_IO_write_ptr - > fp->_wide_data->_IO_write_base) - || _IO_in_put_mode (fp)); - - if (mode == 0) + /* No point looking for offsets in the buffer if it hasn't even been + allocated. */ + if (fp->_wide_data->_IO_buf_base != NULL) { + 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)); + /* 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 @@ -633,14 +629,142 @@ _IO_wfile_seekoff (fp, offset, dir, mode) return -1; } - /* There is no more data in the backup buffer. We can - switch back. */ - _IO_switch_to_main_wget_area (fp); + /* Nothing in the backup store, so note the backed up pointers + without changing the state. */ + wide_read_base = fp->_wide_data->_IO_save_base; + wide_read_ptr = wide_read_base; + wide_read_end = fp->_wide_data->_IO_save_end; + } + else + { + wide_read_base = fp->_wide_data->_IO_read_base; + wide_read_ptr = fp->_wide_data->_IO_read_ptr; + wide_read_end = fp->_wide_data->_IO_read_end; + } + + struct _IO_codecvt *cv = fp->_codecvt; + int clen = (*cv->__codecvt_do_encoding) (cv); + + if (!was_writing) + { + if (clen > 0) + { + offset -= (wide_read_end - wide_read_ptr) * clen; + offset -= fp->_IO_read_end - fp->_IO_read_ptr; + } + else + { + int nread; + + size_t delta = wide_read_ptr - wide_read_base; + __mbstate_t state = fp->_wide_data->_IO_last_state; + nread = (*cv->__codecvt_do_length) (cv, &state, + fp->_IO_read_base, + fp->_IO_read_end, delta); + offset -= fp->_IO_read_end - fp->_IO_read_base - nread; + } + } + else + { + if (clen > 0) + offset += (fp->_wide_data->_IO_write_ptr + - fp->_wide_data->_IO_write_base) * clen; + else + { + size_t delta = (fp->_wide_data->_IO_write_ptr + - fp->_wide_data->_IO_write_base); + + /* Allocate enough space for the conversion. */ + size_t outsize = delta * sizeof (wchar_t); + char *out = malloc (outsize); + char *outstop = out; + const wchar_t *in = fp->_wide_data->_IO_write_base; + + enum __codecvt_result status; + + __mbstate_t state = fp->_wide_data->_IO_last_state; + status = (*cv->__codecvt_do_out) (cv, &state, + in, in + delta, &in, + out, out + outsize, &outstop); + + /* We don't check for __codecvt_partial because it can be + returned on one of two conditions: either the output + buffer is full or the input sequence is incomplete. We + take care to allocate enough buffer and our input + sequences must be complete since they are accepted as + wchar_t; if not, then that is an error. */ + if (__glibc_unlikely (status != __codecvt_ok)) + return WEOF; + + 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; } - dir = _IO_seek_cur, offset = 0; /* Don't move any pointers. */ + /* 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) + result = fp->_offset; + else + result = get_file_offset (fp); + + if (result == EOF) + return result; + + result += offset; + + return result; +} + +_IO_off64_t +_IO_wfile_seekoff (fp, offset, dir, mode) + _IO_FILE *fp; + _IO_off64_t offset; + int dir; + int mode; +{ + _IO_off64_t result; + _IO_off64_t delta, new_offset; + long int count; + + /* Short-circuit into a separate function. We don't want to mix any + functionality and we don't want to touch anything inside the FILE + object. */ + if (mode == 0) + return do_ftell_wide (fp); + + /* POSIX.1 8.2.3.7 says that after a call the fflush() the file + offset of the underlying file must be exact. */ + int must_be_exact = ((fp->_wide_data->_IO_read_base + == fp->_wide_data->_IO_read_end) + && (fp->_wide_data->_IO_write_base + == fp->_wide_data->_IO_write_ptr)); + + bool was_writing = ((fp->_wide_data->_IO_write_ptr + > fp->_wide_data->_IO_write_base) + || _IO_in_put_mode (fp)); + /* Flush unwritten characters. (This may do an unneeded write if we seek within the buffer. But to be able to switch to reading, we would need to set @@ -648,7 +772,7 @@ _IO_wfile_seekoff (fp, offset, dir, mode) which assumes file_ptr() is eGptr. Anyway, since we probably end up flushing when we close(), it doesn't make much difference.) FIXME: simulate mem-mapped files. */ - else if (was_writing && _IO_switch_to_wget_mode (fp)) + if (was_writing && _IO_switch_to_wget_mode (fp)) return WEOF; if (fp->_wide_data->_IO_buf_base == NULL) @@ -693,7 +817,6 @@ _IO_wfile_seekoff (fp, offset, dir, mode) { int nread; - flushed: delta = (fp->_wide_data->_IO_read_ptr - fp->_wide_data->_IO_read_base); fp->_wide_data->_IO_state = fp->_wide_data->_IO_last_state; @@ -706,80 +829,9 @@ _IO_wfile_seekoff (fp, offset, dir, mode) offset -= fp->_IO_read_end - fp->_IO_read_base - nread; } } - else - { - char *new_write_ptr = fp->_IO_write_ptr; - - if (clen > 0) - offset += (fp->_wide_data->_IO_write_ptr - - fp->_wide_data->_IO_write_base) / clen; - else - { - enum __codecvt_result status = __codecvt_ok; - delta = (fp->_wide_data->_IO_write_ptr - - fp->_wide_data->_IO_write_base); - const wchar_t *write_base = fp->_wide_data->_IO_write_base; - - /* FIXME: This actually ends up in two iterations of conversion, - one here and the next when the buffer actually gets flushed. - It may be possible to optimize this in future so that - wdo_write identifies already converted content and does not - redo it. In any case, this is much better than having to - flush buffers for every ftell. */ - do - { - /* There is not enough space in the buffer to do the entire - conversion, so there is no point trying to avoid the - buffer flush. Just do it and go back to how it was with - the read mode. */ - if (status == __codecvt_partial - || (delta > 0 && new_write_ptr == fp->_IO_buf_end)) - { - if (_IO_switch_to_wget_mode (fp)) - return WEOF; - goto flushed; - } - - const wchar_t *new_wbase = fp->_wide_data->_IO_write_base; - fp->_wide_data->_IO_state = fp->_wide_data->_IO_last_state; - status = (*cv->__codecvt_do_out) (cv, - &fp->_wide_data->_IO_state, - write_base, - write_base + delta, - &new_wbase, - new_write_ptr, - fp->_IO_buf_end, - &new_write_ptr); - - delta -= new_wbase - write_base; - - /* If there was an error, then return WEOF. - TODO: set buffer state. */ - if (__builtin_expect (status == __codecvt_error, 0)) - return WEOF; - } - while (delta > 0); - } - - /* _IO_read_end coincides with fp._offset, so the actual file position - is fp._offset - (_IO_read_end - new_write_ptr). This is fine - even if fp._offset is not set, since fp->_IO_read_end is then at - _IO_buf_base and this adjustment is for unbuffered output. */ - offset -= fp->_IO_read_end - new_write_ptr; - } if (fp->_offset == _IO_pos_BAD) - { - if (mode != 0) - goto dumb; - else - { - result = _IO_SYSSEEK (fp, 0, dir); - if (result == EOF) - return result; - fp->_offset = result; - } - } + goto dumb; /* Make offset absolute, assuming current pointer is file_ptr(). */ offset += fp->_offset; @@ -802,10 +854,6 @@ _IO_wfile_seekoff (fp, offset, dir, mode) } /* At this point, dir==_IO_seek_set. */ - /* If we are only interested in the current position we've found it now. */ - if (mode == 0) - return offset; - /* If destination is within current buffer, optimize: */ if (fp->_offset != _IO_pos_BAD && fp->_IO_read_base != NULL && !_IO_in_backup (fp))