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