@@ -60,7 +60,9 @@ tests = tst_swprintf tst_wprintf tst_swscanf tst_wscanf tst_getwc tst_putwc \
tst-mmap2-eofsync tst-mmap-offend bug-fopena+ bug-wfflush \
bug-ungetc2 bug-ftell bug-ungetc3 bug-ungetc4 tst-fopenloc2 \
tst-memstream1 tst-memstream2 tst-memstream3 tst-memstream4 \
+ tst-memstream5 \
tst-wmemstream1 tst-wmemstream2 tst-wmemstream3 tst-wmemstream4 \
+ tst-wmemstream5 \
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 \
@@ -26,11 +26,15 @@ struct _IO_FILE_memstream
_IO_strfile _sf;
char **bufloc;
size_t *sizeloc;
+ char *prevwriteptr;
+ char *seekwriteptr;
};
static int _IO_mem_sync (FILE* fp) __THROW;
static void _IO_mem_finish (FILE* fp, int) __THROW;
+static off64_t _IO_mem_seekoff (FILE *fp, off64_t offset,
+ int dir, int mode) __THROW;
static const struct _IO_jump_t _IO_mem_jumps libio_vtable =
@@ -43,7 +47,7 @@ static const struct _IO_jump_t _IO_mem_jumps libio_vtable =
JUMP_INIT (pbackfail, _IO_str_pbackfail),
JUMP_INIT (xsputn, _IO_default_xsputn),
JUMP_INIT (xsgetn, _IO_default_xsgetn),
- JUMP_INIT (seekoff, _IO_str_seekoff),
+ JUMP_INIT (seekoff, _IO_mem_seekoff),
JUMP_INIT (seekpos, _IO_default_seekpos),
JUMP_INIT (setbuf, _IO_default_setbuf),
JUMP_INIT (sync, _IO_mem_sync),
@@ -96,6 +100,26 @@ __open_memstream (char **bufloc, size_t *sizeloc)
new_f->fp.bufloc = bufloc;
new_f->fp.sizeloc = sizeloc;
+ /* To correctly report the buffer size the implementation must track both
+ the buffer size and currently bytes are written. However _IO_write_ptr
+ is updated on both write and seek operations (since some _IO_* function
+ access the pointer directly to optimize updates). So to track current
+ written bytes two fields are used:
+
+ - prevwriteptr: track previous _IO_write_ptr before a seek operation on
+ the stream.
+ - seekwriteptr: track resulted _IO_write_ptr after a seek operation on
+ the stream.
+
+ Also, prevwriteptr is only updated iff _IO_write_ptr changed over calls
+ (meaning that a write operation happened).
+
+ So final buffer size is based on current _IO_write_ptr only if
+ its value is different than seekwriteptr. Otherwise it uses the old
+ _IO_write_ptr value before seek operation (prevwriteptr). */
+ new_f->fp.prevwriteptr = new_f->fp.seekwriteptr =
+ new_f->fp._sf._sbf._f._IO_write_ptr;
+
/* Disable single thread optimization. BZ 21735. */
new_f->fp._sf._sbf._f._flags2 |= _IO_FLAGS2_NEED_LOCK;
@@ -104,6 +128,21 @@ __open_memstream (char **bufloc, size_t *sizeloc)
libc_hidden_def (__open_memstream)
weak_alias (__open_memstream, open_memstream)
+/* Update FP with SIZE number of bytes written and return true if a write
+ operation has occurred. */
+static bool
+update_bufsize (const FILE *fp, size_t *size)
+{
+ const struct _IO_FILE_memstream *mp =
+ (const struct _IO_FILE_memstream *) fp;
+ if (fp->_IO_write_ptr == mp->seekwriteptr)
+ {
+ *size = mp->prevwriteptr - fp->_IO_write_base;
+ return false;
+ }
+ *size = fp->_IO_write_ptr - fp->_IO_write_base;
+ return true;
+}
static int
_IO_mem_sync (FILE *fp)
@@ -117,7 +156,7 @@ _IO_mem_sync (FILE *fp)
}
*mp->bufloc = fp->_IO_write_base;
- *mp->sizeloc = fp->_IO_write_ptr - fp->_IO_write_base;
+ update_bufsize (fp, mp->sizeloc);
return 0;
}
@@ -132,11 +171,23 @@ _IO_mem_finish (FILE *fp, int dummy)
fp->_IO_write_ptr - fp->_IO_write_base + 1);
if (*mp->bufloc != NULL)
{
- (*mp->bufloc)[fp->_IO_write_ptr - fp->_IO_write_base] = '\0';
- *mp->sizeloc = fp->_IO_write_ptr - fp->_IO_write_base;
+ /* An '\0' should be appended iff a write operation ocurred. */
+ if (update_bufsize (fp, mp->sizeloc))
+ (*mp->bufloc)[*mp->sizeloc] = '\0';
fp->_IO_buf_base = NULL;
}
_IO_str_finish (fp, 0);
}
+
+static off64_t
+_IO_mem_seekoff (FILE *fp, off64_t offset, int dir, int mode)
+{
+ struct _IO_FILE_memstream *mp = (struct _IO_FILE_memstream *) fp;
+ if (fp->_IO_write_ptr != mp->seekwriteptr)
+ mp->prevwriteptr = fp->_IO_write_ptr;
+ off64_t ret = _IO_str_seekoff (fp, offset, dir, mode);
+ mp->seekwriteptr = fp->_IO_write_ptr;
+ return ret;
+}
@@ -126,15 +126,17 @@ do_test_bz20181 (void)
if (fflush (fp) != 0)
ERROR_RET1 ("fflush failed (errno = %d)\n", errno);
- /* Avoid truncating the buffer on close. */
+ /* fseek updates the internal buffer, but open_memstream should set the
+ size to smaller of the buffer size and number of bytes written. Since
+ it was written just character ('z') final size should be 1. */
if (fseek (fp, 3, SEEK_SET) != 0)
ERROR_RET1 ("fseek failed (errno = %d)\n", errno);
if (fclose (fp) != 0)
ERROR_RET1 ("fclose failed (errno = %d\n", errno);
- if (size != 3)
- ERROR_RET1 ("size != 3\n");
+ if (size != 1)
+ ERROR_RET1 ("size != 1 (got %zu)\n", size);
if (buf[0] != W('z')
|| buf[1] != W('b')
new file mode 100644
@@ -0,0 +1,73 @@
+/* Test for open_memstream BZ #15298.
+ Copyright (C) 2019 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 "tst-memstream.h"
+
+static void
+mcheck_abort (enum mcheck_status ev)
+{
+ FAIL_EXIT1 ("mecheck failed with status %d\n", (int) ev);
+}
+
+static int
+do_test (void)
+{
+ mcheck_pedantic (mcheck_abort);
+
+ {
+ CHAR_T *buf;
+ size_t size;
+
+ FILE *fp = OPEN_MEMSTREAM (&buf, &size);
+ TEST_VERIFY_EXIT (fp != NULL);
+
+ /* Move internal position but do not write any bytes. Final size should
+ be 0. */
+ TEST_VERIFY_EXIT (fseek (fp, 10, SEEK_SET) != -1);
+ TEST_VERIFY_EXIT (fseek (fp, 20, SEEK_CUR) != -1);
+ TEST_VERIFY_EXIT (fseek (fp, 30, SEEK_CUR) != -1);
+ TEST_VERIFY_EXIT (fflush (fp) != -1);
+ TEST_VERIFY (size == 0);
+
+ /* Now write some bytes and change internal position. Final size should
+ be based on written bytes. */
+ TEST_VERIFY_EXIT (fseek (fp, 0, SEEK_SET) != -1);
+ TEST_VERIFY_EXIT (FWRITE (W("abc"), 1, 3, fp) == 3);
+ TEST_VERIFY_EXIT (fseek (fp, 20, SEEK_CUR) != -1);
+ TEST_VERIFY_EXIT (fseek (fp, 30, SEEK_CUR) != -1);
+ TEST_VERIFY_EXIT (fflush (fp) != -1);
+ TEST_VERIFY (size == 3);
+
+ /* Finally set position, write some bytes and change position again.
+ Final size should be based again on write position. */
+ size_t offset = 2048;
+ TEST_VERIFY_EXIT (fseek (fp, offset, SEEK_SET) != -1);
+ TEST_VERIFY_EXIT (FWRITE (W("def"), 1, 3, fp) == 3);
+ TEST_VERIFY_EXIT (fseek (fp, 20, SEEK_CUR) != -1);
+ TEST_VERIFY_EXIT (fseek (fp, 20, SEEK_CUR) != -1);
+ TEST_VERIFY_EXIT (fflush (fp) != -1);
+ TEST_VERIFY (size == (offset + 3));
+
+ TEST_VERIFY_EXIT (fclose (fp) == 0);
+ free (buf);
+ }
+
+ return 0;
+}
+
+#include <support/test-driver.c>
new file mode 100644
@@ -0,0 +1,20 @@
+/* Test for open_wmemstream BZ #15298.
+ Copyright (C) 2019 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/>. */
+
+#define TEST_WCHAR
+#include <libio/tst-memstream4.c>
@@ -27,11 +27,15 @@ struct _IO_FILE_wmemstream
_IO_strfile _sf;
wchar_t **bufloc;
size_t *sizeloc;
+ wchar_t *prevwriteptr;
+ wchar_t *seekwriteptr;
};
static int _IO_wmem_sync (FILE* fp) __THROW;
static void _IO_wmem_finish (FILE* fp, int) __THROW;
+static off64_t _IO_wmem_seekoff (FILE *fp, off64_t offset,
+ int dir, int mode) __THROW;
static const struct _IO_jump_t _IO_wmem_jumps libio_vtable =
@@ -44,7 +48,7 @@ static const struct _IO_jump_t _IO_wmem_jumps libio_vtable =
JUMP_INIT (pbackfail, (_IO_pbackfail_t) _IO_wstr_pbackfail),
JUMP_INIT (xsputn, _IO_wdefault_xsputn),
JUMP_INIT (xsgetn, _IO_wdefault_xsgetn),
- JUMP_INIT (seekoff, _IO_wstr_seekoff),
+ JUMP_INIT (seekoff, _IO_wmem_seekoff),
JUMP_INIT (seekpos, _IO_default_seekpos),
JUMP_INIT (setbuf, _IO_default_setbuf),
JUMP_INIT (sync, _IO_wmem_sync),
@@ -98,12 +102,47 @@ open_wmemstream (wchar_t **bufloc, size_t *sizeloc)
new_f->fp.bufloc = bufloc;
new_f->fp.sizeloc = sizeloc;
+ /* To correctly report the buffer size the implementation must track both
+ the buffer size and currently bytes are written. However, _IO_write_ptr
+ is updated on both write and seek operations (since some _IO_* function
+ access the pointer directly to optimize updates). So to track current
+ written bytes two fields are used:
+
+ - prevwriteptr: track previous _IO_write_ptr before a seek operation on
+ the stream.
+ - seekwriteptr: track resulted _IO_write_ptr after a seek operation on
+ the stream.
+
+ Also, prevwriteptr is only updated iff _IO_write_ptr changed over calls
+ (meaning that a write operation happened).
+
+ So final buffer size is based on current _IO_write_ptr only if
+ its value is different than seekwriteptr. Otherwise, it uses the old
+ _IO_write_ptr value before seek operation (prevwriteptr). */
+ new_f->fp.prevwriteptr = new_f->fp.seekwriteptr =
+ new_f->fp._sf._sbf._f._wide_data->_IO_write_ptr;
+
/* Disable single thread optimization. BZ 21735. */
new_f->fp._sf._sbf._f._flags2 |= _IO_FLAGS2_NEED_LOCK;
return (FILE *) &new_f->fp._sf._sbf;
}
+/* Update FP with SIZE number of bytes written and return true if a write
+ operation has occurred. */
+static bool
+update_bufsize (const FILE *fp, size_t *size)
+{
+ const struct _IO_FILE_wmemstream *mp =
+ (const struct _IO_FILE_wmemstream *) fp;
+ if (fp->_wide_data->_IO_write_ptr == mp->seekwriteptr)
+ {
+ *size = mp->prevwriteptr - fp->_wide_data->_IO_write_base;
+ return false;
+ }
+ *size = fp->_wide_data->_IO_write_ptr - fp->_wide_data->_IO_write_base;
+ return true;
+}
static int
_IO_wmem_sync (FILE *fp)
@@ -117,13 +156,11 @@ _IO_wmem_sync (FILE *fp)
}
*mp->bufloc = fp->_wide_data->_IO_write_base;
- *mp->sizeloc = (fp->_wide_data->_IO_write_ptr
- - fp->_wide_data->_IO_write_base);
+ update_bufsize (fp, mp->sizeloc);
return 0;
}
-
static void
_IO_wmem_finish (FILE *fp, int dummy)
{
@@ -135,13 +172,22 @@ _IO_wmem_finish (FILE *fp, int dummy)
* sizeof (wchar_t));
if (*mp->bufloc != NULL)
{
- size_t len = (fp->_wide_data->_IO_write_ptr
- - fp->_wide_data->_IO_write_base);
- (*mp->bufloc)[len] = '\0';
- *mp->sizeloc = len;
+ if (update_bufsize (fp, mp->sizeloc))
+ (*mp->bufloc)[*mp->sizeloc] = L'\0';
fp->_wide_data->_IO_buf_base = NULL;
}
_IO_wstr_finish (fp, 0);
}
+
+static off64_t
+_IO_wmem_seekoff (FILE *fp, off64_t offset, int dir, int mode)
+{
+ struct _IO_FILE_wmemstream *mp = (struct _IO_FILE_wmemstream *) fp;
+ if (fp->_wide_data->_IO_write_ptr != mp->seekwriteptr)
+ mp->prevwriteptr = fp->_wide_data->_IO_write_ptr;
+ off64_t ret = _IO_wstr_seekoff (fp, offset, dir, mode);
+ mp->seekwriteptr = fp->_wide_data->_IO_write_ptr;
+ return ret;
+}