2e9afc
commit ae42bbc55a9e05976269026ddabcfb917f6e922f
2e9afc
Author: Siddhesh Poyarekar <siddhesh@redhat.com>
2e9afc
Date:   Mon Mar 17 18:42:53 2014 +0530
2e9afc
2e9afc
    Change offset in fdopen only if setting O_APPEND
2e9afc
    
2e9afc
    fdopen should only be allowed to change the offset in the file it
2e9afc
    attaches to if it is setting O_APPEND.  If O_APPEND is already set, it
2e9afc
    should not change the state of the handle.
2e9afc
2e9afc
commit ea33158c96c53a64402a772186956c1f5cb556ae
2e9afc
Author: Siddhesh Poyarekar <siddhesh@redhat.com>
2e9afc
Date:   Tue Mar 11 17:04:49 2014 +0530
2e9afc
2e9afc
    Fix offset caching for streams and use it for ftell (BZ #16680)
2e9afc
    
2e9afc
    The ftell implementation was made conservative to ensure that
2e9afc
    incorrectly cached offsets never affect it.  However, this causes
2e9afc
    problems for append mode when a file stream is rewound.  Additionally,
2e9afc
    the 'clever' trick of using stat to get position for append mode files
2e9afc
    caused more problems than it solved and broke old behavior.  I have
2e9afc
    described the various problems that it caused and then finally the
2e9afc
    solution.
2e9afc
    
2e9afc
    For a and a+ mode files, rewinding the stream should result in ftell
2e9afc
    returning 0 as the offset, but the stat() trick caused it to
2e9afc
    (incorrectly) always return the end of file.  Now I couldn't find
2e9afc
    anything in POSIX that specifies the stream position after rewind()
2e9afc
    for a file opened in 'a' mode, but for 'a+' mode it should be set to
2e9afc
    0.  For 'a' mode too, it probably makes sense to keep it set to 0 in
2e9afc
    the interest of retaining old behavior.
2e9afc
    
2e9afc
    The initial file position for append mode files is implementation
2e9afc
    defined, so the implementation could either retain the current file
2e9afc
    position or move the position to the end of file.  The earlier ftell
2e9afc
    implementation would move the offset to end of file for append-only
2e9afc
    mode, but retain the old offset for a+ mode.  It would also cache the
2e9afc
    offset (this detail is important).  My patch broke this and would set
2e9afc
    the initial position to end of file for both append modes, thus
2e9afc
    breaking old behavior.  I was ignorant enough to write an incorrect
2e9afc
    test case for it too.
2e9afc
    
2e9afc
    The Change:
2e9afc
    
2e9afc
    I have now brought back the behavior of seeking to end of file for
2e9afc
    append-only streams, but with a slight difference.  I don't cache the
2e9afc
    offset though, since we would want ftell to query the current file
2e9afc
    position through lseek while the stream is not active.  Since the
2e9afc
    offset is moved to the end of file, we can rely on the file position
2e9afc
    reported by lseek and we don't need to resort to the stat() nonsense.
2e9afc
    
2e9afc
    Finally, the cache is always reliable, except when there are unflished
2e9afc
    writes in an append mode stream (i.e. both a and a+).  In the latter
2e9afc
    case, it is safe to just do an lseek to SEEK_END.  The value can be
2e9afc
    safely cached too, since the file handle is already active at this
2e9afc
    point.  Incidentally, this is the only state change we affect in the
2e9afc
    file handle (apart from taking locks of course).
2e9afc
    
2e9afc
    I have also updated the test case to correct my impression of the
2e9afc
    initial file position for a+ streams to the initial behavior.  I have
2e9afc
    verified that this does not break any existing tests in the testsuite
2e9afc
    and also passes with the new tests.
2e9afc
2e9afc
commit b1dbb426e164ad1236c2c76268e03fec5c7a7bbe
2e9afc
Author: Siddhesh Poyarekar <siddhesh@redhat.com>
2e9afc
Date:   Mon Mar 10 16:20:01 2014 +0530
2e9afc
2e9afc
    Fix up return codes for tests in tst-ftell-active-handler
2e9afc
    
2e9afc
    The test functions used a variable ret to store failure codes for
2e9afc
    individual tests, but the variable was incorrectly used to record
2e9afc
    other failure codes too, resulting in overwriting of the tests status.
2e9afc
    This is now fixed by making sure that the ret variable is used only
2e9afc
    for recording test failures.
2e9afc
    
2e9afc
    	* libio/tst-ftell-active-handler.c (do_ftell_test): Don't mix
2e9afc
    	up test status with function return status.
2e9afc
    	(do_write_test): Likewise.
2e9afc
    	(do_append_test): Likewise.
2e9afc
diff --git a/libio/fileops.c b/libio/fileops.c
2e9afc
index 2e7bc8d..cf68dbf 100644
2e9afc
--- a/libio/fileops.c
2e9afc
+++ b/libio/fileops.c
2e9afc
@@ -232,13 +232,18 @@ _IO_file_open (fp, filename, posix_mode, prot, read_write, is32not64)
2e9afc
     return NULL;
2e9afc
   fp->_fileno = fdesc;
2e9afc
   _IO_mask_flags (fp, read_write,_IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING);
2e9afc
-  if ((read_write & _IO_IS_APPENDING) && (read_write & _IO_NO_READS))
2e9afc
-    if (_IO_SEEKOFF (fp, (_IO_off64_t)0, _IO_seek_end, _IOS_INPUT|_IOS_OUTPUT)
2e9afc
-	== _IO_pos_BAD && errno != ESPIPE)
2e9afc
-      {
2e9afc
-	close_not_cancel (fdesc);
2e9afc
-	return NULL;
2e9afc
-      }
2e9afc
+  /* For append mode, send the file offset to the end of the file.  Don't
2e9afc
+     update the offset cache though, since the file handle is not active.  */
2e9afc
+  if ((read_write & (_IO_IS_APPENDING | _IO_NO_READS))
2e9afc
+      == (_IO_IS_APPENDING | _IO_NO_READS))
2e9afc
+    {
2e9afc
+      _IO_off64_t new_pos = _IO_SYSSEEK (fp, 0, _IO_seek_end);
2e9afc
+      if (new_pos == _IO_pos_BAD && errno != ESPIPE)
2e9afc
+	{
2e9afc
+	  close_not_cancel (fdesc);
2e9afc
+	  return NULL;
2e9afc
+	}
2e9afc
+    }
2e9afc
   _IO_link_in ((struct _IO_FILE_plus *) fp);
2e9afc
   return fp;
2e9afc
 }
2e9afc
@@ -929,43 +934,13 @@ _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
+/* ftell{,o} implementation.  The only time we modify the state of the stream
2e9afc
+   is when we have unflushed writes.  In that case we seek to the end and
2e9afc
+   record that offset in the stream object.  */
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
+  _IO_off64_t result, offset = 0;
2e9afc
 
2e9afc
   /* No point looking at unflushed data if we haven't allocated buffers
2e9afc
      yet.  */
2e9afc
@@ -974,39 +949,37 @@ do_ftell (_IO_FILE *fp)
2e9afc
       bool was_writing = (fp->_IO_write_ptr > fp->_IO_write_base
2e9afc
 			  || _IO_in_put_mode (fp));
2e9afc
 
2e9afc
+      bool append_mode = (fp->_flags & _IO_IS_APPENDING) == _IO_IS_APPENDING;
2e9afc
+
2e9afc
+      /* When we have unflushed writes in append mode, seek to the end of the
2e9afc
+	 file and record that offset.  This is the only time we change the file
2e9afc
+	 stream state and it is safe since the file handle is active.  */
2e9afc
+      if (was_writing && append_mode)
2e9afc
+	{
2e9afc
+	  result = _IO_SYSSEEK (fp, 0, _IO_seek_end);
2e9afc
+	  if (result == _IO_pos_BAD)
2e9afc
+	    return EOF;
2e9afc
+	  else
2e9afc
+	    fp->_offset = result;
2e9afc
+	}
2e9afc
+
2e9afc
       /* Adjust for unflushed data.  */
2e9afc
       if (!was_writing)
2e9afc
-	result -= fp->_IO_read_end - fp->_IO_read_ptr;
2e9afc
+	offset -= 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
+	offset += fp->_IO_write_ptr - fp->_IO_read_end;
2e9afc
     }
2e9afc
 
2e9afc
-  if (use_cached_offset)
2e9afc
-    result += fp->_offset;
2e9afc
+  if (fp->_offset != _IO_pos_BAD)
2e9afc
+    result = fp->_offset;
2e9afc
   else
2e9afc
-    result += get_file_offset (fp);
2e9afc
+    result = _IO_SYSSEEK (fp, 0, _IO_seek_cur);
2e9afc
 
2e9afc
   if (result == EOF)
2e9afc
     return result;
2e9afc
 
2e9afc
+  result += offset;
2e9afc
+
2e9afc
   if (result < 0)
2e9afc
     {
2e9afc
       __set_errno (EINVAL);
2e9afc
@@ -1016,7 +989,6 @@ do_ftell (_IO_FILE *fp)
2e9afc
   return result;
2e9afc
 }
2e9afc
 
2e9afc
-
2e9afc
 _IO_off64_t
2e9afc
 _IO_new_file_seekoff (fp, offset, dir, mode)
2e9afc
      _IO_FILE *fp;
2e9afc
diff --git a/libio/iofdopen.c b/libio/iofdopen.c
2e9afc
index 3f266f7..b36d21d 100644
2e9afc
--- a/libio/iofdopen.c
2e9afc
+++ b/libio/iofdopen.c
2e9afc
@@ -59,6 +59,11 @@ _IO_new_fdopen (fd, mode)
2e9afc
   int i;
2e9afc
   int use_mmap = 0;
2e9afc
 
2e9afc
+  /* Decide whether we modify the offset of the file we attach to and seek to
2e9afc
+     the end of file.  We only do this if the mode is 'a' and if the file
2e9afc
+     descriptor did not have O_APPEND in its flags already.  */
2e9afc
+  bool do_seek = false;
2e9afc
+
2e9afc
   switch (*mode)
2e9afc
     {
2e9afc
     case 'r':
2e9afc
@@ -128,6 +133,7 @@ _IO_new_fdopen (fd, mode)
2e9afc
      */
2e9afc
   if ((posix_mode & O_APPEND) && !(fd_flags & O_APPEND))
2e9afc
     {
2e9afc
+      do_seek = true;
2e9afc
 #ifdef F_SETFL
2e9afc
       if (_IO_fcntl (fd, F_SETFL, fd_flags | O_APPEND) == -1)
2e9afc
 #endif
2e9afc
@@ -167,6 +173,16 @@ _IO_new_fdopen (fd, mode)
2e9afc
   _IO_mask_flags (&new_f->fp.file, read_write,
2e9afc
 		  _IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING);
2e9afc
 
2e9afc
+  /* For append mode, set the file offset to the end of the file if we added
2e9afc
+     O_APPEND to the file descriptor flags.  Don't update the offset cache
2e9afc
+     though, since the file handle is not active.  */
2e9afc
+  if (do_seek && ((read_write & (_IO_IS_APPENDING | _IO_NO_READS))
2e9afc
+		  == (_IO_IS_APPENDING | _IO_NO_READS)))
2e9afc
+    {
2e9afc
+      _IO_off64_t new_pos = _IO_SYSSEEK (&new_f->fp.file, 0, _IO_seek_end);
2e9afc
+      if (new_pos == _IO_pos_BAD && errno != ESPIPE)
2e9afc
+	return NULL;
2e9afc
+    }
2e9afc
   return &new_f->fp.file;
2e9afc
 }
2e9afc
 libc_hidden_ver (_IO_new_fdopen, _IO_fdopen)
2e9afc
diff --git a/libio/tst-ftell-active-handler.c b/libio/tst-ftell-active-handler.c
2e9afc
index 54bfe63..e9dc7b3 100644
2e9afc
--- a/libio/tst-ftell-active-handler.c
2e9afc
+++ b/libio/tst-ftell-active-handler.c
2e9afc
@@ -88,6 +88,107 @@ static size_t file_len;
2e9afc
 typedef int (*fputs_func_t) (const void *data, FILE *fp);
2e9afc
 fputs_func_t fputs_func;
2e9afc
 
2e9afc
+/* Test that ftell output after a rewind is correct.  */
2e9afc
+static int
2e9afc
+do_rewind_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
+	  {"w", O_WRONLY, 0, data_len},
2e9afc
+	  {"w+", O_RDWR, 0, data_len},
2e9afc
+	  {"r+", O_RDWR, 0, data_len},
2e9afc
+	  /* The new offsets for 'a' and 'a+' modes have to factor in the
2e9afc
+	     previous writes since they always append to the end of the
2e9afc
+	     file.  */
2e9afc
+	  {"a", O_WRONLY, 0, 3 * data_len},
2e9afc
+	  {"a+", O_RDWR, 0, 4 * data_len},
2e9afc
+    };
2e9afc
+
2e9afc
+  /* Empty the file before the test so that our offsets are simple to
2e9afc
+     calculate.  */
2e9afc
+  FILE *fp = fopen (filename, "w");
2e9afc
+  if (fp == NULL)
2e9afc
+    {
2e9afc
+      printf ("Failed to open file for emptying\n");
2e9afc
+      return 1;
2e9afc
+    }
2e9afc
+  fclose (fp);
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
+	  int fileret;
2e9afc
+
2e9afc
+	  printf ("\trewind: %s (file, \"%s\"): ", j == 0 ? "fdopen" : "fopen",
2e9afc
+		  test_modes[i].mode);
2e9afc
+
2e9afc
+	  if (j == 0)
2e9afc
+	    fileret = get_handles_fdopen (filename, fd, fp,
2e9afc
+					  test_modes[i].fd_mode,
2e9afc
+					  test_modes[i].mode);
2e9afc
+	  else
2e9afc
+	    fileret = get_handles_fopen (filename, fd, fp, test_modes[i].mode);
2e9afc
+
2e9afc
+	  if (fileret != 0)
2e9afc
+	    return fileret;
2e9afc
+
2e9afc
+	  /* Write some content to the file, rewind and ensure that the ftell
2e9afc
+	     output after the rewind is 0.  POSIX does not specify what the
2e9afc
+	     behavior is when a file is rewound in 'a' mode, so we retain
2e9afc
+	     current behavior, which is to keep the 0 offset.  */
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
+	  rewind (fp);
2e9afc
+	  long offset = ftell (fp);
2e9afc
+
2e9afc
+	  if (offset != test_modes[i].old_off)
2e9afc
+	    {
2e9afc
+	      printf ("Incorrect old offset.  Expected %zu, but got %ld, ",
2e9afc
+		      test_modes[i].old_off, offset);
2e9afc
+	      ret |= 1;
2e9afc
+	    }
2e9afc
+	  else
2e9afc
+	    printf ("old offset = %ld, ", offset);
2e9afc
+
2e9afc
+	  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
+	  /* After this write, the offset in append modes should factor in the
2e9afc
+	     implicit lseek to the end of file.  */
2e9afc
+	  offset = ftell (fp);
2e9afc
+	  if (offset != test_modes[i].new_off)
2e9afc
+	    {
2e9afc
+	      printf ("Incorrect new offset.  Expected %zu, but got %ld\n",
2e9afc
+		      test_modes[i].new_off, offset);
2e9afc
+	      ret |= 1;
2e9afc
+	    }
2e9afc
+	  else
2e9afc
+	    printf ("new offset = %ld\n", offset);
2e9afc
+	}
2e9afc
+    }
2e9afc
+  return ret;
2e9afc
+}
2e9afc
+
2e9afc
 /* Test that the value of ftell is not cached when the stream handle is not
2e9afc
    active.  */
2e9afc
 static int
2e9afc
@@ -107,11 +208,13 @@ do_ftell_test (const char *filename)
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
+	  /* For the 'a' mode, 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
+	     added to the old value.  For a+ mode however, the initial file
2e9afc
+	     position is the file position of the underlying file descriptor,
2e9afc
+	     since it is initially assumed to be in read mode.  */
2e9afc
 	  {"a", O_WRONLY, data_len, 2 * data_len},
2e9afc
-	  {"a+", O_RDWR, 2 * data_len, 3 * data_len},
2e9afc
+	  {"a+", O_RDWR, 0, 3 * data_len},
2e9afc
     };
2e9afc
   for (int j = 0; j < 2; j++)
2e9afc
     {
2e9afc
@@ -119,17 +222,20 @@ do_ftell_test (const char *filename)
2e9afc
 	{
2e9afc
 	  FILE *fp;
2e9afc
 	  int fd;
2e9afc
+	  int fileret;
2e9afc
+
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
+	    fileret = get_handles_fdopen (filename, fd, fp,
2e9afc
+					  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
+	    fileret = get_handles_fopen (filename, fd, fp, test_modes[i].mode);
2e9afc
 
2e9afc
-	  if (ret != 0)
2e9afc
-	    return ret;
2e9afc
+	  if (fileret != 0)
2e9afc
+	    return fileret;
2e9afc
 
2e9afc
 	  long off = ftell (fp);
2e9afc
 	  if (off != test_modes[i].old_off)
2e9afc
@@ -143,13 +249,18 @@ do_ftell_test (const char *filename)
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
+	  int write_ret = write (fd, data, data_len);
2e9afc
+	  if (write_ret != data_len)
2e9afc
+	    {
2e9afc
+	      printf ("write failed (%m)\n");
2e9afc
+	      ret |= 1;
2e9afc
+	    }
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
+		      test_modes[i].new_off, off);
2e9afc
 	      ret |= 1;
2e9afc
 	    }
2e9afc
 	  else
2e9afc
@@ -184,21 +295,23 @@ do_write_test (const char *filename)
2e9afc
     {
2e9afc
       for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++)
2e9afc
 	{
2e9afc
+	  int fileret;
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
+	    fileret = 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
+	    fileret = get_handles_fdopen (filename, fd, fp,
2e9afc
+					  test_modes[i].fd_mode,
2e9afc
+					  test_modes[i].mode);
2e9afc
 
2e9afc
-	  if (ret != 0)
2e9afc
-	    return ret;
2e9afc
+	  if (fileret != 0)
2e9afc
+	    return fileret;
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
+	  off_t seek_ret = lseek (fd, file_len - 1, SEEK_SET);
2e9afc
+	  if (seek_ret == -1)
2e9afc
 	    {
2e9afc
 	      printf ("lseek failed: %m\n");
2e9afc
 	      ret |= 1;
2e9afc
@@ -258,17 +371,20 @@ do_append_test (const char *filename)
2e9afc
     {
2e9afc
       for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++)
2e9afc
 	{
2e9afc
+	  int fileret;
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
+	    fileret = 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
+	    fileret = get_handles_fdopen (filename, fd, fp,
2e9afc
+					  test_modes[i].fd_mode,
2e9afc
+					  test_modes[i].mode);
2e9afc
 
2e9afc
-	  if (ret != 0)
2e9afc
-	    return ret;
2e9afc
+	  if (fileret != 0)
2e9afc
+	    return fileret;
2e9afc
 
2e9afc
 	  /* Write some data.  */
2e9afc
 	  size_t written = fputs_func (data, fp);
2e9afc
@@ -298,6 +414,61 @@ do_append_test (const char *filename)
2e9afc
 	}
2e9afc
     }
2e9afc
 
2e9afc
+  /* For fdopen in 'a' mode, the file descriptor should not change if the file
2e9afc
+     is already open with the O_APPEND flag set.  */
2e9afc
+  fd = open (filename, O_WRONLY | O_APPEND, 0);
2e9afc
+  if (fd == -1)
2e9afc
+    {
2e9afc
+      printf ("open(O_APPEND) failed: %m\n");
2e9afc
+      return 1;
2e9afc
+    }
2e9afc
+
2e9afc
+  off_t seek_ret = lseek (fd, file_len - 1, SEEK_SET);
2e9afc
+  if (seek_ret == -1)
2e9afc
+    {
2e9afc
+      printf ("lseek[O_APPEND][0] failed: %m\n");
2e9afc
+      ret |= 1;
2e9afc
+    }
2e9afc
+
2e9afc
+  fp = fdopen (fd, "a");
2e9afc
+  if (fp == NULL)
2e9afc
+    {
2e9afc
+      printf ("fdopen(O_APPEND) failed: %m\n");
2e9afc
+      close (fd);
2e9afc
+      return 1;
2e9afc
+    }
2e9afc
+
2e9afc
+  off_t new_seek_ret = lseek (fd, 0, SEEK_CUR);
2e9afc
+  if (seek_ret == -1)
2e9afc
+    {
2e9afc
+      printf ("lseek[O_APPEND][1] failed: %m\n");
2e9afc
+      ret |= 1;
2e9afc
+    }
2e9afc
+
2e9afc
+  printf ("\tappend: fdopen (file, \"a\"): O_APPEND: ");
2e9afc
+
2e9afc
+  if (seek_ret != new_seek_ret)
2e9afc
+    {
2e9afc
+      printf ("incorrectly modified file offset to %ld, should be %ld",
2e9afc
+	      new_seek_ret, seek_ret);
2e9afc
+      ret |= 1;
2e9afc
+    }
2e9afc
+  else
2e9afc
+    printf ("retained current file offset %ld", seek_ret);
2e9afc
+
2e9afc
+  new_seek_ret = ftello (fp);
2e9afc
+
2e9afc
+  if (seek_ret != new_seek_ret)
2e9afc
+    {
2e9afc
+      printf (", ftello reported incorrect offset %ld, should be %ld\n",
2e9afc
+	      new_seek_ret, seek_ret);
2e9afc
+      ret |= 1;
2e9afc
+    }
2e9afc
+  else
2e9afc
+    printf (", ftello reported correct offset %ld\n", seek_ret);
2e9afc
+
2e9afc
+  fclose (fp);
2e9afc
+
2e9afc
   return ret;
2e9afc
 }
2e9afc
 
2e9afc
@@ -309,6 +480,7 @@ do_one_test (const char *filename)
2e9afc
   ret |= do_ftell_test (filename);
2e9afc
   ret |= do_write_test (filename);
2e9afc
   ret |= do_append_test (filename);
2e9afc
+  ret |= do_rewind_test (filename);
2e9afc
 
2e9afc
   return ret;
2e9afc
 }
2e9afc
diff --git a/libio/wfileops.c b/libio/wfileops.c
2e9afc
index 8b2e108..3199861 100644
2e9afc
--- a/libio/wfileops.c
2e9afc
+++ b/libio/wfileops.c
2e9afc
@@ -597,12 +597,12 @@ done:
2e9afc
 }
2e9afc
 
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
+   pointer while we try to get the current state of the stream except in one
2e9afc
+   case, which is when we have unflushed writes in append mode.  */
2e9afc
 static _IO_off64_t
2e9afc
 do_ftell_wide (_IO_FILE *fp)
2e9afc
 {
2e9afc
   _IO_off64_t result, offset = 0;
2e9afc
-  bool use_cached_offset = false;
2e9afc
 
2e9afc
   /* No point looking for offsets in the buffer if it hasn't even been
2e9afc
      allocated.  */
2e9afc
@@ -615,6 +615,20 @@ do_ftell_wide (_IO_FILE *fp)
2e9afc
 			   > fp->_wide_data->_IO_write_base)
2e9afc
 			  || _IO_in_put_mode (fp));
2e9afc
 
2e9afc
+      bool append_mode = (fp->_flags & _IO_IS_APPENDING) == _IO_IS_APPENDING;
2e9afc
+
2e9afc
+      /* When we have unflushed writes in append mode, seek to the end of the
2e9afc
+	 file and record that offset.  This is the only time we change the file
2e9afc
+	 stream state and it is safe since the file handle is active.  */
2e9afc
+      if (was_writing && append_mode)
2e9afc
+	{
2e9afc
+	  result = _IO_SYSSEEK (fp, 0, _IO_seek_end);
2e9afc
+	  if (result == _IO_pos_BAD)
2e9afc
+	    return EOF;
2e9afc
+	  else
2e9afc
+	    fp->_offset = result;
2e9afc
+	}
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
@@ -703,37 +717,24 @@ do_ftell_wide (_IO_FILE *fp)
2e9afc
 	     position is fp._offset - (_IO_read_end - new_write_ptr).  */
2e9afc
 	  offset -= fp->_IO_read_end - fp->_IO_write_ptr;
2e9afc
 	}
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
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
+  if (fp->_offset != _IO_pos_BAD)
2e9afc
     result = fp->_offset;
2e9afc
   else
2e9afc
-    result = get_file_offset (fp);
2e9afc
+    result = _IO_SYSSEEK (fp, 0, _IO_seek_cur);
2e9afc
 
2e9afc
   if (result == EOF)
2e9afc
     return result;
2e9afc
 
2e9afc
   result += offset;
2e9afc
 
2e9afc
+  if (result < 0)
2e9afc
+    {
2e9afc
+      __set_errno (EINVAL);
2e9afc
+      return EOF;
2e9afc
+    }
2e9afc
+
2e9afc
   return result;
2e9afc
 }
2e9afc