posix_spawn_file_actions_addopen.
https://sourceware.org/bugzilla/show_bug.cgi?id=17048
(bmoses, backport)
+
+libio/Makefile
+libio/iofdopen.c
+libio/fileops.c
+libio/tst-ftell-active-handler.c
+libio/tst-ftell-append.c
+libio/wfileops.c
+ For b/15017950, backport patch to fix offset computation for append+
+ mode on switching from read, and preceeding patches to fix another
+ issue with append files and ftell, and to adjust test environments.
+ https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=ea33158c96c53a64402a772186956c1f5cb556ae
+ https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=003e49ed5d2034d73bfcf5324c461785687b7e88
+ https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=2482ae433a4249495859343ae1fba408300f2c2e
+ https://sourceware.org/bugzilla/show_bug.cgi?id=16724
+ (bmoses, backport)
tst-wmemstream1 tst-wmemstream2 \
bug-memstream1 bug-wmemstream1 \
tst-setvbuf1 tst-popen1 tst-fgetwc bug-wsetpos tst-fseek \
- tst-fwrite-error tst-ftell-partial-wide tst-ftell-active-handler
+ tst-fwrite-error tst-ftell-partial-wide tst-ftell-active-handler \
+ tst-ftell-append
ifeq (yes,$(build-shared))
# Add test-fopenloc only if shared library is enabled since it depends on
# shared localedata objects.
bug-ftell-ENV = LOCPATH=$(common-objpfx)localedata
tst-fgetwc-ENV = LOCPATH=$(common-objpfx)localedata
tst-fseek-ENV = LOCPATH=$(common-objpfx)localedata
+tst-ftell-partial-wide-ENV = LOCPATH=$(common-objpfx)localedata
+tst-ftell-active-handler-ENV = LOCPATH=$(common-objpfx)localedata
+tst-ftell-append-ENV = LOCPATH=$(common-objpfx)localedata
generated = tst-fopenloc.mtrace tst-fopenloc.check
The position in the buffer that corresponds to the position
in external file system is normally _IO_read_end, except in putback
- mode, when it is _IO_save_end.
+ mode, when it is _IO_save_end and also when the file is in append mode,
+ since switching from read to write mode automatically sends the position in
+ the external file system to the end of file.
If the field _fb._offset is >= 0, it gives the offset in
the file as a whole corresponding to eGptr(). (?)
return NULL;
fp->_fileno = fdesc;
_IO_mask_flags (fp, read_write,_IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING);
- if ((read_write & _IO_IS_APPENDING) && (read_write & _IO_NO_READS))
- if (_IO_SEEKOFF (fp, (_IO_off64_t)0, _IO_seek_end, _IOS_INPUT|_IOS_OUTPUT)
- == _IO_pos_BAD && errno != ESPIPE)
- {
- close_not_cancel (fdesc);
- return NULL;
- }
+ /* For append mode, send the file offset to the end of the file. Don't
+ update the offset cache though, since the file handle is not active. */
+ if ((read_write & (_IO_IS_APPENDING | _IO_NO_READS))
+ == (_IO_IS_APPENDING | _IO_NO_READS))
+ {
+ _IO_off64_t new_pos = _IO_SYSSEEK (fp, 0, _IO_seek_end);
+ if (new_pos == _IO_pos_BAD && errno != ESPIPE)
+ {
+ close_not_cancel (fdesc);
+ return NULL;
+ }
+ }
_IO_link_in ((struct _IO_FILE_plus *) fp);
return fp;
}
return 0;
}
-/* Get the current file offset using a system call. This is the safest method
- to get the current file offset, since we are sure that we get the current
- state of the file. Before the stream handle is activated (by using fread,
- fwrite, etc.), an application may alter the state of the file descriptor
- underlying it by calling read/write/lseek on it. Using a cached offset at
- this point will result in returning the incorrect value. Same is the case
- when one switches from reading in a+ mode to writing, where the buffer has
- not been flushed - the cached offset would reflect the reading position
- while the actual write position would be at the end of the file.
-
- do_ftell and do_ftell_wide may resort to using the cached offset in some
- special cases instead of calling get_file_offset, but those cases should be
- thoroughly described. */
-_IO_off64_t
-get_file_offset (_IO_FILE *fp)
-{
- if ((fp->_flags & _IO_IS_APPENDING) == _IO_IS_APPENDING)
- {
- struct stat64 st;
- bool ret = (_IO_SYSSTAT (fp, &st) == 0 && S_ISREG (st.st_mode));
- if (ret)
- return st.st_size;
- else
- return EOF;
- }
- else
- return _IO_SYSSEEK (fp, 0, _IO_seek_cur);
-}
-
-
-/* ftell{,o} implementation. Don't modify any state of the file pointer while
- we try to get the current state of the stream. */
+/* ftell{,o} implementation. The only time we modify the state of the stream
+ is when we have unflushed writes. In that case we seek to the end and
+ record that offset in the stream object. */
static _IO_off64_t
do_ftell (_IO_FILE *fp)
{
- _IO_off64_t result = 0;
- bool use_cached_offset = false;
+ _IO_off64_t result, offset = 0;
/* No point looking at unflushed data if we haven't allocated buffers
yet. */
bool was_writing = (fp->_IO_write_ptr > fp->_IO_write_base
|| _IO_in_put_mode (fp));
+ bool append_mode = (fp->_flags & _IO_IS_APPENDING) == _IO_IS_APPENDING;
+
+ /* When we have unflushed writes in append mode, seek to the end of the
+ file and record that offset. This is the only time we change the file
+ stream state and it is safe since the file handle is active. */
+ if (was_writing && append_mode)
+ {
+ result = _IO_SYSSEEK (fp, 0, _IO_seek_end);
+ if (result == _IO_pos_BAD)
+ return EOF;
+ else
+ fp->_offset = result;
+ }
+
/* Adjust for unflushed data. */
if (!was_writing)
- result -= fp->_IO_read_end - fp->_IO_read_ptr;
+ offset -= fp->_IO_read_end - fp->_IO_read_ptr;
+ /* We don't trust _IO_read_end to represent the current file offset when
+ writing in append mode because the value would have to be shifted to
+ the end of the file during a flush. Use the write base instead, along
+ with the new offset we got above when we did a seek to the end of the
+ file. */
+ else if (append_mode)
+ offset += fp->_IO_write_ptr - fp->_IO_write_base;
+ /* For all other modes, _IO_read_end represents the file offset. */
else
- result += fp->_IO_write_ptr - fp->_IO_read_end;
-
- /* It is safe to use the cached offset when available if there is
- unbuffered data (indicating that the file handle is active) and the
- handle is not for a file open in a+ mode. The latter condition is
- because there could be a scenario where there is a switch from read
- mode to write mode using an fseek to an arbitrary position. In this
- case, there would be unbuffered data due to be appended to the end of
- the file, but the offset may not necessarily be the end of the
- file. It is fine to use the cached offset when the a+ stream is in
- read mode though, since the offset is maintained correctly in that
- case. Note that this is not a comprehensive set of cases when the
- offset is reliable. The offset may be reliable even in some cases
- where there is no unflushed input and the handle is active, but it's
- just that we don't have a way to identify that condition reliably. */
- use_cached_offset = (result != 0 && fp->_offset != _IO_pos_BAD
- && ((fp->_flags & (_IO_IS_APPENDING | _IO_NO_READS))
- == (_IO_IS_APPENDING | _IO_NO_READS)
- && was_writing));
+ offset += fp->_IO_write_ptr - fp->_IO_read_end;
}
- if (use_cached_offset)
- result += fp->_offset;
+ if (fp->_offset != _IO_pos_BAD)
+ result = fp->_offset;
else
- result += get_file_offset (fp);
+ result = _IO_SYSSEEK (fp, 0, _IO_seek_cur);
if (result == EOF)
return result;
+ result += offset;
+
if (result < 0)
{
__set_errno (EINVAL);
return result;
}
-
_IO_off64_t
_IO_new_file_seekoff (fp, offset, dir, mode)
_IO_FILE *fp;
_IO_mask_flags (&new_f->fp.file, read_write,
_IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING);
+ /* For append mode, set the file offset to the end of the file. Don't
+ update the offset cache though, since the file handle is not active. */
+ if ((read_write & (_IO_IS_APPENDING | _IO_NO_READS))
+ == (_IO_IS_APPENDING | _IO_NO_READS))
+ {
+ _IO_off64_t new_pos = _IO_SYSSEEK (&new_f->fp.file, 0, _IO_seek_end);
+ if (new_pos == _IO_pos_BAD && errno != ESPIPE)
+ return NULL;
+ }
return &new_f->fp.file;
}
libc_hidden_ver (_IO_new_fdopen, _IO_fdopen)
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
{"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++)
{
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
ret |= do_ftell_test (filename);
ret |= do_write_test (filename);
ret |= do_append_test (filename);
+ ret |= do_rewind_test (filename);
return ret;
}
--- /dev/null
+/* Verify that ftell returns the correct value after a read and a write on a
+ file opened in a+ mode.
+ Copyright (C) 2014 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <locale.h>
+#include <wchar.h>
+
+/* data points to either char_data or wide_data, depending on whether we're
+ testing regular file mode or wide mode respectively. Similarly,
+ fputs_func points to either fputs or fputws. data_len keeps track of the
+ length of the current data and file_len maintains the current file
+ length. */
+#define BUF_LEN 4
+static void *buf;
+static char char_buf[BUF_LEN];
+static wchar_t wide_buf[BUF_LEN];
+static const void *data;
+static const char *char_data = "abcdefghijklmnopqrstuvwxyz";
+static const wchar_t *wide_data = L"abcdefghijklmnopqrstuvwxyz";
+static size_t data_len;
+static size_t file_len;
+
+typedef int (*fputs_func_t) (const void *data, FILE *fp);
+fputs_func_t fputs_func;
+
+typedef void *(*fgets_func_t) (void *s, int size, FILE *stream);
+fgets_func_t fgets_func;
+
+static int do_test (void);
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
+
+static FILE *
+init_file (const char *filename)
+{
+ FILE *fp = fopen (filename, "w");
+ if (fp == NULL)
+ {
+ printf ("fopen: %m\n");
+ return NULL;
+ }
+
+ int written = fputs_func (data, fp);
+
+ if (written == EOF)
+ {
+ printf ("fputs failed to write data\n");
+ fclose (fp);
+ return NULL;
+ }
+
+ file_len = data_len;
+
+ fclose (fp);
+
+ fp = fopen (filename, "a+");
+ if (fp == NULL)
+ {
+ printf ("fopen(a+): %m\n");
+ return NULL;
+ }
+
+ return fp;
+}
+
+static int
+do_one_test (const char *filename)
+{
+ FILE *fp = init_file (filename);
+
+ if (fp == NULL)
+ return 1;
+
+ void *ret = fgets_func (buf, BUF_LEN, fp);
+
+ if (ret == NULL)
+ {
+ printf ("read failed: %m\n");
+ fclose (fp);
+ return 1;
+ }
+
+ int written = fputs_func (data, fp);
+
+ if (written == EOF)
+ {
+ printf ("fputs failed to write data\n");
+ fclose (fp);
+ return 1;
+ }
+
+ file_len += data_len;
+
+ long off = ftell (fp);
+
+ if (off != file_len)
+ {
+ printf ("Incorrect offset %ld, expected %zu\n", off, file_len);
+ fclose (fp);
+ return 1;
+ }
+ else
+ printf ("Correct offset %ld after write.\n", off);
+
+ return 0;
+}
+
+/* Run the tests for regular files and wide mode files. */
+static int
+do_test (void)
+{
+ int ret = 0;
+ char *filename;
+ int fd = create_temp_file ("tst-ftell-append-tmp.", &filename);
+
+ if (fd == -1)
+ {
+ printf ("create_temp_file: %m\n");
+ return 1;
+ }
+
+ close (fd);
+
+ /* Tests for regular files. */
+ puts ("Regular mode:");
+ fputs_func = (fputs_func_t) fputs;
+ fgets_func = (fgets_func_t) fgets;
+ data = char_data;
+ buf = char_buf;
+ data_len = strlen (char_data);
+ ret |= do_one_test (filename);
+
+ /* Tests for wide files. */
+ puts ("Wide mode:");
+ if (setlocale (LC_ALL, "en_US.UTF-8") == NULL)
+ {
+ printf ("Cannot set en_US.UTF-8 locale.\n");
+ return 1;
+ }
+ fputs_func = (fputs_func_t) fputws;
+ fgets_func = (fgets_func_t) fgetws;
+ data = wide_data;
+ buf = wide_buf;
+ data_len = wcslen (wide_data);
+ ret |= do_one_test (filename);
+
+ return ret;
+}
}
/* ftell{,o} implementation for wide mode. Don't modify any state of the file
- pointer while we try to get the current state of the stream. */
+ pointer while we try to get the current state of the stream except in one
+ case, which is when we have unflushed writes in append mode. */
static _IO_off64_t
do_ftell_wide (_IO_FILE *fp)
{
_IO_off64_t result, offset = 0;
- bool use_cached_offset = false;
/* No point looking for offsets in the buffer if it hasn't even been
allocated. */
> fp->_wide_data->_IO_write_base)
|| _IO_in_put_mode (fp));
+ bool append_mode = (fp->_flags & _IO_IS_APPENDING) == _IO_IS_APPENDING;
+
+ /* When we have unflushed writes in append mode, seek to the end of the
+ file and record that offset. This is the only time we change the file
+ stream state and it is safe since the file handle is active. */
+ if (was_writing && append_mode)
+ {
+ result = _IO_SYSSEEK (fp, 0, _IO_seek_end);
+ if (result == _IO_pos_BAD)
+ return EOF;
+ else
+ fp->_offset = result;
+ }
+
/* XXX For wide stream with backup store it is not very
reasonable to determine the offset. The pushed-back
character might require a state change and we need not be
offset += outstop - out;
}
- /* _IO_read_end coincides with fp._offset, so the actual file
- position is fp._offset - (_IO_read_end - new_write_ptr). */
- offset -= fp->_IO_read_end - fp->_IO_write_ptr;
+ /* We don't trust _IO_read_end to represent the current file offset
+ when writing in append mode because the value would have to be
+ shifted to the end of the file during a flush. Use the write base
+ instead, along with the new offset we got above when we did a seek
+ to the end of the file. */
+ if (append_mode)
+ offset += fp->_IO_write_ptr - fp->_IO_write_base;
+ /* For all other modes, _IO_read_end represents the file offset. */
+ else
+ offset += fp->_IO_write_ptr - fp->_IO_read_end;
}
-
- /* It is safe to use the cached offset when available if there is
- unbuffered data (indicating that the file handle is active) and
- the handle is not for a file open in a+ mode. The latter
- condition is because there could be a scenario where there is a
- switch from read mode to write mode using an fseek to an arbitrary
- position. In this case, there would be unbuffered data due to be
- appended to the end of the file, but the offset may not
- necessarily be the end of the file. It is fine to use the cached
- offset when the a+ stream is in read mode though, since the offset
- is maintained correctly in that case. Note that this is not a
- comprehensive set of cases when the offset is reliable. The
- offset may be reliable even in some cases where there is no
- unflushed input and the handle is active, but it's just that we
- don't have a way to identify that condition reliably. */
- use_cached_offset = (offset != 0 && fp->_offset != _IO_pos_BAD
- && ((fp->_flags & (_IO_IS_APPENDING | _IO_NO_READS))
- == (_IO_IS_APPENDING | _IO_NO_READS)
- && was_writing));
}
- if (use_cached_offset)
+ if (fp->_offset != _IO_pos_BAD)
result = fp->_offset;
else
- result = get_file_offset (fp);
+ result = _IO_SYSSEEK (fp, 0, _IO_seek_cur);
if (result == EOF)
return result;
result += offset;
+ if (result < 0)
+ {
+ __set_errno (EINVAL);
+ return EOF;
+ }
+
return result;
}