Hey there,

I stumbled across a problem with apr_file_trunc not updating its
internal buffer state if the previous operation was a read.  This
is a follow-up to a similar problem fixed in 2010.

Fix, test and commit message below. Developed against 1.4.5,
the functional patch should also cleanly apply to /trunk.

-- Stefan^2.

[[[
Fix another issue with apr_file_trunc on buffered files.
If the file was not in write mode, the buffer content and size
information would not be clipped to the truncation offset.

* file_io/unix/seek.c
  (apr_file_trunc): Clip the buffer in read mode as well.

* test/testfile.c
  (test_truncate): Extend this test to discover stale data and
                   invalid size information.
]]]
[[[
Index: file_io/unix/seek.c
===================================================================
--- file_io/unix/seek.c    (revision 1618575)
+++ file_io/unix/seek.c    (working copy)
@@ -117,6 +117,24 @@ apr_status_t apr_file_trunc(apr_file_t *
             /* Reset buffer positions for write mode */
             fp->bufpos = fp->direction = fp->dataRead = 0;
         }
+
+        /* Limit the buffered data to what will remain of the actual file.
+ * That prevents stale data from being read and wrong file locations
+         * being reported. */
+        if (fp->filePtr >= offset) {
+            /* The entire buffer will lie beyond EOF now. Put it at EOF. */
+            fp->dataRead = fp->bufpos = 0;
+            fp->filePtr = offset;
+        }
+        else if (fp->filePtr + fp->dataRead > offset) {
+            /* Only part of the buffer will be beyond EOF.
+             * Truncate it and adjust the read pointer if necessary. */
+            fp->dataRead = offset - fp->filePtr;
+            if (fp->dataRead < fp->bufpos) {
+                fp->bufpos = fp->dataRead;
+            }
+        }
+
         file_unlock(fp);
         if (rc) {
             return rc;
Index: test/testfile.c
===================================================================
--- test/testfile.c    (revision 1618575)
+++ test/testfile.c    (working copy)
@@ -777,8 +777,11 @@ static void test_truncate(abts_case *tc,
     apr_file_t *f;
     const char *fname = "data/testtruncate.dat";
     const char *s;
+    apr_off_t offset;
     apr_size_t nbytes;
     apr_finfo_t finfo;
+    const char teststring = "Hello World";
+    char *buffer = apr_pcalloc(p, sizeof(teststring));

     apr_file_remove(fname, p);

@@ -806,6 +809,81 @@ static void test_truncate(abts_case *tc,
     ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
ABTS_ASSERT(tc, "File size mismatch, expected 0 (empty)", finfo.size == 0);

+    /* Regression test: File read buffer did not get truncated. */
+
+    /* Setup. */
+    rv = apr_file_open(&f, fname,
+ APR_FOPEN_READ | APR_FOPEN_WRITE | APR_FOPEN_BUFFERED |
+                       APR_FOPEN_TRUNCATE, APR_UREAD | APR_UWRITE, p);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+
+    nbytes = sizeof(teststring);
+    rv = apr_file_write(f, "Hello World", &nbytes);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+    rv = apr_file_flush(f);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+    offset = 0;
+    rv = apr_file_seek(f, APR_SET, &offset);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+    rv = apr_file_read(f, buffer, &nbytes);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+    rv = apr_file_trunc(f, 5);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+
+    /* Test. Attempting to read data should return nothing but it used to
+       return stale data. */
+    nbytes = 6;
+    buffer[0] = 0;
+    rv = apr_file_read(f, buffer, &nbytes);
+    ABTS_ASSERT(tc, "Read stale buffered data beyond EOF",
+                nbytes == 0 && buffer[0] == 0);
+    ABTS_INT_EQUAL(tc, 1, APR_STATUS_IS_EOF(rv));
+
+    rv = apr_file_close(f);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+
+    /* Regression test: File read buffer state had been inconsistent. */
+
+    /* Setup. */
+    rv = apr_file_open(&f, fname,
+ APR_FOPEN_READ | APR_FOPEN_WRITE | APR_FOPEN_BUFFERED |
+                       APR_FOPEN_TRUNCATE, APR_UREAD | APR_UWRITE, p);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+
+    nbytes = sizeof(teststring);
+    rv = apr_file_write(f, "Hello World", &nbytes);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+    rv = apr_file_flush(f);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+    offset = 0;
+    rv = apr_file_seek(f, APR_SET, &offset);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+    rv = apr_file_read(f, buffer, &nbytes);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+    ABTS_INT_EQUAL(tc, sizeof(teststring), nbytes);
+    rv = apr_file_trunc(f, 5);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+
+    /* Test. Superficially, seek() seems to work ... */
+    offset = 0;
+    rv = apr_file_seek(f, APR_CUR, &offset);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+    ABTS_ASSERT(tc, "File pointer is not at EOF", offset == 5);
+
+    /* ... but the internal buffer state is inconsistent making it fail
+       further down the road. */
+    nbytes = 1;
+    rv = apr_file_write(f, "X", &nbytes);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+    offset = 0;
+    rv = apr_file_seek(f, APR_CUR, &offset);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+    ABTS_ASSERT(tc, "Buffered file pointer is off", offset == 6);
+
+    rv = apr_file_close(f);
+    ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+
+    /* Cleanup */
     rv = apr_file_remove(fname, p);
     ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
 }
]]]

Reply via email to