2e9afc
diff --git a/libio/Makefile b/libio/Makefile
2e9afc
index 22dbcae..488ee51 100644
2e9afc
--- a/libio/Makefile
2e9afc
+++ b/libio/Makefile
2e9afc
@@ -60,7 +60,7 @@ tests = tst_swprintf tst_wprintf tst_swscanf tst_wscanf tst_getwc tst_putwc   \
2e9afc
 	tst-wmemstream1 tst-wmemstream2 \
2e9afc
 	bug-memstream1 bug-wmemstream1 \
2e9afc
 	tst-setvbuf1 tst-popen1 tst-fgetwc bug-wsetpos tst-fseek \
2e9afc
-	tst-fwrite-error
2e9afc
+	tst-fwrite-error tst-ftell-active-handler
2e9afc
 ifeq (yes,$(build-shared))
2e9afc
 # Add test-fopenloc only if shared library is enabled since it depends on
2e9afc
 # shared localedata objects.
2e9afc
diff --git a/libio/fileops.c b/libio/fileops.c
2e9afc
index a3499be..2e7bc8d 100644
2e9afc
--- a/libio/fileops.c
2e9afc
+++ b/libio/fileops.c
2e9afc
@@ -929,6 +929,93 @@ _IO_file_sync_mmap (_IO_FILE *fp)
2e9afc
   return 0;
2e9afc
 }
2e9afc
 
2e9afc
+/* Get the current file offset using a system call.  This is the safest method
2e9afc
+   to get the current file offset, since we are sure that we get the current
2e9afc
+   state of the file.  Before the stream handle is activated (by using fread,
2e9afc
+   fwrite, etc.), an application may alter the state of the file descriptor
2e9afc
+   underlying it by calling read/write/lseek on it.  Using a cached offset at
2e9afc
+   this point will result in returning the incorrect value.  Same is the case
2e9afc
+   when one switches from reading in a+ mode to writing, where the buffer has
2e9afc
+   not been flushed - the cached offset would reflect the reading position
2e9afc
+   while the actual write position would be at the end of the file.
2e9afc
+
2e9afc
+   do_ftell and do_ftell_wide may resort to using the cached offset in some
2e9afc
+   special cases instead of calling get_file_offset, but those cases should be
2e9afc
+   thoroughly described.  */
2e9afc
+_IO_off64_t
2e9afc
+get_file_offset (_IO_FILE *fp)
2e9afc
+{
2e9afc
+  if ((fp->_flags & _IO_IS_APPENDING) == _IO_IS_APPENDING)
2e9afc
+    {
2e9afc
+      struct stat64 st;
2e9afc
+      bool ret = (_IO_SYSSTAT (fp, &st) == 0 && S_ISREG (st.st_mode));
2e9afc
+      if (ret)
2e9afc
+	return st.st_size;
2e9afc
+      else
2e9afc
+	return EOF;
2e9afc
+    }
2e9afc
+  else
2e9afc
+    return _IO_SYSSEEK (fp, 0, _IO_seek_cur);
2e9afc
+}
2e9afc
+
2e9afc
+
2e9afc
+/* ftell{,o} implementation.  Don't modify any state of the file pointer while
2e9afc
+   we try to get the current state of the stream.  */
2e9afc
+static _IO_off64_t
2e9afc
+do_ftell (_IO_FILE *fp)
2e9afc
+{
2e9afc
+  _IO_off64_t result = 0;
2e9afc
+  bool use_cached_offset = false;
2e9afc
+
2e9afc
+  /* No point looking at unflushed data if we haven't allocated buffers
2e9afc
+     yet.  */
2e9afc
+  if (fp->_IO_buf_base != NULL)
2e9afc
+    {
2e9afc
+      bool was_writing = (fp->_IO_write_ptr > fp->_IO_write_base
2e9afc
+			  || _IO_in_put_mode (fp));
2e9afc
+
2e9afc
+      /* Adjust for unflushed data.  */
2e9afc
+      if (!was_writing)
2e9afc
+	result -= fp->_IO_read_end - fp->_IO_read_ptr;
2e9afc
+      else
2e9afc
+	result += fp->_IO_write_ptr - fp->_IO_read_end;
2e9afc
+
2e9afc
+      /* It is safe to use the cached offset when available if there is
2e9afc
+	 unbuffered data (indicating that the file handle is active) and the
2e9afc
+	 handle is not for a file open in a+ mode.  The latter condition is
2e9afc
+	 because there could be a scenario where there is a switch from read
2e9afc
+	 mode to write mode using an fseek to an arbitrary position.  In this
2e9afc
+	 case, there would be unbuffered data due to be appended to the end of
2e9afc
+	 the file, but the offset may not necessarily be the end of the
2e9afc
+	 file.  It is fine to use the cached offset when the a+ stream is in
2e9afc
+	 read mode though, since the offset is maintained correctly in that
2e9afc
+	 case.  Note that this is not a comprehensive set of cases when the
2e9afc
+	 offset is reliable.  The offset may be reliable even in some cases
2e9afc
+	 where there is no unflushed input and the handle is active, but it's
2e9afc
+	 just that we don't have a way to identify that condition reliably.  */
2e9afc
+      use_cached_offset = (result != 0 && fp->_offset != _IO_pos_BAD
2e9afc
+			   && ((fp->_flags & (_IO_IS_APPENDING | _IO_NO_READS))
2e9afc
+			       == (_IO_IS_APPENDING | _IO_NO_READS)
2e9afc
+			       && was_writing));
2e9afc
+    }
2e9afc
+
2e9afc
+  if (use_cached_offset)
2e9afc
+    result += fp->_offset;
2e9afc
+  else
2e9afc
+    result += get_file_offset (fp);
2e9afc
+
2e9afc
+  if (result == EOF)
2e9afc
+    return result;
2e9afc
+
2e9afc
+  if (result < 0)
2e9afc
+    {
2e9afc
+      __set_errno (EINVAL);
2e9afc
+      return EOF;
2e9afc
+    }
2e9afc
+
2e9afc
+  return result;
2e9afc
+}
2e9afc
+
2e9afc
 
2e9afc
 _IO_off64_t
2e9afc
 _IO_new_file_seekoff (fp, offset, dir, mode)
2e9afc
@@ -940,6 +1027,13 @@ _IO_new_file_seekoff (fp, offset, dir, mode)
2e9afc
   _IO_off64_t result;
2e9afc
   _IO_off64_t delta, new_offset;
2e9afc
   long count;
2e9afc
+
2e9afc
+  /* Short-circuit into a separate function.  We don't want to mix any
2e9afc
+     functionality and we don't want to touch anything inside the FILE
2e9afc
+     object. */
2e9afc
+  if (mode == 0)
2e9afc
+    return do_ftell (fp);
2e9afc
+
2e9afc
   /* POSIX.1 8.2.3.7 says that after a call the fflush() the file
2e9afc
      offset of the underlying file must be exact.  */
2e9afc
   int must_be_exact = (fp->_IO_read_base == fp->_IO_read_end
2e9afc
@@ -948,9 +1042,6 @@ _IO_new_file_seekoff (fp, offset, dir, mode)
2e9afc
   bool was_writing = (fp->_IO_write_ptr > fp->_IO_write_base
2e9afc
 		      || _IO_in_put_mode (fp));
2e9afc
 
2e9afc
-  if (mode == 0)
2e9afc
-    dir = _IO_seek_cur, offset = 0; /* Don't move any pointers. */
2e9afc
-
2e9afc
   /* Flush unwritten characters.
2e9afc
      (This may do an unneeded write if we seek within the buffer.
2e9afc
      But to be able to switch to reading, we would need to set
2e9afc
@@ -958,7 +1049,7 @@ _IO_new_file_seekoff (fp, offset, dir, mode)
2e9afc
      which assumes file_ptr() is eGptr.  Anyway, since we probably
2e9afc
      end up flushing when we close(), it doesn't make much difference.)
2e9afc
      FIXME: simulate mem-mapped files. */
2e9afc
-  else if (was_writing && _IO_switch_to_get_mode (fp))
2e9afc
+  if (was_writing && _IO_switch_to_get_mode (fp))
2e9afc
     return EOF;
2e9afc
 
2e9afc
   if (fp->_IO_buf_base == NULL)
2e9afc
@@ -978,30 +1069,10 @@ _IO_new_file_seekoff (fp, offset, dir, mode)
2e9afc
     {
2e9afc
     case _IO_seek_cur:
2e9afc
       /* Adjust for read-ahead (bytes is buffer). */
2e9afc
-      if (mode != 0 || !was_writing)
2e9afc
-	offset -= fp->_IO_read_end - fp->_IO_read_ptr;
2e9afc
-      else
2e9afc
-	{
2e9afc
-	  /* _IO_read_end coincides with fp._offset, so the actual file position
2e9afc
-	     is fp._offset - (_IO_read_end - new_write_ptr).  This is fine
2e9afc
-	     even if fp._offset is not set, since fp->_IO_read_end is then at
2e9afc
-	     _IO_buf_base and this adjustment is for unbuffered output.  */
2e9afc
-	  offset -= fp->_IO_read_end - fp->_IO_write_ptr;
2e9afc
-	}
2e9afc
+      offset -= fp->_IO_read_end - fp->_IO_read_ptr;
2e9afc
 
2e9afc
       if (fp->_offset == _IO_pos_BAD)
2e9afc
-	{
2e9afc
-	  if (mode != 0)
2e9afc
-	    goto dumb;
2e9afc
-	  else
2e9afc
-	    {
2e9afc
-	      result = _IO_SYSSEEK (fp, 0, dir);
2e9afc
-	      if (result == EOF)
2e9afc
-		return result;
2e9afc
-
2e9afc
-	      fp->_offset = result;
2e9afc
-	    }
2e9afc
-	}
2e9afc
+	goto dumb;
2e9afc
       /* Make offset absolute, assuming current pointer is file_ptr(). */
2e9afc
       offset += fp->_offset;
2e9afc
       if (offset < 0)
2e9afc
@@ -1028,10 +1099,6 @@ _IO_new_file_seekoff (fp, offset, dir, mode)
2e9afc
     }
2e9afc
   /* At this point, dir==_IO_seek_set. */
2e9afc
 
2e9afc
-  /* If we are only interested in the current position we've found it now.  */
2e9afc
-  if (mode == 0)
2e9afc
-    return offset;
2e9afc
-
2e9afc
   /* If destination is within current buffer, optimize: */
2e9afc
   if (fp->_offset != _IO_pos_BAD && fp->_IO_read_base != NULL
2e9afc
       && !_IO_in_backup (fp))
2e9afc
diff --git a/libio/iofdopen.c b/libio/iofdopen.c
2e9afc
index 066ff19..3f266f7 100644
2e9afc
--- a/libio/iofdopen.c
2e9afc
+++ b/libio/iofdopen.c
2e9afc
@@ -141,9 +141,6 @@ _IO_new_fdopen (fd, mode)
2e9afc
 #ifdef _IO_MTSAFE_IO
2e9afc
   new_f->fp.file._lock = &new_f->lock;
2e9afc
 #endif
2e9afc
-  /* Set up initially to use the `maybe_mmap' jump tables rather than using
2e9afc
-     __fopen_maybe_mmap to do it, because we need them in place before we
2e9afc
-     call _IO_file_attach or else it will allocate a buffer immediately.  */
2e9afc
   _IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd,
2e9afc
 #ifdef _G_HAVE_MMAP
2e9afc
 	       (use_mmap && (read_write & _IO_NO_WRITES))
2e9afc
@@ -159,13 +156,12 @@ _IO_new_fdopen (fd, mode)
2e9afc
 #if  !_IO_UNIFIED_JUMPTABLES
2e9afc
   new_f->fp.vtable = NULL;
2e9afc
 #endif
2e9afc
-  if (_IO_file_attach ((_IO_FILE *) &new_f->fp, fd) == NULL)
2e9afc
-    {
2e9afc
-      _IO_setb (&new_f->fp.file, NULL, NULL, 0);
2e9afc
-      _IO_un_link (&new_f->fp);
2e9afc
-      free (new_f);
2e9afc
-      return NULL;
2e9afc
-    }
2e9afc
+  /* We only need to record the fd because _IO_file_init will have unset the
2e9afc
+     offset.  It is important to unset the cached offset because the real
2e9afc
+     offset in the file could change between now and when the handle is
2e9afc
+     activated and we would then mislead ftell into believing that we have a
2e9afc
+     valid offset.  */
2e9afc
+  new_f->fp.file._fileno = fd;
2e9afc
   new_f->fp.file._flags &= ~_IO_DELETE_DONT_CLOSE;
2e9afc
 
2e9afc
   _IO_mask_flags (&new_f->fp.file, read_write,
2e9afc
diff --git a/libio/iofwide.c b/libio/iofwide.c
2e9afc
index 5cff632..64187e4 100644
2e9afc
--- a/libio/iofwide.c
2e9afc
+++ b/libio/iofwide.c
2e9afc
@@ -199,12 +199,6 @@ _IO_fwide (fp, mode)
2e9afc
 
2e9afc
       /* From now on use the wide character callback functions.  */
2e9afc
       ((struct _IO_FILE_plus *) fp)->vtable = fp->_wide_data->_wide_vtable;
2e9afc
-
2e9afc
-      /* One last twist: we get the current stream position.  The wide
2e9afc
-	 char streams have much more problems with not knowing the
2e9afc
-	 current position and so we should disable the optimization
2e9afc
-	 which allows the functions without knowing the position.  */
2e9afc
-      fp->_offset = _IO_SYSSEEK (fp, 0, _IO_seek_cur);
2e9afc
     }
2e9afc
 
2e9afc
   /* Set the mode now.  */
2e9afc
diff --git a/libio/libioP.h b/libio/libioP.h
2e9afc
index 4ca723c..8a7b85b 100644
2e9afc
--- a/libio/libioP.h
2e9afc
+++ b/libio/libioP.h
2e9afc
@@ -397,6 +397,7 @@ extern void _IO_wdoallocbuf (_IO_FILE *) __THROW;
2e9afc
 libc_hidden_proto (_IO_wdoallocbuf)
2e9afc
 extern void _IO_unsave_wmarkers (_IO_FILE *) __THROW;
2e9afc
 extern unsigned _IO_adjust_wcolumn (unsigned, const wchar_t *, int) __THROW;
2e9afc
+extern _IO_off64_t get_file_offset (_IO_FILE *fp);
2e9afc
 
2e9afc
 /* Marker-related function. */
2e9afc
 
2e9afc
diff --git a/libio/tst-ftell-active-handler.c b/libio/tst-ftell-active-handler.c
2e9afc
new file mode 100644
2e9afc
index 0000000..175e904
2e9afc
--- /dev/null
2e9afc
+++ b/libio/tst-ftell-active-handler.c
2e9afc
@@ -0,0 +1,384 @@
2e9afc
+/* Verify that ftell returns the correct value at various points before and
2e9afc
+   after the handler on which it is called becomes active.
2e9afc
+   Copyright (C) 2014 Free Software Foundation, Inc.
2e9afc
+   This file is part of the GNU C Library.
2e9afc
+
2e9afc
+   The GNU C Library is free software; you can redistribute it and/or
2e9afc
+   modify it under the terms of the GNU Lesser General Public
2e9afc
+   License as published by the Free Software Foundation; either
2e9afc
+   version 2.1 of the License, or (at your option) any later version.
2e9afc
+
2e9afc
+   The GNU C Library is distributed in the hope that it will be useful,
2e9afc
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
2e9afc
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
2e9afc
+   Lesser General Public License for more details.
2e9afc
+
2e9afc
+   You should have received a copy of the GNU Lesser General Public
2e9afc
+   License along with the GNU C Library; if not, see
2e9afc
+   <http://www.gnu.org/licenses/>.  */
2e9afc
+
2e9afc
+#include <stdio.h>
2e9afc
+#include <stdlib.h>
2e9afc
+#include <string.h>
2e9afc
+#include <errno.h>
2e9afc
+#include <unistd.h>
2e9afc
+#include <fcntl.h>
2e9afc
+#include <locale.h>
2e9afc
+#include <wchar.h>
2e9afc
+
2e9afc
+static int do_test (void);
2e9afc
+
2e9afc
+#define TEST_FUNCTION do_test ()
2e9afc
+#include "../test-skeleton.c"
2e9afc
+
2e9afc
+#define get_handles_fdopen(filename, fd, fp, fd_mode, mode) \
2e9afc
+({									      \
2e9afc
+  int ret = 0;								      \
2e9afc
+  (fd) = open ((filename), (fd_mode), 0);				      \
2e9afc
+  if ((fd) == -1)							      \
2e9afc
+    {									      \
2e9afc
+      printf ("open failed: %m\n");					      \
2e9afc
+      ret = 1;								      \
2e9afc
+    }									      \
2e9afc
+  else									      \
2e9afc
+    {									      \
2e9afc
+      (fp) = fdopen ((fd), (mode));					      \
2e9afc
+      if ((fp) == NULL)							      \
2e9afc
+        {								      \
2e9afc
+          printf ("fdopen failed: %m\n");				      \
2e9afc
+          close (fd);							      \
2e9afc
+          ret = 1;							      \
2e9afc
+        }								      \
2e9afc
+    }									      \
2e9afc
+  ret;									      \
2e9afc
+})
2e9afc
+
2e9afc
+#define get_handles_fopen(filename, fd, fp, mode) \
2e9afc
+({									      \
2e9afc
+  int ret = 0;								      \
2e9afc
+  (fp) = fopen ((filename), (mode));					      \
2e9afc
+  if ((fp) == NULL)							      \
2e9afc
+    {									      \
2e9afc
+      printf ("fopen failed: %m\n");					      \
2e9afc
+      ret = 1;								      \
2e9afc
+    }									      \
2e9afc
+  else									      \
2e9afc
+    {									      \
2e9afc
+      (fd) = fileno (fp);						      \
2e9afc
+      if ((fd) == -1)							      \
2e9afc
+        {								      \
2e9afc
+	  printf ("fileno failed: %m\n");				      \
2e9afc
+	  ret = 1;							      \
2e9afc
+	}								      \
2e9afc
+    }									      \
2e9afc
+  ret;									      \
2e9afc
+})
2e9afc
+
2e9afc
+/* data points to either char_data or wide_data, depending on whether we're
2e9afc
+   testing regular file mode or wide mode respectively.  Similarly,
2e9afc
+   fputs_func points to either fputs or fputws.  data_len keeps track of the
2e9afc
+   length of the current data and file_len maintains the current file
2e9afc
+   length.  */
2e9afc
+static const void *data;
2e9afc
+static const char *char_data = "abcdef";
2e9afc
+static const wchar_t *wide_data = L"abcdef";
2e9afc
+static size_t data_len;
2e9afc
+static size_t file_len;
2e9afc
+
2e9afc
+typedef int (*fputs_func_t) (const void *data, FILE *fp);
2e9afc
+fputs_func_t fputs_func;
2e9afc
+
2e9afc
+/* Test that the value of ftell is not cached when the stream handle is not
2e9afc
+   active.  */
2e9afc
+static int
2e9afc
+do_ftell_test (const char *filename)
2e9afc
+{
2e9afc
+  int ret = 0;
2e9afc
+  struct test
2e9afc
+    {
2e9afc
+      const char *mode;
2e9afc
+      int fd_mode;
2e9afc
+      size_t old_off;
2e9afc
+      size_t new_off;
2e9afc
+    } test_modes[] = {
2e9afc
+	  /* In w, w+ and r+ modes, the file position should be at the
2e9afc
+	     beginning of the file.  After the write, the offset should be
2e9afc
+	     updated to data_len.  */
2e9afc
+	  {"w", O_WRONLY, 0, data_len},
2e9afc
+	  {"w+", O_RDWR, 0, data_len},
2e9afc
+	  {"r+", O_RDWR, 0, data_len},
2e9afc
+	  /* For 'a' and 'a+' modes, the initial file position should be the
2e9afc
+	     current end of file. After the write, the offset has data_len
2e9afc
+	     added to the old value.  */
2e9afc
+	  {"a", O_WRONLY, data_len, 2 * data_len},
2e9afc
+	  {"a+", O_RDWR, 2 * data_len, 3 * data_len},
2e9afc
+    };
2e9afc
+  for (int j = 0; j < 2; j++)
2e9afc
+    {
2e9afc
+      for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++)
2e9afc
+	{
2e9afc
+	  FILE *fp;
2e9afc
+	  int fd;
2e9afc
+	  printf ("\tftell: %s (file, \"%s\"): ", j == 0 ? "fdopen" : "fopen",
2e9afc
+		  test_modes[i].mode);
2e9afc
+
2e9afc
+	  if (j == 0)
2e9afc
+	    ret = get_handles_fdopen (filename, fd, fp, test_modes[i].fd_mode,
2e9afc
+				      test_modes[i].mode);
2e9afc
+	  else
2e9afc
+	    ret = get_handles_fopen (filename, fd, fp, test_modes[i].mode);
2e9afc
+
2e9afc
+	  if (ret != 0)
2e9afc
+	    return ret;
2e9afc
+
2e9afc
+	  long off = ftell (fp);
2e9afc
+	  if (off != test_modes[i].old_off)
2e9afc
+	    {
2e9afc
+	      printf ("Incorrect old offset.  Expected %zu but got %ld, ",
2e9afc
+		      test_modes[i].old_off, off);
2e9afc
+	      ret |= 1;
2e9afc
+	    }
2e9afc
+	  else
2e9afc
+	    printf ("old offset = %ld, ", off);
2e9afc
+
2e9afc
+	  /* The effect of this write on the offset should be seen in the ftell
2e9afc
+	     call that follows it.  */
2e9afc
+	  int ret = write (fd, data, data_len);
2e9afc
+	  off = ftell (fp);
2e9afc
+
2e9afc
+	  if (off != test_modes[i].new_off)
2e9afc
+	    {
2e9afc
+	      printf ("Incorrect new offset.  Expected %zu but got %ld\n",
2e9afc
+		      test_modes[i].old_off, off);
2e9afc
+	      ret |= 1;
2e9afc
+	    }
2e9afc
+	  else
2e9afc
+	    printf ("new offset = %ld\n", off);
2e9afc
+
2e9afc
+	  fclose (fp);
2e9afc
+	}
2e9afc
+    }
2e9afc
+
2e9afc
+  return ret;
2e9afc
+}
2e9afc
+
2e9afc
+/* This test opens the file for writing, moves the file offset of the
2e9afc
+   underlying file, writes out data and then checks if ftell trips on it.  */
2e9afc
+static int
2e9afc
+do_write_test (const char *filename)
2e9afc
+{
2e9afc
+  FILE *fp = NULL;
2e9afc
+  int fd;
2e9afc
+  int ret = 0;
2e9afc
+  struct test
2e9afc
+    {
2e9afc
+      const char *mode;
2e9afc
+      int fd_mode;
2e9afc
+    } test_modes[] = {
2e9afc
+	  {"w", O_WRONLY},
2e9afc
+	  {"w+", O_RDWR},
2e9afc
+	  {"r+", O_RDWR}
2e9afc
+    };
2e9afc
+
2e9afc
+  for (int j = 0; j < 2; j++)
2e9afc
+    {
2e9afc
+      for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++)
2e9afc
+	{
2e9afc
+	  printf ("\twrite: %s (file, \"%s\"): ", j == 0 ? "fopen" : "fdopen",
2e9afc
+		  test_modes[i].mode);
2e9afc
+
2e9afc
+	  if (j == 0)
2e9afc
+	    ret = get_handles_fopen (filename, fd, fp, test_modes[i].mode);
2e9afc
+	  else
2e9afc
+	    ret = get_handles_fdopen (filename, fd, fp, test_modes[i].fd_mode,
2e9afc
+				      test_modes[i].mode);
2e9afc
+
2e9afc
+	  if (ret != 0)
2e9afc
+	    return ret;
2e9afc
+
2e9afc
+	  /* Move offset to just before the end of the file.  */
2e9afc
+	  off_t ret = lseek (fd, file_len - 1, SEEK_SET);
2e9afc
+	  if (ret == -1)
2e9afc
+	    {
2e9afc
+	      printf ("lseek failed: %m\n");
2e9afc
+	      ret |= 1;
2e9afc
+	    }
2e9afc
+
2e9afc
+	  /* Write some data.  */
2e9afc
+	  size_t written = fputs_func (data, fp);
2e9afc
+
2e9afc
+	  if (written == EOF)
2e9afc
+	    {
2e9afc
+	      printf ("fputs[1] failed to write data\n");
2e9afc
+	      ret |= 1;
2e9afc
+	    }
2e9afc
+
2e9afc
+	  /* Verify that the offset points to the end of the file.  The length
2e9afc
+	     of the file would be the original length + the length of data
2e9afc
+	     written to it - the amount by which we moved the offset using
2e9afc
+	     lseek.  */
2e9afc
+	  long offset = ftell (fp);
2e9afc
+	  file_len = file_len - 1 + data_len;
2e9afc
+
2e9afc
+	  if (offset != file_len)
2e9afc
+	    {
2e9afc
+	      printf ("Incorrect offset.  Expected %zu, but got %ld\n",
2e9afc
+		      file_len, offset);
2e9afc
+
2e9afc
+	      ret |= 1;
2e9afc
+	    }
2e9afc
+
2e9afc
+	  printf ("offset = %ld\n", offset);
2e9afc
+	  fclose (fp);
2e9afc
+        }
2e9afc
+    }
2e9afc
+
2e9afc
+  return ret;
2e9afc
+}
2e9afc
+
2e9afc
+/* This test opens a file in append mode, writes some data, and then verifies
2e9afc
+   that ftell does not trip over it.  */
2e9afc
+static int
2e9afc
+do_append_test (const char *filename)
2e9afc
+{
2e9afc
+  FILE *fp = NULL;
2e9afc
+  int ret = 0;
2e9afc
+  int fd;
2e9afc
+
2e9afc
+  struct test
2e9afc
+    {
2e9afc
+      const char *mode;
2e9afc
+      int fd_mode;
2e9afc
+    } test_modes[] = {
2e9afc
+	  {"a", O_WRONLY},
2e9afc
+	  {"a+", O_RDWR}
2e9afc
+    };
2e9afc
+
2e9afc
+  for (int j = 0; j < 2; j++)
2e9afc
+    {
2e9afc
+      for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++)
2e9afc
+	{
2e9afc
+	  printf ("\tappend: %s (file, \"%s\"): ", j == 0 ? "fopen" : "fdopen",
2e9afc
+		  test_modes[i].mode);
2e9afc
+
2e9afc
+	  if (j == 0)
2e9afc
+	    ret = get_handles_fopen (filename, fd, fp, test_modes[i].mode);
2e9afc
+	  else
2e9afc
+	    ret = get_handles_fdopen (filename, fd, fp, test_modes[i].fd_mode,
2e9afc
+				      test_modes[i].mode);
2e9afc
+
2e9afc
+	  if (ret != 0)
2e9afc
+	    return ret;
2e9afc
+
2e9afc
+	  /* Write some data.  */
2e9afc
+	  size_t written = fputs_func (data, fp);
2e9afc
+
2e9afc
+	  if (written == EOF)
2e9afc
+	    {
2e9afc
+	      printf ("fputs[1] failed to write all data\n");
2e9afc
+	      ret |= 1;
2e9afc
+	    }
2e9afc
+
2e9afc
+	  /* Verify that the offset points to the end of the file.  The file
2e9afc
+	     len is maintained by adding data_len each time to reflect the data
2e9afc
+	     written to it.  */
2e9afc
+	  long offset = ftell (fp);
2e9afc
+	  file_len += data_len;
2e9afc
+
2e9afc
+	  if (offset != file_len)
2e9afc
+	    {
2e9afc
+	      printf ("Incorrect offset.  Expected %zu, but got %ld\n",
2e9afc
+		      file_len, offset);
2e9afc
+
2e9afc
+	      ret |= 1;
2e9afc
+	    }
2e9afc
+
2e9afc
+	  printf ("offset = %ld\n", offset);
2e9afc
+	  fclose (fp);
2e9afc
+	}
2e9afc
+    }
2e9afc
+
2e9afc
+  return ret;
2e9afc
+}
2e9afc
+
2e9afc
+static int
2e9afc
+do_one_test (const char *filename)
2e9afc
+{
2e9afc
+  int ret = 0;
2e9afc
+
2e9afc
+  ret |= do_ftell_test (filename);
2e9afc
+  ret |= do_write_test (filename);
2e9afc
+  ret |= do_append_test (filename);
2e9afc
+
2e9afc
+  return ret;
2e9afc
+}
2e9afc
+
2e9afc
+/* Run a set of tests for ftell for regular files and wide mode files.  */
2e9afc
+static int
2e9afc
+do_test (void)
2e9afc
+{
2e9afc
+  int ret = 0;
2e9afc
+  FILE *fp = NULL;
2e9afc
+  char *filename;
2e9afc
+  size_t written;
2e9afc
+  int fd = create_temp_file ("tst-active-handler-tmp.", &filename);
2e9afc
+
2e9afc
+  if (fd == -1)
2e9afc
+    {
2e9afc
+      printf ("create_temp_file: %m\n");
2e9afc
+      return 1;
2e9afc
+    }
2e9afc
+
2e9afc
+  fp = fdopen (fd, "w");
2e9afc
+  if (fp == NULL)
2e9afc
+    {
2e9afc
+      printf ("fdopen[0]: %m\n");
2e9afc
+      close (fd);
2e9afc
+      return 1;
2e9afc
+    }
2e9afc
+
2e9afc
+  data = char_data;
2e9afc
+  data_len = strlen (char_data);
2e9afc
+  file_len = strlen (char_data);
2e9afc
+  written = fputs (data, fp);
2e9afc
+
2e9afc
+  if (written == EOF)
2e9afc
+    {
2e9afc
+      printf ("fputs[1] failed to write data\n");
2e9afc
+      ret = 1;
2e9afc
+    }
2e9afc
+
2e9afc
+  fclose (fp);
2e9afc
+  if (ret)
2e9afc
+    return ret;
2e9afc
+
2e9afc
+  /* Tests for regular files.  */
2e9afc
+  puts ("Regular mode:");
2e9afc
+  fputs_func = (fputs_func_t) fputs;
2e9afc
+  data = char_data;
2e9afc
+  data_len = strlen (char_data);
2e9afc
+  ret |= do_one_test (filename);
2e9afc
+
2e9afc
+  /* Truncate the file before repeating the tests in wide mode.  */
2e9afc
+  fp = fopen (filename, "w");
2e9afc
+  if (fp == NULL)
2e9afc
+    {
2e9afc
+      printf ("fopen failed %m\n");
2e9afc
+      return 1;
2e9afc
+    }
2e9afc
+  fclose (fp);
2e9afc
+
2e9afc
+  /* Tests for wide files.  */
2e9afc
+  puts ("Wide mode:");
2e9afc
+  if (setlocale (LC_ALL, "en_US.UTF-8") == NULL)
2e9afc
+    {
2e9afc
+      printf ("Cannot set en_US.UTF-8 locale.\n");
2e9afc
+      return 1;
2e9afc
+    }
2e9afc
+  fputs_func = (fputs_func_t) fputws;
2e9afc
+  data = wide_data;
2e9afc
+  data_len = wcslen (wide_data);
2e9afc
+  ret |= do_one_test (filename);
2e9afc
+
2e9afc
+  return ret;
2e9afc
+}
2e9afc
diff --git a/libio/wfileops.c b/libio/wfileops.c
2e9afc
index 9cebe77..8b2e108 100644
2e9afc
--- a/libio/wfileops.c
2e9afc
+++ b/libio/wfileops.c
2e9afc
@@ -596,29 +596,25 @@ done:
2e9afc
   return 0;
2e9afc
 }
2e9afc
 
2e9afc
-_IO_off64_t
2e9afc
-_IO_wfile_seekoff (fp, offset, dir, mode)
2e9afc
-     _IO_FILE *fp;
2e9afc
-     _IO_off64_t offset;
2e9afc
-     int dir;
2e9afc
-     int mode;
2e9afc
+/* ftell{,o} implementation for wide mode.  Don't modify any state of the file
2e9afc
+   pointer while we try to get the current state of the stream.  */
2e9afc
+static _IO_off64_t
2e9afc
+do_ftell_wide (_IO_FILE *fp)
2e9afc
 {
2e9afc
-  _IO_off64_t result;
2e9afc
-  _IO_off64_t delta, new_offset;
2e9afc
-  long int count;
2e9afc
-  /* POSIX.1 8.2.3.7 says that after a call the fflush() the file
2e9afc
-     offset of the underlying file must be exact.  */
2e9afc
-  int must_be_exact = ((fp->_wide_data->_IO_read_base
2e9afc
-			== fp->_wide_data->_IO_read_end)
2e9afc
-		       && (fp->_wide_data->_IO_write_base
2e9afc
-			   == fp->_wide_data->_IO_write_ptr));
2e9afc
+  _IO_off64_t result, offset = 0;
2e9afc
+  bool use_cached_offset = false;
2e9afc
 
2e9afc
-  bool was_writing = ((fp->_wide_data->_IO_write_ptr
2e9afc
-		       > fp->_wide_data->_IO_write_base)
2e9afc
-		      || _IO_in_put_mode (fp));
2e9afc
-
2e9afc
-  if (mode == 0)
2e9afc
+  /* No point looking for offsets in the buffer if it hasn't even been
2e9afc
+     allocated.  */
2e9afc
+  if (fp->_wide_data->_IO_buf_base != NULL)
2e9afc
     {
2e9afc
+      const wchar_t *wide_read_base;
2e9afc
+      const wchar_t *wide_read_ptr;
2e9afc
+      const wchar_t *wide_read_end;
2e9afc
+      bool was_writing = ((fp->_wide_data->_IO_write_ptr
2e9afc
+			   > fp->_wide_data->_IO_write_base)
2e9afc
+			  || _IO_in_put_mode (fp));
2e9afc
+
2e9afc
       /* XXX For wide stream with backup store it is not very
2e9afc
 	 reasonable to determine the offset.  The pushed-back
2e9afc
 	 character might require a state change and we need not be
2e9afc
@@ -633,14 +629,142 @@ _IO_wfile_seekoff (fp, offset, dir, mode)
2e9afc
 	      return -1;
2e9afc
 	    }
2e9afc
 
2e9afc
-	  /* There is no more data in the backup buffer.  We can
2e9afc
-	     switch back.  */
2e9afc
-	  _IO_switch_to_main_wget_area (fp);
2e9afc
+	  /* Nothing in the backup store, so note the backed up pointers
2e9afc
+	     without changing the state.  */
2e9afc
+	  wide_read_base = fp->_wide_data->_IO_save_base;
2e9afc
+	  wide_read_ptr = wide_read_base;
2e9afc
+	  wide_read_end = fp->_wide_data->_IO_save_end;
2e9afc
+	}
2e9afc
+      else
2e9afc
+	{
2e9afc
+	  wide_read_base = fp->_wide_data->_IO_read_base;
2e9afc
+	  wide_read_ptr = fp->_wide_data->_IO_read_ptr;
2e9afc
+	  wide_read_end = fp->_wide_data->_IO_read_end;
2e9afc
+	}
2e9afc
+
2e9afc
+      struct _IO_codecvt *cv = fp->_codecvt;
2e9afc
+      int clen = (*cv->__codecvt_do_encoding) (cv);
2e9afc
+
2e9afc
+      if (!was_writing)
2e9afc
+	{
2e9afc
+	  if (clen > 0)
2e9afc
+	    {
2e9afc
+	      offset -= (wide_read_end - wide_read_ptr) * clen;
2e9afc
+	      offset -= fp->_IO_read_end - fp->_IO_read_ptr;
2e9afc
+	    }
2e9afc
+	  else
2e9afc
+	    {
2e9afc
+	      int nread;
2e9afc
+
2e9afc
+	      size_t delta = wide_read_ptr - wide_read_base;
2e9afc
+	      __mbstate_t state = fp->_wide_data->_IO_last_state;
2e9afc
+	      nread = (*cv->__codecvt_do_length) (cv, &state,
2e9afc
+						  fp->_IO_read_base,
2e9afc
+						  fp->_IO_read_end, delta);
2e9afc
+	      offset -= fp->_IO_read_end - fp->_IO_read_base - nread;
2e9afc
+	    }
2e9afc
+	}
2e9afc
+      else
2e9afc
+	{
2e9afc
+	  if (clen > 0)
2e9afc
+	    offset += (fp->_wide_data->_IO_write_ptr
2e9afc
+		       - fp->_wide_data->_IO_write_base) * clen;
2e9afc
+	  else
2e9afc
+	    {
2e9afc
+	      size_t delta = (fp->_wide_data->_IO_write_ptr
2e9afc
+			      - fp->_wide_data->_IO_write_base);
2e9afc
+
2e9afc
+	      /* Allocate enough space for the conversion.  */
2e9afc
+	      size_t outsize = delta * sizeof (wchar_t);
2e9afc
+	      char *out = malloc (outsize);
2e9afc
+	      char *outstop = out;
2e9afc
+	      const wchar_t *in = fp->_wide_data->_IO_write_base;
2e9afc
+
2e9afc
+	      enum __codecvt_result status;
2e9afc
+
2e9afc
+	      __mbstate_t state = fp->_wide_data->_IO_last_state;
2e9afc
+	      status = (*cv->__codecvt_do_out) (cv, &state,
2e9afc
+						in, in + delta, &in,
2e9afc
+						out, out + outsize, &outstop);
2e9afc
+
2e9afc
+	      /* We don't check for __codecvt_partial because it can be
2e9afc
+		 returned on one of two conditions: either the output
2e9afc
+		 buffer is full or the input sequence is incomplete.  We
2e9afc
+		 take care to allocate enough buffer and our input
2e9afc
+		 sequences must be complete since they are accepted as
2e9afc
+		 wchar_t; if not, then that is an error.  */
2e9afc
+	      if (__glibc_unlikely (status != __codecvt_ok))
2e9afc
+		return WEOF;
2e9afc
+
2e9afc
+	      offset += outstop - out;
2e9afc
+	    }
2e9afc
+
2e9afc
+	  /* _IO_read_end coincides with fp._offset, so the actual file
2e9afc
+	     position is fp._offset - (_IO_read_end - new_write_ptr).  */
2e9afc
+	  offset -= fp->_IO_read_end - fp->_IO_write_ptr;
2e9afc
 	}
2e9afc
 
2e9afc
-      dir = _IO_seek_cur, offset = 0; /* Don't move any pointers. */
2e9afc
+      /* It is safe to use the cached offset when available if there is
2e9afc
+	 unbuffered data (indicating that the file handle is active) and
2e9afc
+	 the handle is not for a file open in a+ mode.  The latter
2e9afc
+	 condition is because there could be a scenario where there is a
2e9afc
+	 switch from read mode to write mode using an fseek to an arbitrary
2e9afc
+	 position.  In this case, there would be unbuffered data due to be
2e9afc
+	 appended to the end of the file, but the offset may not
2e9afc
+	 necessarily be the end of the file.  It is fine to use the cached
2e9afc
+	 offset when the a+ stream is in read mode though, since the offset
2e9afc
+	 is maintained correctly in that case.  Note that this is not a
2e9afc
+	 comprehensive set of cases when the offset is reliable.  The
2e9afc
+	 offset may be reliable even in some cases where there is no
2e9afc
+	 unflushed input and the handle is active, but it's just that we
2e9afc
+	 don't have a way to identify that condition reliably.  */
2e9afc
+      use_cached_offset = (offset != 0 && fp->_offset != _IO_pos_BAD
2e9afc
+			   && ((fp->_flags & (_IO_IS_APPENDING | _IO_NO_READS))
2e9afc
+			       == (_IO_IS_APPENDING | _IO_NO_READS)
2e9afc
+			       && was_writing));
2e9afc
     }
2e9afc
 
2e9afc
+  if (use_cached_offset)
2e9afc
+    result = fp->_offset;
2e9afc
+  else
2e9afc
+    result = get_file_offset (fp);
2e9afc
+
2e9afc
+  if (result == EOF)
2e9afc
+    return result;
2e9afc
+
2e9afc
+  result += offset;
2e9afc
+
2e9afc
+  return result;
2e9afc
+}
2e9afc
+
2e9afc
+_IO_off64_t
2e9afc
+_IO_wfile_seekoff (fp, offset, dir, mode)
2e9afc
+     _IO_FILE *fp;
2e9afc
+     _IO_off64_t offset;
2e9afc
+     int dir;
2e9afc
+     int mode;
2e9afc
+{
2e9afc
+  _IO_off64_t result;
2e9afc
+  _IO_off64_t delta, new_offset;
2e9afc
+  long int count;
2e9afc
+
2e9afc
+  /* Short-circuit into a separate function.  We don't want to mix any
2e9afc
+     functionality and we don't want to touch anything inside the FILE
2e9afc
+     object. */
2e9afc
+  if (mode == 0)
2e9afc
+    return do_ftell_wide (fp);
2e9afc
+
2e9afc
+  /* POSIX.1 8.2.3.7 says that after a call the fflush() the file
2e9afc
+     offset of the underlying file must be exact.  */
2e9afc
+  int must_be_exact = ((fp->_wide_data->_IO_read_base
2e9afc
+			== fp->_wide_data->_IO_read_end)
2e9afc
+		       && (fp->_wide_data->_IO_write_base
2e9afc
+			   == fp->_wide_data->_IO_write_ptr));
2e9afc
+
2e9afc
+  bool was_writing = ((fp->_wide_data->_IO_write_ptr
2e9afc
+		       > fp->_wide_data->_IO_write_base)
2e9afc
+		      || _IO_in_put_mode (fp));
2e9afc
+
2e9afc
   /* Flush unwritten characters.
2e9afc
      (This may do an unneeded write if we seek within the buffer.
2e9afc
      But to be able to switch to reading, we would need to set
2e9afc
@@ -648,7 +772,7 @@ _IO_wfile_seekoff (fp, offset, dir, mode)
2e9afc
      which assumes file_ptr() is eGptr.  Anyway, since we probably
2e9afc
      end up flushing when we close(), it doesn't make much difference.)
2e9afc
      FIXME: simulate mem-mapped files. */
2e9afc
-  else if (was_writing && _IO_switch_to_wget_mode (fp))
2e9afc
+  if (was_writing && _IO_switch_to_wget_mode (fp))
2e9afc
     return WEOF;
2e9afc
 
2e9afc
   if (fp->_wide_data->_IO_buf_base == NULL)
2e9afc
@@ -693,7 +817,6 @@ _IO_wfile_seekoff (fp, offset, dir, mode)
2e9afc
 	    {
2e9afc
 	      int nread;
2e9afc
 
2e9afc
-	    flushed:
2e9afc
 	      delta = (fp->_wide_data->_IO_read_ptr
2e9afc
 		       - fp->_wide_data->_IO_read_base);
2e9afc
 	      fp->_wide_data->_IO_state = fp->_wide_data->_IO_last_state;
2e9afc
@@ -706,80 +829,9 @@ _IO_wfile_seekoff (fp, offset, dir, mode)
2e9afc
 	      offset -= fp->_IO_read_end - fp->_IO_read_base - nread;
2e9afc
 	    }
2e9afc
 	}
2e9afc
-      else
2e9afc
-	{
2e9afc
-	  char *new_write_ptr = fp->_IO_write_ptr;
2e9afc
-
2e9afc
-	  if (clen > 0)
2e9afc
-	    offset += (fp->_wide_data->_IO_write_ptr
2e9afc
-		       - fp->_wide_data->_IO_write_base) / clen;
2e9afc
-	  else
2e9afc
-	    {
2e9afc
-	      enum __codecvt_result status = __codecvt_ok;
2e9afc
-	      delta = (fp->_wide_data->_IO_write_ptr
2e9afc
-		       - fp->_wide_data->_IO_write_base);
2e9afc
-	      const wchar_t *write_base = fp->_wide_data->_IO_write_base;
2e9afc
-
2e9afc
-	      /* FIXME: This actually ends up in two iterations of conversion,
2e9afc
-		 one here and the next when the buffer actually gets flushed.
2e9afc
-		 It may be possible to optimize this in future so that
2e9afc
-		 wdo_write identifies already converted content and does not
2e9afc
-		 redo it.  In any case, this is much better than having to
2e9afc
-		 flush buffers for every ftell.  */
2e9afc
-	      do
2e9afc
-		{
2e9afc
-		  /* There is not enough space in the buffer to do the entire
2e9afc
-		     conversion, so there is no point trying to avoid the
2e9afc
-		     buffer flush.  Just do it and go back to how it was with
2e9afc
-		     the read mode.  */
2e9afc
-		  if (status == __codecvt_partial
2e9afc
-		      || (delta > 0 && new_write_ptr == fp->_IO_buf_end))
2e9afc
-		    {
2e9afc
-		      if (_IO_switch_to_wget_mode (fp))
2e9afc
-			return WEOF;
2e9afc
-		      goto flushed;
2e9afc
-		    }
2e9afc
-
2e9afc
-		  const wchar_t *new_wbase = fp->_wide_data->_IO_write_base;
2e9afc
-		  fp->_wide_data->_IO_state = fp->_wide_data->_IO_last_state;
2e9afc
-		  status = (*cv->__codecvt_do_out) (cv,
2e9afc
-						    &fp->_wide_data->_IO_state,
2e9afc
-						    write_base,
2e9afc
-						    write_base + delta,
2e9afc
-						    &new_wbase,
2e9afc
-						    new_write_ptr,
2e9afc
-						    fp->_IO_buf_end,
2e9afc
-						    &new_write_ptr);
2e9afc
-
2e9afc
-		  delta -= new_wbase - write_base;
2e9afc
-
2e9afc
-		  /* If there was an error, then return WEOF.
2e9afc
-		     TODO: set buffer state.  */
2e9afc
-		  if (__builtin_expect (status == __codecvt_error, 0))
2e9afc
-		      return WEOF;
2e9afc
-		}
2e9afc
-	      while (delta > 0);
2e9afc
-	    }
2e9afc
-
2e9afc
-	  /* _IO_read_end coincides with fp._offset, so the actual file position
2e9afc
-	     is fp._offset - (_IO_read_end - new_write_ptr).  This is fine
2e9afc
-	     even if fp._offset is not set, since fp->_IO_read_end is then at
2e9afc
-	     _IO_buf_base and this adjustment is for unbuffered output.  */
2e9afc
-	  offset -= fp->_IO_read_end - new_write_ptr;
2e9afc
-	}
2e9afc
 
2e9afc
       if (fp->_offset == _IO_pos_BAD)
2e9afc
-	{
2e9afc
-	  if (mode != 0)
2e9afc
-	    goto dumb;
2e9afc
-	  else
2e9afc
-	    {
2e9afc
-	      result = _IO_SYSSEEK (fp, 0, dir);
2e9afc
-	      if (result == EOF)
2e9afc
-		return result;
2e9afc
-	      fp->_offset = result;
2e9afc
-	    }
2e9afc
-	}
2e9afc
+	goto dumb;
2e9afc
 
2e9afc
       /* Make offset absolute, assuming current pointer is file_ptr(). */
2e9afc
       offset += fp->_offset;
2e9afc
@@ -802,10 +854,6 @@ _IO_wfile_seekoff (fp, offset, dir, mode)
2e9afc
     }
2e9afc
   /* At this point, dir==_IO_seek_set. */
2e9afc
 
2e9afc
-  /* If we are only interested in the current position we've found it now.  */
2e9afc
-  if (mode == 0)
2e9afc
-    return offset;
2e9afc
-
2e9afc
   /* If destination is within current buffer, optimize: */
2e9afc
   if (fp->_offset != _IO_pos_BAD && fp->_IO_read_base != NULL
2e9afc
       && !_IO_in_backup (fp))