Hi everyone, This patch fixes two issues with apr_file_trunc() for buffered files:
- The Win32 implementation incorrectly flushes the buffered writes _after_ truncating a file. Such files will have unexpected data written after the position at which they should've been truncated. (Under Unix, this issue has been fixed in https://svn.apache.org/r1044440) - Both Win32 and Unix implementations incorrectly keep the data read into a buffer after the file is truncated. Thus, reading from a file after apr_file_trunc() can return invalid data from the previous file offset. Log message: [[[ Fix two issues with apr_file_trunc() for buffered files: - The Win32 implementation incorrectly flushes the buffered writes _after_ truncating a file. Such files will have unexpected data written after the position at which they should've been truncated. (Under Unix, this issue has been fixed in https://svn.apache.org/r1044440) - Both Win32 and Unix implementations incorrectly keep the data read into a buffer after the file is truncated. Thus, reading from a file after apr_file_trunc() can return invalid data from the previous file offset. * file_io/win32/seek.c (apr_file_trunc): Flush the write buffer or discard the read buffer before truncating. Propely update the internal file offset (filePtr) and the eof_hit marker. * file_io/unix/seek.c (apr_file_trunc): Discard the read buffer before truncating. * test/testfile.c (test_file_trunc): Extend the checks. Factor out part of this test... (test_file_trunc_buffered_write): ...into this new test. (test_file_trunc_buffered_write2, test_file_trunc_buffered_read): New tests. (testfile): Run the new tests. Patch by: Evgeny Kotkov <evgeny.kotkov {at} visualsvn.com> ]]] Regards, Evgeny Kotkov
Index: file_io/unix/seek.c =================================================================== --- file_io/unix/seek.c (revision 1784526) +++ file_io/unix/seek.c (working copy) @@ -117,6 +117,13 @@ apr_status_t apr_file_trunc(apr_file_t *fp, apr_of /* Reset buffer positions for write mode */ fp->bufpos = fp->direction = fp->dataRead = 0; } + else if (fp->direction == 0) { + /* Discard the read buffer, as we are about to reposition + * ourselves to the end of file. + */ + fp->bufpos = 0; + fp->dataRead = 0; + } file_unlock(fp); if (rc) { return rc; Index: file_io/win32/seek.c =================================================================== --- file_io/win32/seek.c (revision 1784526) +++ file_io/win32/seek.c (working copy) @@ -161,17 +161,43 @@ APR_DECLARE(apr_status_t) apr_file_trunc(apr_file_ LONG offhi = (LONG)(offset >> 32); DWORD rc; + if (thefile->buffered) { + if (thefile->direction == 1) { + /* Figure out what needs to be flushed. Don't flush the part + * of the write buffer that will get truncated anyway. + */ + if (offset < thefile->filePtr) { + thefile->bufpos = 0; + } + else if (offset < thefile->filePtr + (apr_off_t)thefile->bufpos) { + thefile->bufpos = offset - thefile->filePtr; + } + + if (thefile->bufpos != 0) { + rv = apr_file_flush(thefile); + if (rv != APR_SUCCESS) + return rv; + } + } + else if (thefile->direction == 0) { + /* Discard the read buffer, as we are about to reposition + * ourselves to the end of file. + */ + thefile->bufpos = 0; + thefile->dataRead = 0; + } + } + rc = SetFilePointer(thefile->filehand, offlo, &offhi, FILE_BEGIN); if (rc == 0xFFFFFFFF) if ((rv = apr_get_os_error()) != APR_SUCCESS) return rv; + thefile->filePtr = offset; + /* Don't report EOF until the next read. */ + thefile->eof_hit = 0; if (!SetEndOfFile(thefile->filehand)) return apr_get_os_error(); - if (thefile->buffered) { - return setptr(thefile, offset); - } - return APR_SUCCESS; } Index: test/testfile.c =================================================================== --- test/testfile.c (revision 1784526) +++ test/testfile.c (working copy) @@ -822,10 +822,10 @@ static void test_file_trunc(abts_case *tc, void *d const char *s; apr_size_t nbytes; apr_finfo_t finfo; + char c; apr_file_remove(fname, p); - /* Test unbuffered */ rv = apr_file_open(&f, fname, APR_FOPEN_CREATE | APR_FOPEN_READ | APR_FOPEN_WRITE, @@ -839,15 +839,40 @@ static void test_file_trunc(abts_case *tc, void *d ABTS_SIZE_EQUAL(tc, strlen(s), nbytes); rv = apr_file_trunc(f, 4); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); + /* Test apr_file_info_get(). */ + rv = apr_file_info_get(&finfo, APR_FINFO_SIZE, f); + ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); + ABTS_INT_EQUAL(tc, 4, (int)finfo.size); + /* EOF is not reported until the next read. */ + rv = apr_file_eof(f); + ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); + rv = apr_file_getc(&c, f); + ABTS_INT_EQUAL(tc, APR_EOF, rv); + rv = apr_file_eof(f); + ABTS_INT_EQUAL(tc, APR_EOF, rv); + rv = apr_file_close(f); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); rv = apr_stat(&finfo, fname, APR_FINFO_SIZE, p); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); - ABTS_ASSERT(tc, "File size mismatch, expected 4", finfo.size == 4); + ABTS_INT_EQUAL(tc, 4, (int)finfo.size); rv = apr_file_remove(fname, p); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); - /* Test buffered */ +} + +static void test_file_trunc_buffered_write(abts_case *tc, void *data) +{ + apr_status_t rv; + apr_file_t *f; + const char *fname = "data/testtruncate_buffered_write.dat"; + const char *s; + apr_size_t nbytes; + apr_finfo_t finfo; + char c; + + apr_file_remove(fname, p); + rv = apr_file_open(&f, fname, APR_FOPEN_CREATE | APR_FOPEN_READ | APR_FOPEN_WRITE | APR_FOPEN_BUFFERED, @@ -854,6 +879,7 @@ static void test_file_trunc(abts_case *tc, void *d APR_FPROT_UREAD | APR_FPROT_UWRITE, p); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); + s = "some data"; nbytes = strlen(s); rv = apr_file_write(f, s, &nbytes); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); @@ -860,16 +886,125 @@ static void test_file_trunc(abts_case *tc, void *d ABTS_SIZE_EQUAL(tc, strlen(s), nbytes); rv = apr_file_trunc(f, 4); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); + /* Test apr_file_info_get(). */ + rv = apr_file_info_get(&finfo, APR_FINFO_SIZE, f); + ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); + ABTS_INT_EQUAL(tc, 4, (int)finfo.size); + /* EOF is not reported until the next read. */ + rv = apr_file_eof(f); + ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); + rv = apr_file_getc(&c, f); + ABTS_INT_EQUAL(tc, APR_EOF, rv); + rv = apr_file_eof(f); + ABTS_INT_EQUAL(tc, APR_EOF, rv); + rv = apr_file_close(f); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); rv = apr_stat(&finfo, fname, APR_FINFO_SIZE, p); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); - ABTS_ASSERT(tc, "File size mismatch, expected 4", finfo.size == 4); + ABTS_INT_EQUAL(tc, 4, (int)finfo.size); rv = apr_file_remove(fname, p); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); } +static void test_file_trunc_buffered_write2(abts_case *tc, void *data) +{ + apr_status_t rv; + apr_file_t *f; + const char *fname = "data/testtruncate_buffered_write2.dat"; + apr_finfo_t finfo; + char c; + + apr_file_remove(fname, p); + + rv = apr_file_open(&f, fname, + APR_FOPEN_CREATE | APR_FOPEN_READ | + APR_FOPEN_WRITE | APR_FOPEN_BUFFERED, + APR_FPROT_OS_DEFAULT, p); + APR_ASSERT_SUCCESS(tc, "open test file", rv); + + rv = apr_file_puts("abc", f); + APR_ASSERT_SUCCESS(tc, "write first string", rv); + rv = apr_file_flush(f); + APR_ASSERT_SUCCESS(tc, "flush", rv); + rv = apr_file_puts("def", f); + APR_ASSERT_SUCCESS(tc, "write second string", rv); + /* Truncate behind the write buffer. */ + rv = apr_file_trunc(f, 2); + APR_ASSERT_SUCCESS(tc, "truncate the file", rv); + /* Test apr_file_info_get(). */ + rv = apr_file_info_get(&finfo, APR_FINFO_SIZE, f); + APR_ASSERT_SUCCESS(tc, "get file info", rv); + ABTS_INT_EQUAL(tc, 2, (int)finfo.size); + /* EOF is not reported until the next read. */ + rv = apr_file_eof(f); + ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); + rv = apr_file_getc(&c, f); + ABTS_INT_EQUAL(tc, APR_EOF, rv); + rv = apr_file_eof(f); + ABTS_INT_EQUAL(tc, APR_EOF, rv); + + apr_file_close(f); + + rv = apr_stat(&finfo, fname, APR_FINFO_SIZE, p); + APR_ASSERT_SUCCESS(tc, "stat file", rv); + ABTS_INT_EQUAL(tc, 2, (int)finfo.size); + + apr_file_remove(fname, p); +} + +static void test_file_trunc_buffered_read(abts_case *tc, void *data) +{ + apr_status_t rv; + apr_file_t *f; + const char *fname = "data/testtruncate_buffered_read.dat"; + apr_finfo_t finfo; + char c; + + apr_file_remove(fname, p); + + rv = apr_file_open(&f, fname, + APR_FOPEN_CREATE | APR_FOPEN_READ | + APR_FOPEN_WRITE, APR_FPROT_OS_DEFAULT, p); + APR_ASSERT_SUCCESS(tc, "open test file", rv); + + rv = apr_file_puts("abc", f); + APR_ASSERT_SUCCESS(tc, "write test data", rv); + apr_file_close(f); + + rv = apr_file_open(&f, fname, + APR_FOPEN_READ | APR_FOPEN_WRITE | + APR_FOPEN_BUFFERED, APR_FPROT_OS_DEFAULT, p); + APR_ASSERT_SUCCESS(tc, "re-open test file", rv); + + /* Read to fill in the buffer. */ + rv = apr_file_getc(&c, f); + APR_ASSERT_SUCCESS(tc, "read char", rv); + /* Truncate the file. */ + rv = apr_file_trunc(f, 1); + APR_ASSERT_SUCCESS(tc, "truncate the file", rv); + /* Test apr_file_info_get(). */ + rv = apr_file_info_get(&finfo, APR_FINFO_SIZE, f); + APR_ASSERT_SUCCESS(tc, "get file info", rv); + ABTS_INT_EQUAL(tc, 1, (int)finfo.size); + /* EOF is not reported until the next read. */ + rv = apr_file_eof(f); + ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); + rv = apr_file_getc(&c, f); + ABTS_INT_EQUAL(tc, APR_EOF, rv); + rv = apr_file_eof(f); + ABTS_INT_EQUAL(tc, APR_EOF, rv); + + apr_file_close(f); + + rv = apr_stat(&finfo, fname, APR_FINFO_SIZE, p); + APR_ASSERT_SUCCESS(tc, "stat file", rv); + ABTS_INT_EQUAL(tc, 1, (int)finfo.size); + + apr_file_remove(fname, p); +} + static void test_bigfprintf(abts_case *tc, void *data) { apr_file_t *f; @@ -1134,6 +1269,9 @@ abts_suite *testfile(abts_suite *suite) abts_run_test(suite, test_mod_neg, NULL); abts_run_test(suite, test_truncate, NULL); abts_run_test(suite, test_file_trunc, NULL); + abts_run_test(suite, test_file_trunc_buffered_write, NULL); + abts_run_test(suite, test_file_trunc_buffered_write2, NULL); + abts_run_test(suite, test_file_trunc_buffered_read, NULL); abts_run_test(suite, test_bigfprintf, NULL); abts_run_test(suite, test_fail_write_flush, NULL); abts_run_test(suite, test_fail_read_flush, NULL);