From: Adhemerval Zanella <adhemerval.zanella@linaro.org>
To: libc-alpha@sourceware.org
Subject: Re: [PATCH v2 2/2] libio: Fix seek-past-end returned size for open_{w}memstream (BZ#15298)
Date: Thu, 25 Aug 2016 19:53:00 -0000 [thread overview]
Message-ID: <d8026c7d-70c8-a8cd-3896-84b143331869@linaro.org> (raw)
In-Reply-To: <1470688986-8798-2-git-send-email-adhemerval.zanella@linaro.org>
Ping.
On 08/08/2016 17:43, Adhemerval Zanella wrote:
> This patch fixes how open_{w}memstream updates the returned buffer
> size on a fclose/fflush operation. POSIX states [1]:
>
> "After a successful fflush() or fclose() [...] the variable pointed
> to by sizep shall contain the smaller of the current buffer length and
> the number of bytes for open_memstream(), or the number of wide characters
> for open_wmemstream(), between the beginning of the buffer and the current
> file position indicator."
>
> Current GLIBC behavior returns the seek position even if there is no previous
> write operation. To correctly report the buffer size the implementation must
> track both the buffer positiob and currently bytes written. However internal
> _IO_write_ptr is updated on both write and seek operations.
>
> This patch fixes it by adding two new internal fields to keep track of both
> previous and next position after a write operation. It allows the sync and
> close callbacks to know if a write has occurred based on previous and
> current _IO_write_ptr value.
>
> Tested on x86_64.
>
> [BZ #15298]
> * libio/memstream.c (_IO_FILE_memstream): Add prevwriteptr and
> seekwriteptr fields.
> (_IO_mem_seekoff): New function.
> (_IO_mem_jumps): Use _IO_mem_seekoff.
> (__open_memstream): Initialize prevwriteptr and seekwriteptr.
> (_IO_mem_sync): Update sizeloc based on written bytes instead of buffer
> current position.
> (_IO_mem_finish): Likewise.
> * libio/wmemstream.c (_IO_FILE_wmemstream): Add prevwriteptr and
> seekwriteptr fields.
> (_IO_wmem_seekoff): New function.
> (_IO_wmem_jumps): Use _IO_mem_seekoff.
> (__open_wmemstream): Initialize prevwriteptr and seekwriteptr.
> (_IO_mem_wsync): Update sizeloc based on written bytes instead of buffer
> current position.
> (_IO_mem_wfinish): Likewise.
> * libio/tst-memstream3.c (do_test_bz15298): New function.
> (do_test_bz18241): Check for expected size after a fseek followed by a
> fflush.
> (do_test): Call do_test_bz15298.
>
> [1] http://pubs.opengroup.org/onlinepubs/9699919799/functions/open_memstream.html
> ---
> ChangeLog | 22 +++++++++++++++
> libio/memstream.c | 52 ++++++++++++++++++++++++++++++++----
> libio/tst-memstream3.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++---
> libio/wmemstream.c | 54 ++++++++++++++++++++++++++++++++-----
> 4 files changed, 185 insertions(+), 15 deletions(-)
>
> diff --git a/ChangeLog b/ChangeLog
> index 28d9012..ea16b48 100644
> --- a/ChangeLog
> +++ b/ChangeLog
> @@ -1,5 +1,27 @@
> 2016-08-05 Adhemerval Zanella <adhemerval.zanella@linaro.org>
>
> + [BZ #15298]
> + * libio/memstream.c (_IO_FILE_memstream): Add prevwriteptr and
> + seekwriteptr fields.
> + (_IO_mem_seekoff): New function.
> + (_IO_mem_jumps): Use _IO_mem_seekoff.
> + (__open_memstream): Initialize prevwriteptr and seekwriteptr.
> + (_IO_mem_sync): Update sizeloc based on written bytes instead of buffer
> + current position.
> + (_IO_mem_finish): Likewise.
> + * libio/wmemstream.c (_IO_FILE_wmemstream): Add prevwriteptr and
> + seekwriteptr fields.
> + (_IO_wmem_seekoff): New function.
> + (_IO_wmem_jumps): Use _IO_mem_seekoff.
> + (__open_wmemstream): Initialize prevwriteptr and seekwriteptr.
> + (_IO_mem_wsync): Update sizeloc based on written bytes instead of buffer
> + current position.
> + (_IO_mem_wfinish): Likewise.
> + * libio/tst-memstream3.c (do_test_bz15298): New function.
> + (do_test_bz18241): Check for expected size after a fseek followed by a
> + fflush.
> + (do_test): Call do_test_bz15298.
> +
> [BZ #18241]
> [BZ #20181]
> * libio/Makefile (test): Add tst-memstream3 and tst-wmemstream3.
> diff --git a/libio/memstream.c b/libio/memstream.c
> index f1e8d58..cb3af9a 100644
> --- a/libio/memstream.c
> +++ b/libio/memstream.c
> @@ -26,12 +26,15 @@ struct _IO_FILE_memstream
> _IO_strfile _sf;
> char **bufloc;
> _IO_size_t *sizeloc;
> + char *prevwriteptr;
> + char *seekwriteptr;
> };
>
>
> static int _IO_mem_sync (_IO_FILE* fp) __THROW;
> static void _IO_mem_finish (_IO_FILE* fp, int) __THROW;
> -
> +static _IO_off64_t _IO_mem_seekoff (_IO_FILE *fp, _IO_off64_t offset,
> + int dir, int mode) __THROW;
>
> static const struct _IO_jump_t _IO_mem_jumps libio_vtable =
> {
> @@ -43,7 +46,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),
> @@ -95,6 +98,24 @@ __open_memstream (char **bufloc, _IO_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 written. However _IO_write_ptr is
> + updated on both write and seek operations. 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 occured)
> +
> + 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;
>
> return (_IO_FILE *) &new_f->fp._sf._sbf;
> }
> @@ -114,7 +135,9 @@ _IO_mem_sync (_IO_FILE *fp)
> }
>
> *mp->bufloc = fp->_IO_write_base;
> - *mp->sizeloc = fp->_IO_write_ptr - fp->_IO_write_base;
> + char *ptr = (fp->_IO_write_ptr == mp->seekwriteptr)
> + ? mp->prevwriteptr : fp->_IO_write_ptr;
> + *mp->sizeloc = ptr - fp->_IO_write_base;
>
> return 0;
> }
> @@ -129,11 +152,30 @@ _IO_mem_finish (_IO_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;
> + size_t len;
> + if (fp->_IO_write_ptr == mp->seekwriteptr)
> + len = mp->prevwriteptr - fp->_IO_write_base;
> + else
> + {
> + /* An '\0' should be appended iff a write operation ocurred. */
> + len = fp->_IO_write_ptr - fp->_IO_write_base;
> + (*mp->bufloc)[len] = '\0';
> + }
> + *mp->sizeloc = len;
>
> fp->_IO_buf_base = NULL;
> }
>
> _IO_str_finish (fp, 0);
> }
> +
> +static _IO_off64_t
> +_IO_mem_seekoff (_IO_FILE *fp, _IO_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;
> + _IO_off64_t ret = _IO_str_seekoff (fp, offset, dir, mode);
> + mp->seekwriteptr = fp->_IO_write_ptr;
> + return ret;
> +}
> diff --git a/libio/tst-memstream3.c b/libio/tst-memstream3.c
> index 34b04e5..65c95ad 100644
> --- a/libio/tst-memstream3.c
> +++ b/libio/tst-memstream3.c
> @@ -57,6 +57,69 @@ error_printf (int line, const char *fmt, ...)
> { error_printf(__LINE__, __VA_ARGS__); return 1; }
>
> static int
> +do_test_bz15298 (void)
> +{
> + CHAR_T *buf;
> + size_t size;
> + size_t ret;
> +
> + FILE *fp = OPEN_MEMSTREAM (&buf, &size);
> + if (fp == NULL)
> + ERROR_RET1 ("%s failed\n", S(OPEN_MEMSTREAM));
> +
> + /* Move internal position but do not write any bytes. Final size should
> + be 0. */
> + if (fseek (fp, 10, SEEK_SET) == -1)
> + ERROR_RET1 ("fseek failed (errno = %d)\n", errno);
> + if (fseek (fp, 20, SEEK_CUR) == -1)
> + ERROR_RET1 ("fseek failed (errno = %d)\n", errno);
> + if (fseek (fp, 30, SEEK_CUR) == -1)
> + ERROR_RET1 ("fseek failed (errno = %d)\n", errno);
> + if (fflush (fp) != 0)
> + ERROR_RET1 ("fflush failed (errno = %d)\n", errno);
> + if (size != 0)
> + ERROR_RET1 ("size != 0 (got %zu)\n", size);
> +
> + /* Now write some bytes and change internal position. Final size should
> + be based on written bytes. */
> + if (fseek (fp, 0, SEEK_SET) == -1)
> + ERROR_RET1 ("fseek failed (errno = %d)\n", errno);
> + if ((ret = FWRITE (W("abc"), 1, 3, fp)) != 3)
> + ERROR_RET1 ("%s failed (errno = %d)\n", S(FWRITE), errno);
> + if (fseek (fp, 20, SEEK_CUR) == -1)
> + ERROR_RET1 ("fseek failed (errno = %d)\n", errno);
> + if (fseek (fp, 30, SEEK_CUR) == -1)
> + ERROR_RET1 ("fseek failed (errno = %d)\n", errno);
> + if (fflush (fp) != 0)
> + ERROR_RET1 ("fflush failed (errno = %d)\n", errno);
> + if (size != 3)
> + ERROR_RET1 ("size != 3 (got %zu)\n", size);
> +
> + /* Finally set position, write some bytes and change position again. Final
> + size should be based again on write position. */
> + size_t offset = 2048;
> + if (fseek (fp, offset, SEEK_SET) == -1)
> + ERROR_RET1 ("fseek failed (errno = %d)\n", errno);
> + if ((ret = FWRITE (W("abc"), 1, 3, fp)) != 3)
> + ERROR_RET1 ("%s failed (errno = %d)\n", S(FWRITE), errno);
> + if (fseek (fp, 20, SEEK_CUR) == -1)
> + ERROR_RET1 ("fseek failed (errno = %d)\n", errno);
> + if (fseek (fp, 30, SEEK_CUR) == -1)
> + ERROR_RET1 ("fseek failed (errno = %d)\n", errno);
> + if (fflush (fp) != 0)
> + ERROR_RET1 ("fflush failed (errno = %d)\n", errno);
> + if (size != offset + 3)
> + ERROR_RET1 ("size != %zu (got %zu)\n", offset + 3, size);
> +
> + if (fclose (fp) != 0)
> + ERROR_RET1 ("fclose failed (errno = %d\n", errno);
> +
> + free (buf);
> +
> + return 0;
> +}
> +
> +static int
> do_test_bz18241 (void)
> {
> CHAR_T *buf;
> @@ -124,15 +187,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')
> @@ -155,6 +220,7 @@ do_test (void)
>
> mcheck_pedantic (mcheck_abort);
>
> + ret += do_test_bz15298 ();
> ret += do_test_bz18241 ();
> ret += do_test_bz20181 ();
>
> diff --git a/libio/wmemstream.c b/libio/wmemstream.c
> index fd01be0..2f8f22c 100644
> --- a/libio/wmemstream.c
> +++ b/libio/wmemstream.c
> @@ -27,12 +27,15 @@ struct _IO_FILE_wmemstream
> _IO_strfile _sf;
> wchar_t **bufloc;
> _IO_size_t *sizeloc;
> + wchar_t *prevwriteptr;
> + wchar_t *seekwriteptr;
> };
>
>
> static int _IO_wmem_sync (_IO_FILE* fp) __THROW;
> static void _IO_wmem_finish (_IO_FILE* fp, int) __THROW;
> -
> +static _IO_off64_t _IO_wmem_seekoff (_IO_FILE *fp, _IO_off64_t offset,
> + int dir, int mode) __THROW;
>
> static const struct _IO_jump_t _IO_wmem_jumps libio_vtable =
> {
> @@ -44,7 +47,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),
> @@ -97,6 +100,24 @@ open_wmemstream (wchar_t **bufloc, _IO_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 written. However _IO_write_ptr is
> + updated on both write and seek operations. 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 occured)
> +
> + 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;
>
> return (_IO_FILE *) &new_f->fp._sf._sbf;
> }
> @@ -114,8 +135,9 @@ _IO_wmem_sync (_IO_FILE *fp)
> }
>
> *mp->bufloc = fp->_wide_data->_IO_write_base;
> - *mp->sizeloc = (fp->_wide_data->_IO_write_ptr
> - - fp->_wide_data->_IO_write_base);
> + wchar_t *ptr = (fp->_wide_data->_IO_write_ptr == mp->seekwriteptr)
> + ? mp->prevwriteptr : fp->_wide_data->_IO_write_ptr;
> + *mp->sizeloc = ptr - fp->_wide_data->_IO_write_base;
>
> return 0;
> }
> @@ -132,9 +154,16 @@ _IO_wmem_finish (_IO_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';
> + size_t len;
> + if (fp->_wide_data->_IO_write_ptr == mp->seekwriteptr)
> + len = mp->prevwriteptr - fp->_wide_data->_IO_write_base;
> + else
> + {
> + /* An '\0' should be appended iff a write operation ocurred. */
> + len = fp->_wide_data->_IO_write_ptr
> + - fp->_wide_data->_IO_write_base;
> + (*mp->bufloc)[len] = L'\0';
> + }
> *mp->sizeloc = len;
>
> fp->_wide_data->_IO_buf_base = NULL;
> @@ -142,3 +171,14 @@ _IO_wmem_finish (_IO_FILE *fp, int dummy)
>
> _IO_wstr_finish (fp, 0);
> }
> +
> +static _IO_off64_t
> +_IO_wmem_seekoff (_IO_FILE *fp, _IO_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;
> + _IO_off64_t ret = _IO_wstr_seekoff (fp, offset, dir, mode);
> + mp->seekwriteptr = fp->_wide_data->_IO_write_ptr;
> + return ret;
> +}
>
next prev parent reply other threads:[~2016-08-25 19:53 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-08-05 17:41 [PATCH 1/2] libio: Multiple fixes for open_{w}memstram (BZ#18241 and BZ#20181) Adhemerval Zanella
2016-08-05 17:41 ` [PATCH 2/2] libio: Fix seek-past-end returned size for open_{w}memstream (BZ#15298) Adhemerval Zanella
2016-08-26 10:15 ` Florian Weimer
2016-08-29 14:43 ` Adhemerval Zanella
2016-08-05 20:11 ` [PATCH 1/2] libio: Multiple fixes for open_{w}memstram (BZ#18241 and BZ#20181) Andreas Schwab
2016-08-05 21:00 ` Adhemerval Zanella
2016-08-08 20:43 ` [PATCH v2 " Adhemerval Zanella
2016-08-08 20:43 ` [PATCH v2 2/2] libio: Fix seek-past-end returned size for open_{w}memstream (BZ#15298) Adhemerval Zanella
2016-08-25 19:53 ` Adhemerval Zanella [this message]
2016-08-25 19:53 ` [PATCH v2 1/2] libio: Multiple fixes for open_{w}memstram (BZ#18241 and BZ#20181) Adhemerval Zanella
2016-09-30 0:06 ` Adhemerval Zanella
2016-09-30 10:12 ` Andreas Schwab
2016-10-01 9:17 ` Andreas Schwab
2016-10-02 13:29 ` Adhemerval Zanella
2017-04-19 9:17 ` Maintaining libio (was: Re: [PATCH v2 1/2] libio: Multiple fixes for open_{w}memstram (BZ#18241 and BZ#20181)) Florian Weimer
2017-04-19 12:35 ` Dmitry V. Levin
2017-04-19 12:52 ` Zack Weinberg
2017-04-19 13:03 ` Dmitry V. Levin
2017-04-19 13:01 ` Maintaining libio Florian Weimer
2017-04-19 13:17 ` Dmitry V. Levin
2017-04-19 13:23 ` Florian Weimer
2017-04-19 12:52 ` Maintaining libio (was: Re: [PATCH v2 1/2] libio: Multiple fixes for open_{w}memstram (BZ#18241 and BZ#20181)) Zack Weinberg
2017-04-19 14:48 ` Maintaining libio Adhemerval Zanella
2017-04-19 15:02 ` Florian Weimer
2017-04-19 15:17 ` Adhemerval Zanella
2017-04-20 15:44 ` Maintaining libio (was: Re: [PATCH v2 1/2] libio: Multiple fixes for open_{w}memstram (BZ#18241 and BZ#20181)) Zack Weinberg
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=d8026c7d-70c8-a8cd-3896-84b143331869@linaro.org \
--to=adhemerval.zanella@linaro.org \
--cc=libc-alpha@sourceware.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).