summaryrefslogtreecommitdiff
path: root/libio/tst-ftell-active-handler.c
diff options
context:
space:
mode:
authorSiddhesh Poyarekar <siddhesh@redhat.com>2014-03-11 17:04:49 +0530
committerSiddhesh Poyarekar <siddhesh@redhat.com>2014-03-17 21:23:56 +0530
commitea33158c96c53a64402a772186956c1f5cb556ae (patch)
tree038673c1ab8849eb3311aa1f8d66c23ee654cf42 /libio/tst-ftell-active-handler.c
parentb1dbb426e164ad1236c2c76268e03fec5c7a7bbe (diff)
Fix offset caching for streams and use it for ftell (BZ #16680)
The ftell implementation was made conservative to ensure that incorrectly cached offsets never affect it. However, this causes problems for append mode when a file stream is rewound. Additionally, the 'clever' trick of using stat to get position for append mode files caused more problems than it solved and broke old behavior. I have described the various problems that it caused and then finally the solution. For a and a+ mode files, rewinding the stream should result in ftell returning 0 as the offset, but the stat() trick caused it to (incorrectly) always return the end of file. Now I couldn't find anything in POSIX that specifies the stream position after rewind() for a file opened in 'a' mode, but for 'a+' mode it should be set to 0. For 'a' mode too, it probably makes sense to keep it set to 0 in the interest of retaining old behavior. The initial file position for append mode files is implementation defined, so the implementation could either retain the current file position or move the position to the end of file. The earlier ftell implementation would move the offset to end of file for append-only mode, but retain the old offset for a+ mode. It would also cache the offset (this detail is important). My patch broke this and would set the initial position to end of file for both append modes, thus breaking old behavior. I was ignorant enough to write an incorrect test case for it too. The Change: I have now brought back the behavior of seeking to end of file for append-only streams, but with a slight difference. I don't cache the offset though, since we would want ftell to query the current file position through lseek while the stream is not active. Since the offset is moved to the end of file, we can rely on the file position reported by lseek and we don't need to resort to the stat() nonsense. Finally, the cache is always reliable, except when there are unflished writes in an append mode stream (i.e. both a and a+). In the latter case, it is safe to just do an lseek to SEEK_END. The value can be safely cached too, since the file handle is already active at this point. Incidentally, this is the only state change we affect in the file handle (apart from taking locks of course). I have also updated the test case to correct my impression of the initial file position for a+ streams to the initial behavior. I have verified that this does not break any existing tests in the testsuite and also passes with the new tests.
Diffstat (limited to 'libio/tst-ftell-active-handler.c')
-rw-r--r--libio/tst-ftell-active-handler.c112
1 files changed, 108 insertions, 4 deletions
diff --git a/libio/tst-ftell-active-handler.c b/libio/tst-ftell-active-handler.c
index 5d5fc26547..40ca58c9e2 100644
--- a/libio/tst-ftell-active-handler.c
+++ b/libio/tst-ftell-active-handler.c
@@ -88,6 +88,107 @@ static size_t file_len;
typedef int (*fputs_func_t) (const void *data, FILE *fp);
fputs_func_t fputs_func;
+/* Test that ftell output after a rewind is correct. */
+static int
+do_rewind_test (const char *filename)
+{
+ int ret = 0;
+ struct test
+ {
+ const char *mode;
+ int fd_mode;
+ size_t old_off;
+ size_t new_off;
+ } test_modes[] = {
+ {"w", O_WRONLY, 0, data_len},
+ {"w+", O_RDWR, 0, data_len},
+ {"r+", O_RDWR, 0, data_len},
+ /* The new offsets for 'a' and 'a+' modes have to factor in the
+ previous writes since they always append to the end of the
+ file. */
+ {"a", O_WRONLY, 0, 3 * data_len},
+ {"a+", O_RDWR, 0, 4 * data_len},
+ };
+
+ /* Empty the file before the test so that our offsets are simple to
+ calculate. */
+ FILE *fp = fopen (filename, "w");
+ if (fp == NULL)
+ {
+ printf ("Failed to open file for emptying\n");
+ return 1;
+ }
+ fclose (fp);
+
+ for (int j = 0; j < 2; j++)
+ {
+ for (int i = 0; i < sizeof (test_modes) / sizeof (struct test); i++)
+ {
+ FILE *fp;
+ int fd;
+ int fileret;
+
+ printf ("\trewind: %s (file, \"%s\"): ", j == 0 ? "fdopen" : "fopen",
+ test_modes[i].mode);
+
+ if (j == 0)
+ fileret = get_handles_fdopen (filename, fd, fp,
+ test_modes[i].fd_mode,
+ test_modes[i].mode);
+ else
+ fileret = get_handles_fopen (filename, fd, fp, test_modes[i].mode);
+
+ if (fileret != 0)
+ return fileret;
+
+ /* Write some content to the file, rewind and ensure that the ftell
+ output after the rewind is 0. POSIX does not specify what the
+ behavior is when a file is rewound in 'a' mode, so we retain
+ current behavior, which is to keep the 0 offset. */
+ size_t written = fputs_func (data, fp);
+
+ if (written == EOF)
+ {
+ printf ("fputs[1] failed to write data\n");
+ ret |= 1;
+ }
+
+ rewind (fp);
+ long offset = ftell (fp);
+
+ if (offset != test_modes[i].old_off)
+ {
+ printf ("Incorrect old offset. Expected %zu, but got %ld, ",
+ test_modes[i].old_off, offset);
+ ret |= 1;
+ }
+ else
+ printf ("old offset = %ld, ", offset);
+
+ written = fputs_func (data, fp);
+
+ if (written == EOF)
+ {
+ printf ("fputs[1] failed to write data\n");
+ ret |= 1;
+ }
+
+ /* After this write, the offset in append modes should factor in the
+ implicit lseek to the end of file. */
+ offset = ftell (fp);
+ if (offset != test_modes[i].new_off)
+ {
+ printf ("Incorrect new offset. Expected %zu, but got %ld\n",
+ test_modes[i].new_off, offset);
+ ret |= 1;
+ }
+ else
+ printf ("new offset = %ld\n", offset);
+ }
+ }
+ return ret;
+}
+
/* Test that the value of ftell is not cached when the stream handle is not
active. */
static int
@@ -107,11 +208,13 @@ do_ftell_test (const char *filename)
{"w", O_WRONLY, 0, data_len},
{"w+", O_RDWR, 0, data_len},
{"r+", O_RDWR, 0, data_len},
- /* For 'a' and 'a+' modes, the initial file position should be the
+ /* For the 'a' mode, the initial file position should be the
current end of file. After the write, the offset has data_len
- added to the old value. */
+ added to the old value. For a+ mode however, the initial file
+ position is the file position of the underlying file descriptor,
+ since it is initially assumed to be in read mode. */
{"a", O_WRONLY, data_len, 2 * data_len},
- {"a+", O_RDWR, 2 * data_len, 3 * data_len},
+ {"a+", O_RDWR, 0, 3 * data_len},
};
for (int j = 0; j < 2; j++)
{
@@ -157,7 +260,7 @@ do_ftell_test (const char *filename)
if (off != test_modes[i].new_off)
{
printf ("Incorrect new offset. Expected %zu but got %ld\n",
- test_modes[i].old_off, off);
+ test_modes[i].new_off, off);
ret |= 1;
}
else
@@ -322,6 +425,7 @@ do_one_test (const char *filename)
ret |= do_ftell_test (filename);
ret |= do_write_test (filename);
ret |= do_append_test (filename);
+ ret |= do_rewind_test (filename);
return ret;
}