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

Reply via email to