util/string: sprintf_append_realloc to concatenate strings with realloc

Appending string by using sprintf with realloc will be generally
usable and add sprintf_append_realloc() and vsprintf_append_realloc()
to the utility.

These APIs follow realloc about buffer management, i.e., the original
buffer is left untouched if they fail.

Besides, the original buffer is NULL, they are equivalent to
sprintf_alloc() and vsprintf_alloc(), respectively.

Change-Id: I8b69d9640e86e1862ddd3917995bad6f59426b7e
Signed-off-by: Shuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com>
Signed-off-by: Chunyang Hui <chunyang.hui@intel.com>
Signed-off-by: Pawel Wodkowski <pawelx.wodkowski@intel.com>
Reviewed-on: https://review.gerrithub.io/c/436913
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Darek Stojaczyk <dariusz.stojaczyk@intel.com>
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
Chandler-Test-Pool: SPDK Automated Test System <sys_sgsw@intel.com>
This commit is contained in:
Shuhei Matsumoto 2018-12-12 15:00:01 +09:00 committed by Jim Harris
parent bf6210b3c7
commit 8adbd90991
3 changed files with 128 additions and 37 deletions

View File

@ -69,6 +69,42 @@ char *spdk_sprintf_alloc(const char *format, ...) __attribute__((format(printf,
*/
char *spdk_vsprintf_alloc(const char *format, va_list args);
/**
* Append string using vsprintf with automatic buffer re-allocation.
*
* The return value is the formatted string, in which the original string in
* buffer is unchanged and the specified formatted string is appended.
*
* The returned string should be passed to free() when no longer needed.
*
* If buffer is NULL, the call is equivalent to spdk_sprintf_alloc().
* If the call fails, the original buffer is left untouched.
*
* \param buffer Buffer which has a formatted string.
* \param format Format for the string to print.
*
* \return the formatted string on success, or NULL on failure.
*/
char *spdk_sprintf_append_realloc(char *buffer, const char *format, ...);
/**
* Append string using vsprintf with automatic buffer re-allocation.
* The return value is the formatted string, in which the original string in
* buffer is unchanged and the specified formatted string is appended.
*
* The returned string should be passed to free() when no longer needed.
*
* If buffer is NULL, the call is equivalent to spdk_sprintf_alloc().
* If the call fails, the original buffer is left untouched.
*
* \param buffer Buffer which has a formatted string.
* \param format Format for the string to print.
* \param args A value that identifies a variable arguments list.
*
* \return the formatted string on success, or NULL on failure.
*/
char *spdk_vsprintf_append_realloc(char *buffer, const char *format, va_list args);
/**
* Convert string to lowercase in place.
*

View File

@ -36,48 +36,55 @@
#include "spdk/string.h"
char *
spdk_vsprintf_alloc(const char *format, va_list args)
spdk_vsprintf_append_realloc(char *buffer, const char *format, va_list args)
{
va_list args_copy;
char *buf;
size_t bufsize;
int rc;
char *new_buffer;
int orig_size = 0, new_size;
/* Try with a small buffer first. */
bufsize = 32;
/* Limit maximum buffer size to something reasonable so we don't loop forever. */
while (bufsize <= 1024 * 1024) {
buf = malloc(bufsize);
if (buf == NULL) {
return NULL;
/* Original buffer size */
if (buffer) {
orig_size = strlen(buffer);
}
/* Necessary buffer size */
va_copy(args_copy, args);
rc = vsnprintf(buf, bufsize, format, args_copy);
new_size = vsnprintf(NULL, 0, format, args_copy);
va_end(args_copy);
/*
* If vsnprintf() returned a count within our current buffer size, we are done.
* The count does not include the \0 terminator, so rc == bufsize is not OK.
*/
if (rc >= 0 && (size_t)rc < bufsize) {
return buf;
}
/*
* vsnprintf() should return the required space, but some libc versions do not
* implement this correctly, so just double the buffer size and try again.
*
* We don't need the data in buf, so rather than realloc(), use free() and malloc()
* again to avoid a copy.
*/
free(buf);
bufsize *= 2;
}
if (new_size < 0) {
return NULL;
}
new_size += orig_size + 1;
new_buffer = realloc(buffer, new_size);
if (new_buffer == NULL) {
return NULL;
}
vsnprintf(new_buffer + orig_size, new_size - orig_size, format, args);
return new_buffer;
}
char *
spdk_sprintf_append_realloc(char *buffer, const char *format, ...)
{
va_list args;
char *ret;
va_start(args, format);
ret = spdk_vsprintf_append_realloc(buffer, format, args);
va_end(args);
return ret;
}
char *
spdk_vsprintf_alloc(const char *format, va_list args)
{
return spdk_vsprintf_append_realloc(NULL, format, args);
}
char *
spdk_sprintf_alloc(const char *format, ...)

View File

@ -202,6 +202,53 @@ test_parse_capacity(void)
CU_ASSERT(rc != 0);
}
static void
test_sprintf_append_realloc(void)
{
char *str1, *str2, *str3, *str4;
/* Test basic functionality. */
str1 = spdk_sprintf_alloc("hello world\ngood morning\n" \
"good afternoon\ngood evening\n");
SPDK_CU_ASSERT_FATAL(str1 != NULL);
str2 = spdk_sprintf_append_realloc(NULL, "hello world\n");
SPDK_CU_ASSERT_FATAL(str2);
str2 = spdk_sprintf_append_realloc(str2, "good morning\n");
SPDK_CU_ASSERT_FATAL(str2);
str2 = spdk_sprintf_append_realloc(str2, "good afternoon\n");
SPDK_CU_ASSERT_FATAL(str2);
str2 = spdk_sprintf_append_realloc(str2, "good evening\n");
SPDK_CU_ASSERT_FATAL(str2);
CU_ASSERT(strcmp(str1, str2) == 0);
free(str1);
free(str2);
/* Test doubling buffer size. */
str3 = spdk_sprintf_append_realloc(NULL, "aaaaaaaaaa\n");
str3 = spdk_sprintf_append_realloc(str3, "bbbbbbbbbb\n");
str3 = spdk_sprintf_append_realloc(str3, "cccccccccc\n");
str4 = malloc(33 + 1);
memset(&str4[0], 'a', 10);
str4[10] = '\n';
memset(&str4[11], 'b', 10);
str4[21] = '\n';
memset(&str4[22], 'c', 10);
str4[32] = '\n';
str4[33] = 0;
CU_ASSERT(strcmp(str3, str4) == 0);
free(str3);
free(str4);
}
int
main(int argc, char **argv)
{
@ -221,7 +268,8 @@ main(int argc, char **argv)
if (
CU_add_test(suite, "test_parse_ip_addr", test_parse_ip_addr) == NULL ||
CU_add_test(suite, "test_str_chomp", test_str_chomp) == NULL ||
CU_add_test(suite, "test_parse_capacity", test_parse_capacity) == NULL) {
CU_add_test(suite, "test_parse_capacity", test_parse_capacity) == NULL ||
CU_add_test(suite, "test_sprintf_append_realloc", test_sprintf_append_realloc) == NULL) {
CU_cleanup_registry();
return CU_get_error();
}