From 75461c74dca5e5fa9c9f2540d7328a38e59a7b9c Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Sun, 21 Feb 2021 21:39:07 +0100 Subject: [PATCH] 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 e1c6466a26..9c0e9b8a47 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2021-02-21 Bruno Haible + + 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 scratch_buffer: Document the exported API. diff --git a/doc/posix-functions/open_memstream.texi b/doc/posix-functions/open_memstream.texi index e28766478f..479738e712 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 0000000000..b5a40093e5 --- /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 . */ + +/* Written by Bruno Haible , 2021. */ + +#include + +/* Specification. */ +#include "string-buffer.h" + +#include +#include +#include +#include + +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 0000000000..6beaff5d9f --- /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 . */ + +/* Written by Bruno Haible , 2021. */ + +#ifndef _STRING_BUFFER_H +#define _STRING_BUFFER_H + +#include +#include +#include + +#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 0000000000..4e7827c817 --- /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.39.5