What alternative does gnulib offer to people who use open_memstream()? open_memstream is not portable: https://www.gnu.org/software/gnulib/manual/html_node/open_005fmemstream.html But gnulib cannot provide a drop-in replacement since it would require unportable stream hackery (worse that stdio-impl.h).
The alternative is a string buffer module. Gnulib has some modules that sound good at first sight but don't fulfil the need: * scratch_buffer stores only one size_t in the struct; it requires the program to keep track how much of the buffer is already occupied. Also it does not have printf-like formatting. * dynarray: Likewise. * line-buffer: Has the right members in the struct. But lacks piecewise accumulation functions. * obstack: Does not have printf-like formatting. Also lacks a "small strings are stack allocated" optimization. So, I'm adding one that fulfils that need. 2021-02-21 Bruno Haible <br...@clisp.org> string-buffer: Add tests. * tests/test-string-buffer.c: New file. * modules/string-buffer-tests: New file. string-buffer: New module. * lib/string-buffer.h: New file. * lib/string-buffer.c: New file. * modules/string-buffer: New file. * doc/posix-functions/open_memstream.texi: Mention the new module.
>From 75461c74dca5e5fa9c9f2540d7328a38e59a7b9c Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Sun, 21 Feb 2021 21:39:07 +0100 Subject: [PATCH 1/2] string-buffer: New module. * lib/string-buffer.h: New file. * lib/string-buffer.c: New file. * modules/string-buffer: New file. * doc/posix-functions/open_memstream.texi: Mention the new module. --- ChangeLog | 8 + doc/posix-functions/open_memstream.texi | 3 + lib/string-buffer.c | 297 ++++++++++++++++++++++++++++++++ lib/string-buffer.h | 85 +++++++++ modules/string-buffer | 26 +++ 5 files changed, 419 insertions(+) create mode 100644 lib/string-buffer.c create mode 100644 lib/string-buffer.h create mode 100644 modules/string-buffer diff --git a/ChangeLog b/ChangeLog index e1c6466..9c0e9b8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,13 @@ 2021-02-21 Bruno Haible <br...@clisp.org> + string-buffer: New module. + * lib/string-buffer.h: New file. + * lib/string-buffer.c: New file. + * modules/string-buffer: New file. + * doc/posix-functions/open_memstream.texi: Mention the new module. + +2021-02-21 Bruno Haible <br...@clisp.org> + scratch_buffer: Document the exported API. * lib/scratch_buffer.h: Add comments, taken from lib/malloc/scratch_buffer.h. diff --git a/doc/posix-functions/open_memstream.texi b/doc/posix-functions/open_memstream.texi index e287664..479738e 100644 --- a/doc/posix-functions/open_memstream.texi +++ b/doc/posix-functions/open_memstream.texi @@ -16,3 +16,6 @@ Portability problems not fixed by Gnulib: This function is missing on some platforms: Mac OS X 10.5, FreeBSD 6.0, NetBSD 7.1, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, HP-UX 11, IRIX 6.5, Solaris 11.3, Cygwin 1.5.x, mingw, MSVC 14, Android 5.1. @end itemize + +An alternative to the @code{open_memstream} function is the Gnulib module +@code{string-buffer}. diff --git a/lib/string-buffer.c b/lib/string-buffer.c new file mode 100644 index 0000000..b5a4009 --- /dev/null +++ b/lib/string-buffer.c @@ -0,0 +1,297 @@ +/* A buffer that accumulates a string by piecewise concatenation. + Copyright (C) 2021 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Bruno Haible <br...@clisp.org>, 2021. */ + +#include <config.h> + +/* Specification. */ +#include "string-buffer.h" + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +void +sb_init (struct string_buffer *buffer) +{ + buffer->data = buffer->space; + buffer->length = 0; + buffer->allocated = sizeof (buffer->space); + buffer->error = false; +} + +/* Ensures that INCREMENT bytes are available beyond the current used length + of BUFFER. + Returns 0, or -1 in case of out-of-memory error. */ +static int +sb_ensure_more_bytes (struct string_buffer *buffer, size_t increment) +{ + size_t incremented_length = buffer->length + increment; + if (incremented_length < increment) + /* Overflow. */ + return -1; + + if (buffer->allocated < incremented_length) + { + size_t new_allocated = 2 * buffer->allocated; + if (new_allocated < buffer->allocated) + /* Overflow. */ + return -1; + if (new_allocated < incremented_length) + new_allocated = incremented_length; + + char *new_data; + if (buffer->data == buffer->space) + { + new_data = (char *) malloc (new_allocated); + if (new_data == NULL) + /* Out-of-memory. */ + return -1; + memcpy (new_data, buffer->data, buffer->length); + } + else + { + new_data = (char *) realloc (buffer->data, new_allocated); + if (new_data == NULL) + /* Out-of-memory. */ + return -1; + } + buffer->data = new_data; + buffer->allocated = new_allocated; + } + return 0; +} + +int +sb_append (struct string_buffer *buffer, const char *str) +{ + size_t len = strlen (str); + if (sb_ensure_more_bytes (buffer, len) < 0) + { + buffer->error = true; + return -1; + } + memcpy (buffer->data + buffer->length, str, len); + buffer->length += len; + return 0; +} + +int +sb_appendvf (struct string_buffer *buffer, const char *formatstring, + va_list list) +{ + va_list list_copy; + + /* Make a bit of room, so that the probability that the first vsnprintf() call + succeeds is high. */ + size_t room = buffer->allocated - buffer->length; + if (room < 64) + { + if (sb_ensure_more_bytes (buffer, 64) < 0) + { + buffer->error = true; + return -1; + } + room = buffer->allocated - buffer->length; + } + + va_copy (list_copy, list); + + /* First vsnprintf() call. */ + int ret = vsnprintf (buffer->data + buffer->length, room, formatstring, list); + if (ret < 0) + { + /* Failed. */ + buffer->error = true; + ret = -1; + } + else + { + if ((size_t) ret <= room) + { + /* The result has fit into room bytes. */ + buffer->length += (size_t) ret; + ret = 0; + } + else + { + /* The result was truncated. Make more room, for a second vsnprintf() + call. */ + if (sb_ensure_more_bytes (buffer, (size_t) ret) < 0) + { + buffer->error = true; + ret = -1; + } + else + { + /* Second vsnprintf() call. */ + room = buffer->allocated - buffer->length; + ret = vsnprintf (buffer->data + buffer->length, room, + formatstring, list_copy); + if (ret < 0) + { + /* Failed. */ + buffer->error = true; + ret = -1; + } + else + { + if ((size_t) ret <= room) + { + /* The result has fit into room bytes. */ + buffer->length += (size_t) ret; + ret = 0; + } + else + /* The return values of the vsnprintf() calls are not + consistent. */ + abort (); + } + } + } + } + + va_end (list_copy); + return ret; +} + +int +sb_appendf (struct string_buffer *buffer, const char *formatstring, ...) +{ + va_list args; + + /* Make a bit of room, so that the probability that the first vsnprintf() call + succeeds is high. */ + size_t room = buffer->allocated - buffer->length; + if (room < 64) + { + if (sb_ensure_more_bytes (buffer, 64) < 0) + { + buffer->error = true; + return -1; + } + room = buffer->allocated - buffer->length; + } + + va_start (args, formatstring); + + /* First vsnprintf() call. */ + int ret = vsnprintf (buffer->data + buffer->length, room, formatstring, args); + if (ret < 0) + { + /* Failed. */ + buffer->error = true; + ret = -1; + } + else + { + if ((size_t) ret <= room) + { + /* The result has fit into room bytes. */ + buffer->length += (size_t) ret; + ret = 0; + } + else + { + /* The result was truncated. Make more room, for a second vsnprintf() + call. */ + if (sb_ensure_more_bytes (buffer, (size_t) ret) < 0) + { + buffer->error = true; + ret = -1; + } + else + { + /* Second vsnprintf() call. */ + room = buffer->allocated - buffer->length; + va_end (args); + va_start (args, formatstring); + ret = vsnprintf (buffer->data + buffer->length, room, + formatstring, args); + if (ret < 0) + { + /* Failed. */ + buffer->error = true; + ret = -1; + } + else + { + if ((size_t) ret <= room) + { + /* The result has fit into room bytes. */ + buffer->length += (size_t) ret; + ret = 0; + } + else + /* The return values of the vsnprintf() calls are not + consistent. */ + abort (); + } + } + } + } + + va_end (args); + return ret; +} + +void +sb_free (struct string_buffer *buffer) +{ + if (buffer->data != buffer->space) + free (buffer->data); +} + +/* Returns the contents of BUFFER, and frees all other memory held + by BUFFER. Returns NULL upon failure or if there was an error earlier. */ +char * +sb_dupfree (struct string_buffer *buffer) +{ + if (buffer->error) + goto fail; + + if (sb_ensure_more_bytes (buffer, 1) < 0) + goto fail; + buffer->data[buffer->length] = '\0'; + buffer->length++; + + if (buffer->data == buffer->space) + { + char *copy = (char *) malloc (buffer->length); + if (copy == NULL) + goto fail; + memcpy (copy, buffer->data, buffer->length); + return copy; + } + else + { + /* Shrink the string before returning it. */ + char *contents = buffer->data; + if (buffer->length < buffer->allocated) + { + contents = realloc (contents, buffer->length); + if (contents == NULL) + goto fail; + } + return contents; + } + + fail: + sb_free (buffer); + return NULL; +} diff --git a/lib/string-buffer.h b/lib/string-buffer.h new file mode 100644 index 0000000..6beaff5 --- /dev/null +++ b/lib/string-buffer.h @@ -0,0 +1,85 @@ +/* A buffer that accumulates a string by piecewise concatenation. + Copyright (C) 2021 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Bruno Haible <br...@clisp.org>, 2021. */ + +#ifndef _STRING_BUFFER_H +#define _STRING_BUFFER_H + +#include <stdarg.h> +#include <stdbool.h> +#include <stddef.h> + +#include "attribute.h" + +/* A string buffer type. */ +struct string_buffer +{ + char *data; + size_t length; /* used bytes, <= allocated */ + size_t allocated; /* allocated bytes */ + bool error; /* true if there was an error */ + char space[1024]; /* stack allocated space */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializes BUFFER to the empty string. */ +extern void sb_init (struct string_buffer *buffer); + +/* Appends the contents of STR to BUFFER. + Returns 0, or -1 in case of out-of-memory error. */ +extern int sb_append (struct string_buffer *buffer, const char *str); + +/* Appends the result of the printf-compatible FORMATSTRING with the argument + list LIST to BUFFER. + Returns 0, or -1 in case of error. */ +extern int sb_appendvf (struct string_buffer *buffer, + const char *formatstring, va_list list) + #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4) + ATTRIBUTE_FORMAT ((__gnu_printf__, 2, 0)) + #else + ATTRIBUTE_FORMAT ((__printf__, 2, 0)) + #endif + ; + +/* Appends the result of the printf-compatible FORMATSTRING with the following + arguments to BUFFER. + Returns 0, or -1 in case of error. */ +extern int sb_appendf (struct string_buffer *buffer, + const char *formatstring, ...) + #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4) + ATTRIBUTE_FORMAT ((__gnu_printf__, 2, 3)) + #else + ATTRIBUTE_FORMAT ((__printf__, 2, 3)) + #endif + ; + +/* Frees the memory held by BUFFER. */ +extern void sb_free (struct string_buffer *buffer); + +/* Returns the contents of BUFFER, and frees all other memory held + by BUFFER. Returns NULL upon failure or if there was an error earlier. + It is the responsibility of the caller to free() the result. */ +extern char * sb_dupfree (struct string_buffer *buffer); + +#ifdef __cplusplus +} +#endif + +#endif /* _STRING_BUFFER_H */ diff --git a/modules/string-buffer b/modules/string-buffer new file mode 100644 index 0000000..4e7827c --- /dev/null +++ b/modules/string-buffer @@ -0,0 +1,26 @@ +Description: +A buffer that accumulates a string by piecewise concatenation. + +Files: +lib/string-buffer.h +lib/string-buffer.c + +Depends-on: +stdbool +attribute +stdarg +vsnprintf-posix + +configure.ac: + +Makefile.am: +lib_SOURCES += string-buffer.c + +Include: +"string-buffer.h" + +License: +LGPLv2+ + +Maintainer: +all -- 2.7.4
>From c9b44f214c7c798c7701c7a281584e262b263655 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Sun, 21 Feb 2021 21:41:53 +0100 Subject: [PATCH 2/2] string-buffer: Add tests. * tests/test-string-buffer.c: New file. * modules/string-buffer-tests: New file. --- ChangeLog | 4 ++ modules/string-buffer-tests | 11 +++++ tests/test-string-buffer.c | 108 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 modules/string-buffer-tests create mode 100644 tests/test-string-buffer.c diff --git a/ChangeLog b/ChangeLog index 9c0e9b8..bbc8adf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2021-02-21 Bruno Haible <br...@clisp.org> + string-buffer: Add tests. + * tests/test-string-buffer.c: New file. + * modules/string-buffer-tests: New file. + string-buffer: New module. * lib/string-buffer.h: New file. * lib/string-buffer.c: New file. diff --git a/modules/string-buffer-tests b/modules/string-buffer-tests new file mode 100644 index 0000000..38473fd --- /dev/null +++ b/modules/string-buffer-tests @@ -0,0 +1,11 @@ +Files: +tests/test-string-buffer.c +tests/macros.h + +Depends-on: + +configure.ac: + +Makefile.am: +TESTS += test-string-buffer +check_PROGRAMS += test-string-buffer diff --git a/tests/test-string-buffer.c b/tests/test-string-buffer.c new file mode 100644 index 0000000..7973559 --- /dev/null +++ b/tests/test-string-buffer.c @@ -0,0 +1,108 @@ +/* Test of buffer that accumulates a string by piecewise concatenation. + Copyright (C) 2021 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Bruno Haible <br...@clisp.org>, 2021. */ + +#include <config.h> + +#include "string-buffer.h" + +#include <string.h> + +#include "macros.h" + +static int +my_appendf (struct string_buffer *buffer, const char *formatstring, ...) +{ + va_list args; + + va_start (args, formatstring); + int ret = sb_appendvf (buffer, formatstring, args); + va_end (args); + + return ret; +} + +char invalid_format_string_1[] = "%&"; +char invalid_format_string_2[] = "%^"; + +int +main () +{ + /* Test simple string concatenation. */ + { + struct string_buffer buffer; + + sb_init (&buffer); + char *s = sb_dupfree (&buffer); + ASSERT (s != NULL && strcmp (s, "") == 0); + free (s); + } + + { + struct string_buffer buffer; + + sb_init (&buffer); + sb_append (&buffer, "abc"); + sb_append (&buffer, ""); + sb_append (&buffer, "defg"); + char *s = sb_dupfree (&buffer); + ASSERT (s != NULL && strcmp (s, "abcdefg") == 0); + free (s); + } + + /* Test printf-like formatting. */ + { + struct string_buffer buffer; + + sb_init (&buffer); + sb_append (&buffer, "<"); + sb_appendf (&buffer, "%x", 3735928559U); + sb_append (&buffer, ">"); + char *s = sb_dupfree (&buffer); + ASSERT (s != NULL && strcmp (s, "<deadbeef>") == 0); + free (s); + } + + /* Test vprintf-like formatting. */ + { + struct string_buffer buffer; + + sb_init (&buffer); + sb_append (&buffer, "<"); + my_appendf (&buffer, "%x", 3735928559U); + sb_append (&buffer, ">"); + char *s = sb_dupfree (&buffer); + ASSERT (s != NULL && strcmp (s, "<deadbeef>") == 0); + free (s); + } + + /* Test printf-like formatting failure. */ + { + struct string_buffer buffer; + + sb_init (&buffer); + sb_append (&buffer, "<"); + sb_appendf (&buffer, invalid_format_string_1, 1); + sb_append (&buffer, "|"); + sb_appendf (&buffer, invalid_format_string_2, 2); + sb_append (&buffer, ">"); + char *s = sb_dupfree (&buffer); + ASSERT (s == NULL); + } + + return 0; +} -- 2.7.4