summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2020-04-04 17:58:54 +0200
committerThomas Haller <thaller@redhat.com>2020-04-04 17:58:54 +0200
commit2d83df38200121c9c0db56d62d07264da6c22d20 (patch)
tree468c73167bf2d41672ed616604f5d21f12748f4e
parent9a54111cc040ebd868d7a6695d12c4204e7092ea (diff)
parent09dcb18381b2b837cfe28a629907a120e66f5fe2 (diff)
downloadNetworkManager-2d83df38200121c9c0db56d62d07264da6c22d20.tar.gz
merge branch 'th/strbuf-v2'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/432
-rw-r--r--Makefile.am1
-rw-r--r--libnm-core/tests/test-general.c7
-rw-r--r--shared/nm-glib-aux/nm-enum-utils.c23
-rw-r--r--shared/nm-glib-aux/nm-io-utils.c32
-rw-r--r--shared/nm-glib-aux/nm-secret-utils.c2
-rw-r--r--shared/nm-glib-aux/nm-secret-utils.h121
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.c199
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.h5
-rw-r--r--shared/nm-glib-aux/nm-str-buf.h208
-rw-r--r--shared/nm-glib-aux/tests/test-shared-general.c149
-rw-r--r--shared/nm-utils/nm-test-utils.h10
11 files changed, 686 insertions, 71 deletions
diff --git a/Makefile.am b/Makefile.am
index 7580e993d5..e1b7b12abd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -415,6 +415,7 @@ shared_nm_glib_aux_libnm_glib_aux_la_SOURCES = \
shared/nm-glib-aux/nm-secret-utils.h \
shared/nm-glib-aux/nm-shared-utils.c \
shared/nm-glib-aux/nm-shared-utils.h \
+ shared/nm-glib-aux/nm-str-buf.h \
shared/nm-glib-aux/nm-time-utils.c \
shared/nm-glib-aux/nm-time-utils.h \
shared/nm-glib-aux/nm-value-type.h \
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index 53847b72a3..e0d0d55fa2 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -7545,10 +7545,11 @@ _do_test_utils_str_utf8safe (const char *str, gsize str_len, const char *expecte
gs_free char *str_free_7 = NULL;
gs_free char *str_free_8 = NULL;
gboolean str_has_nul = FALSE;
+#define RND_FLAG ((nmtst_get_rand_bool ()) ? NM_UTILS_STR_UTF8_SAFE_FLAG_NONE : NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET)
- buf_safe = nm_utils_buf_utf8safe_escape (str, str_len, flags, &str_free_1);
+ buf_safe = nm_utils_buf_utf8safe_escape (str, str_len, flags | RND_FLAG, &str_free_1);
- str_safe = nm_utils_str_utf8safe_escape (str, flags, &str_free_2);
+ str_safe = nm_utils_str_utf8safe_escape (str, flags | RND_FLAG, &str_free_2);
if (str_len == 0) {
g_assert (buf_safe == NULL);
@@ -7568,7 +7569,7 @@ _do_test_utils_str_utf8safe (const char *str, gsize str_len, const char *expecte
} else
str_has_nul = TRUE;
- str_free_3 = nm_utils_str_utf8safe_escape_cp (str, flags);
+ str_free_3 = nm_utils_str_utf8safe_escape_cp (str, flags | RND_FLAG);
g_assert_cmpstr (str_free_3, ==, str_safe);
g_assert ((!str && !str_free_3) || (str != str_free_3));
diff --git a/shared/nm-glib-aux/nm-enum-utils.c b/shared/nm-glib-aux/nm-enum-utils.c
index e105a4f5e5..854eda6e5a 100644
--- a/shared/nm-glib-aux/nm-enum-utils.c
+++ b/shared/nm-glib-aux/nm-enum-utils.c
@@ -6,6 +6,7 @@
#include "nm-default.h"
#include "nm-enum-utils.h"
+#include "nm-str-buf.h"
/*****************************************************************************/
@@ -142,12 +143,14 @@ _nm_utils_enum_to_str_full (GType type,
else
return g_strdup (enum_value->value_nick);
} else if (G_IS_FLAGS_CLASS (klass)) {
- GFlagsValue *flags_value;
- GString *str = g_string_new ("");
unsigned uvalue = (unsigned) value;
+ GFlagsValue *flags_value;
+ NMStrBuf strbuf;
flags_separator = flags_separator ?: " ";
+ nm_str_buf_init (&strbuf, 16, FALSE);
+
for ( ; value_infos && value_infos->nick; value_infos++) {
nm_assert (_enum_is_valid_flags_nick (value_infos->nick));
@@ -160,9 +163,9 @@ _nm_utils_enum_to_str_full (GType type,
continue;
}
- if (str->len)
- g_string_append (str, flags_separator);
- g_string_append (str, value_infos->nick);
+ if (strbuf.len)
+ nm_str_buf_append (&strbuf, flags_separator);
+ nm_str_buf_append (&strbuf, value_infos->nick);
uvalue &= ~((unsigned) value_infos->value);
if (uvalue == 0) {
/* we printed all flags. Done. */
@@ -172,20 +175,20 @@ _nm_utils_enum_to_str_full (GType type,
do {
flags_value = g_flags_get_first_value (G_FLAGS_CLASS (klass), uvalue);
- if (str->len)
- g_string_append (str, flags_separator);
+ if (strbuf.len)
+ nm_str_buf_append (&strbuf, flags_separator);
if ( !flags_value
|| !_enum_is_valid_flags_nick (flags_value->value_nick)) {
if (uvalue)
- g_string_append_printf (str, "0x%x", uvalue);
+ nm_str_buf_append_printf (&strbuf, "0x%x", uvalue);
break;
}
- g_string_append (str, flags_value->value_nick);
+ nm_str_buf_append (&strbuf, flags_value->value_nick);
uvalue &= ~flags_value->value;
} while (uvalue);
flags_done:
- return g_string_free (str, FALSE);
+ return nm_str_buf_finalize (&strbuf, NULL);
}
g_return_val_if_reached (NULL);
diff --git a/shared/nm-glib-aux/nm-io-utils.c b/shared/nm-glib-aux/nm-io-utils.c
index a08fcf4b10..7716cc39d0 100644
--- a/shared/nm-glib-aux/nm-io-utils.c
+++ b/shared/nm-glib-aux/nm-io-utils.c
@@ -51,32 +51,6 @@ _get_contents_error (GError **error, int errsv, int *out_errsv, const char *form
_get_contents_error (error, _errsv, out_errsv, __VA_ARGS__); \
})
-static char *
-_mem_realloc (char *old, gboolean do_bzero_mem, gsize cur_len, gsize new_len)
-{
- char *new;
-
- /* re-allocating to zero bytes is an odd case. We don't need it
- * and it's not supported. */
- nm_assert (new_len > 0);
-
- /* regardless of success/failure, @old will always be freed/consumed. */
-
- if (do_bzero_mem && cur_len > 0) {
- new = g_try_malloc (new_len);
- if (new)
- memcpy (new, old, NM_MIN (cur_len, new_len));
- nm_explicit_bzero (old, cur_len);
- g_free (old);
- } else {
- new = g_try_realloc (old, new_len);
- if (!new)
- g_free (old);
- }
-
- return new;
-}
-
/**
* nm_utils_fd_get_contents:
* @fd: open file descriptor to read. The fd will not be closed,
@@ -161,7 +135,7 @@ nm_utils_fd_get_contents (int fd,
str[n_read] = '\0';
if (n_read < n_stat) {
- if (!(str = _mem_realloc (str, do_bzero_mem, n_stat + 1, n_read + 1)))
+ if (!(str = nm_secret_mem_try_realloc_take (str, do_bzero_mem, n_stat + 1, n_read + 1)))
return _get_contents_error (error, ENOMEM, out_errsv, "failure to reallocate buffer with %zu bytes", n_read + 1);
}
NM_SET_OUT (length, n_read);
@@ -222,7 +196,7 @@ nm_utils_fd_get_contents (int fd,
n_alloc = NM_MIN (n_read + 1, sizeof (buf));
}
- if (!(str = _mem_realloc (str, do_bzero_mem, old_n_alloc, n_alloc))) {
+ if (!(str = nm_secret_mem_try_realloc_take (str, do_bzero_mem, old_n_alloc, n_alloc))) {
if (do_bzero_mem)
nm_explicit_bzero (buf, sizeof (buf));
return _get_contents_error (error, ENOMEM, out_errsv, "failure to allocate buffer of %zu bytes", n_alloc);
@@ -241,7 +215,7 @@ nm_utils_fd_get_contents (int fd,
else {
str[n_have] = '\0';
if (n_have + 1 < n_alloc) {
- if (!(str = _mem_realloc (str, do_bzero_mem, n_alloc, n_have + 1)))
+ if (!(str = nm_secret_mem_try_realloc_take (str, do_bzero_mem, n_alloc, n_have + 1)))
return _get_contents_error (error, ENOMEM, out_errsv, "failure to truncate buffer to %zu bytes", n_have + 1);
}
}
diff --git a/shared/nm-glib-aux/nm-secret-utils.c b/shared/nm-glib-aux/nm-secret-utils.c
index 282511d10d..78369b5edf 100644
--- a/shared/nm-glib-aux/nm-secret-utils.c
+++ b/shared/nm-glib-aux/nm-secret-utils.c
@@ -16,7 +16,7 @@ void
nm_explicit_bzero (void *s, gsize n)
{
/* gracefully handle n == 0. This is important, callers rely on it. */
- if (n == 0)
+ if (G_UNLIKELY (n == 0))
return;
nm_assert (s);
diff --git a/shared/nm-glib-aux/nm-secret-utils.h b/shared/nm-glib-aux/nm-secret-utils.h
index 17d5e85301..501e752071 100644
--- a/shared/nm-glib-aux/nm-secret-utils.h
+++ b/shared/nm-glib-aux/nm-secret-utils.h
@@ -152,4 +152,125 @@ GBytes *nm_secret_buf_to_gbytes_take (NMSecretBuf *secret, gssize actual_len);
gboolean nm_utils_memeqzero_secret (gconstpointer data, gsize length);
+/*****************************************************************************/
+
+/**
+ * nm_secret_mem_realloc:
+ * @m_old: the current buffer of length @cur_len.
+ * @do_bzero_mem: if %TRUE, bzero the old buffer
+ * @cur_len: the current buffer length of @m_old. It is necessary for bzero.
+ * @new_len: the desired new length
+ *
+ * If @do_bzero_mem is false, this is like g_realloc().
+ * Otherwise, this will allocate a new buffer of the desired size, copy over the
+ * old data, and bzero the old buffer before freeing it. As such, it also behaves
+ * similar to g_realloc(), with the overhead of nm_explicit_bzero() and using
+ * malloc/free intead of realloc().
+ *
+ * Returns: the new allocated buffer. Think of it behaving like g_realloc().
+ */
+static inline gpointer
+nm_secret_mem_realloc (gpointer m_old, gboolean do_bzero_mem, gsize cur_len, gsize new_len)
+{
+ gpointer m_new;
+
+ nm_assert (m_old || cur_len == 0);
+
+ if ( do_bzero_mem
+ && G_LIKELY (cur_len > 0)) {
+ m_new = g_malloc (new_len);
+ if (G_LIKELY (new_len > 0))
+ memcpy (m_new, m_old, NM_MIN (cur_len, new_len));
+ nm_explicit_bzero (m_old, cur_len);
+ g_free (m_old);
+ } else
+ m_new = g_realloc (m_old, new_len);
+
+ return m_new;
+}
+
+/**
+ * nm_secret_mem_try_realloc:
+ * @m_old: the current buffer of length @cur_len.
+ * @do_bzero_mem: if %TRUE, bzero the old buffer
+ * @cur_len: the current buffer length of @m_old. It is necessary for bzero.
+ * @new_len: the desired new length
+ *
+ * If @do_bzero_mem is false, this is like g_try_realloc().
+ * Otherwise, this will try to allocate a new buffer of the desired size, copy over the
+ * old data, and bzero the old buffer before freeing it. As such, it also behaves
+ * similar to g_try_realloc(), with the overhead of nm_explicit_bzero() and using
+ * malloc/free intead of realloc().
+ *
+ * Returns: the new allocated buffer or NULL. Think of it behaving like g_try_realloc().
+ */
+static inline gpointer
+nm_secret_mem_try_realloc (gpointer m_old, gboolean do_bzero_mem, gsize cur_len, gsize new_len)
+{
+ gpointer m_new;
+
+ nm_assert (m_old || cur_len == 0);
+
+ if ( do_bzero_mem
+ && G_LIKELY (cur_len > 0)) {
+ if (G_UNLIKELY (new_len == 0))
+ m_new = NULL;
+ else {
+ m_new = g_try_malloc (new_len);
+ if (!m_new)
+ return NULL;
+ memcpy (m_new, m_old, NM_MIN (cur_len, new_len));
+ }
+ nm_explicit_bzero (m_old, cur_len);
+ g_free (m_old);
+ return m_new;
+ }
+
+ return g_try_realloc (m_old, new_len);
+}
+
+/**
+ * nm_secret_mem_try_realloc_take:
+ * @m_old: the current buffer of length @cur_len.
+ * @do_bzero_mem: if %TRUE, bzero the old buffer
+ * @cur_len: the current buffer length of @m_old. It is necessary for bzero.
+ * @new_len: the desired new length
+ *
+ * This works like nm_secret_mem_try_realloc(), which is not unlike g_try_realloc().
+ * The difference is, if we fail to allocate a new buffer, then @m_old will be
+ * freed (and possibly cleared). This differs from plain realloc(), where the
+ * old buffer is unchanged if the operation fails.
+ *
+ * Returns: the new allocated buffer or NULL. Think of it behaving like g_try_realloc()
+ * but it will always free @m_old.
+ */
+static inline gpointer
+nm_secret_mem_try_realloc_take (gpointer m_old, gboolean do_bzero_mem, gsize cur_len, gsize new_len)
+{
+ gpointer m_new;
+
+ nm_assert (m_old || cur_len == 0);
+
+ if ( do_bzero_mem
+ && G_LIKELY (cur_len > 0)) {
+ if (G_UNLIKELY (new_len == 0))
+ m_new = NULL;
+ else {
+ m_new = g_try_malloc (new_len);
+ if (G_LIKELY (m_new))
+ memcpy (m_new, m_old, NM_MIN (cur_len, new_len));
+ }
+ nm_explicit_bzero (m_old, cur_len);
+ g_free (m_old);
+ return m_new;
+ }
+
+ m_new = g_try_realloc (m_old, new_len);
+ if (G_UNLIKELY (!m_new && new_len > 0))
+ g_free (m_old);
+ return m_new;
+}
+
+/*****************************************************************************/
+
#endif /* __NM_SECRET_UTILS_H__ */
diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c
index f1371801ca..14e32956db 100644
--- a/shared/nm-glib-aux/nm-shared-utils.c
+++ b/shared/nm-glib-aux/nm-shared-utils.c
@@ -15,6 +15,7 @@
#include <net/if.h>
#include "nm-errno.h"
+#include "nm-str-buf.h"
/*****************************************************************************/
@@ -84,6 +85,75 @@ nm_ip_addr_set_from_untrusted (int addr_family,
/*****************************************************************************/
+gsize
+nm_utils_get_next_realloc_size (gboolean true_realloc, gsize requested)
+{
+ gsize n, x;
+
+ /* https://doc.qt.io/qt-5/containers.html#growth-strategies */
+
+ if (requested <= 40) {
+ /* small allocations. Increase in small steps of 8 bytes.
+ *
+ * We get thus sizes of 8, 16, 32, 40. */
+ if (requested <= 8)
+ return 8;
+ if (requested <= 16)
+ return 16;
+ if (requested <= 32)
+ return 32;
+
+ /* The return values for < 104 are essentially hard-coded, and the choice here is
+ * made without very strong reasons.
+ *
+ * We want to stay 24 bytes below the power-of-two border 64. Hence, return 40 here.
+ * However, the next step then is already 104 (128 - 24). It's a larger gap than in
+ * the steps before.
+ *
+ * It's not clear whether some of the steps should be adjusted (or how exactly). */
+ return 40;
+ }
+
+ if ( requested <= 0x2000u - 24u
+ || G_UNLIKELY (!true_realloc)) {
+ /* mid sized allocations. Return next power of two, minus 24 bytes extra space
+ * at the beginning.
+ * That means, we double the size as we grow.
+ *
+ * With !true_realloc, it means that the caller does not intend to call
+ * realloc() but instead clone the buffer. This is for example the case, when we
+ * want to nm_explicit_bzero() the old buffer. In that case we really want to grow
+ * the buffer exponentially every time and not increment in page sizes of 4K (below).
+ *
+ * We get thus sizes of 104, 232, 488, 1000, 2024, 4072, 8168... */
+
+ if (G_UNLIKELY (requested > G_MAXSIZE / 2u - 24u))
+ return G_MAXSIZE;
+
+ x = requested + 24u;
+ n = 128u;
+ while (n < x) {
+ n <<= 1;
+ nm_assert (n > 128u);
+ }
+
+ nm_assert (n > 24u && n - 24u >= requested);
+ return n - 24u;
+ }
+
+ if (G_UNLIKELY (requested > G_MAXSIZE - 0x1000u - 24u))
+ return G_MAXSIZE;
+
+ /* For large allocations (with !true_realloc) we allocate memory in chunks of
+ * 4K (- 24 bytes extra), assuming that the memory gets mmapped and thus
+ * realloc() is efficient by just reordering pages. */
+ n = ((requested + (0x0FFFu + 24u)) & ~((gsize) 0x0FFFu)) - 24u;
+ nm_assert (n >= requested);
+ return n;
+}
+
+/*****************************************************************************/
+
pid_t
nm_utils_gettid (void)
{
@@ -2134,18 +2204,20 @@ nm_g_type_find_implementing_class_for_property (GType gtype,
/*****************************************************************************/
static void
-_str_append_escape (GString *s, char ch)
+_str_buf_append_c_escape_octal (NMStrBuf *strbuf,
+ char ch)
{
- g_string_append_c (s, '\\');
- g_string_append_c (s, '0' + ((((guchar) ch) >> 6) & 07));
- g_string_append_c (s, '0' + ((((guchar) ch) >> 3) & 07));
- g_string_append_c (s, '0' + ( ((guchar) ch) & 07));
+ nm_str_buf_append_c4 (strbuf,
+ '\\',
+ '0' + ((char) ((((guchar) ch) >> 6) & 07)),
+ '0' + ((char) ((((guchar) ch) >> 3) & 07)),
+ '0' + ((char) ((((guchar) ch) ) & 07)));
}
gconstpointer
nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_free)
{
- GString *gstr;
+ NMStrBuf strbuf;
gsize len;
const char *s;
@@ -2167,9 +2239,9 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr
return str;
}
- gstr = g_string_new_len (NULL, len);
+ nm_str_buf_init (&strbuf, len, FALSE);
- g_string_append_len (gstr, str, s - str);
+ nm_str_buf_append_len (&strbuf, str, s - str);
str = s;
for (;;) {
@@ -2192,6 +2264,9 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr
v = v * 8 + (ch - '0');
ch = (++str)[0];
if (ch >= '0' && ch <= '7') {
+ /* technically, escape sequences larger than \3FF are out of range
+ * and invalid. We don't check for that, and do the same as
+ * g_strcompress(): silently clip the value with & 0xFF. */
v = v * 8 + (ch - '0');
++str;
}
@@ -2213,21 +2288,20 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr
str++;
}
- g_string_append_c (gstr, ch);
+ nm_str_buf_append_c (&strbuf, ch);
s = strchr (str, '\\');
if (!s) {
- g_string_append (gstr, str);
+ nm_str_buf_append (&strbuf, str);
break;
}
- g_string_append_len (gstr, str, s - str);
+ nm_str_buf_append_len (&strbuf, str, s - str);
str = s;
}
- *out_len = gstr->len;
- *to_free = gstr->str;
- return g_string_free (gstr, FALSE);
+ return (*to_free = nm_str_buf_finalize (&strbuf,
+ out_len));
}
/**
@@ -2268,7 +2342,7 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa
const char *p = NULL;
const char *s;
gboolean nul_terminated = FALSE;
- GString *gstr;
+ NMStrBuf strbuf;
g_return_val_if_fail (to_free, NULL);
@@ -2299,7 +2373,9 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa
return str;
}
- gstr = g_string_sized_new (buflen + 5);
+ nm_str_buf_init (&strbuf,
+ buflen + 5,
+ NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET));
s = str;
do {
@@ -2309,21 +2385,22 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa
for (; s < p; s++) {
char ch = s[0];
+ nm_assert (ch);
if (ch == '\\')
- g_string_append (gstr, "\\\\");
+ nm_str_buf_append_c2 (&strbuf, '\\', '\\');
else if ( ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) \
&& ch < ' ') \
|| ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) \
&& ((guchar) ch) >= 127))
- _str_append_escape (gstr, ch);
+ _str_buf_append_c_escape_octal (&strbuf, ch);
else
- g_string_append_c (gstr, ch);
+ nm_str_buf_append_c (&strbuf, ch);
}
if (buflen <= 0)
break;
- _str_append_escape (gstr, p[0]);
+ _str_buf_append_c_escape_octal (&strbuf, p[0]);
buflen--;
if (buflen == 0)
@@ -2333,8 +2410,7 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa
(void) g_utf8_validate (s, buflen, &p);
} while (TRUE);
- *to_free = g_string_free (gstr, FALSE);
- return *to_free;
+ return (*to_free = nm_str_buf_finalize (&strbuf, NULL));
}
const char *
@@ -2358,13 +2434,11 @@ nm_utils_buf_utf8safe_escape_bytes (GBytes *bytes, NMUtilsStrUtf8SafeFlags flags
const char *
nm_utils_str_utf8safe_unescape (const char *str, char **to_free)
{
+ gsize len;
+
g_return_val_if_fail (to_free, NULL);
- if (!str || !strchr (str, '\\')) {
- *to_free = NULL;
- return str;
- }
- return (*to_free = g_strcompress (str));
+ return nm_utils_buf_utf8safe_unescape (str, &len, (gpointer *) to_free);
}
/**
@@ -2424,7 +2498,10 @@ nm_utils_str_utf8safe_escape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags)
char *
nm_utils_str_utf8safe_unescape_cp (const char *str)
{
- return str ? g_strcompress (str) : NULL;
+ char *s;
+
+ str = nm_utils_str_utf8safe_unescape (str, &s);
+ return s ?: g_strdup (str);
}
char *
@@ -4386,3 +4463,69 @@ nm_utils_ifname_valid (const char* name,
g_return_val_if_reached (FALSE);
}
+
+/*****************************************************************************/
+
+void
+_nm_str_buf_ensure_size (NMStrBuf *strbuf,
+ gsize new_size,
+ gboolean reserve_exact)
+{
+ _nm_str_buf_assert (strbuf);
+
+ /* Currently this only supports strictly growing the buffer. */
+ nm_assert (new_size > strbuf->_allocated);
+
+ if (!reserve_exact) {
+ new_size = nm_utils_get_next_realloc_size (!strbuf->_do_bzero_mem,
+ new_size);
+ }
+
+ strbuf->_str = nm_secret_mem_realloc (strbuf->_str,
+ strbuf->_do_bzero_mem,
+ strbuf->_allocated,
+ new_size);
+ strbuf->_allocated = new_size;
+}
+
+void
+nm_str_buf_append_printf (NMStrBuf *strbuf,
+ const char *format,
+ ...)
+{
+ va_list args;
+ gsize available;
+ int l;
+
+ _nm_str_buf_assert (strbuf);
+
+ available = strbuf->_allocated - strbuf->_len;
+
+ va_start (args, format);
+ l = g_vsnprintf (&strbuf->_str[strbuf->_len],
+ available,
+ format,
+ args);
+ va_end (args);
+
+ nm_assert (l >= 0);
+ nm_assert (l < G_MAXINT);
+
+ if ((gsize) l > available) {
+ gsize l2 = ((gsize) l) + 1u;
+
+ nm_str_buf_maybe_expand (strbuf, l2, FALSE);
+
+ va_start (args, format);
+ l = g_vsnprintf (&strbuf->_str[strbuf->_len],
+ l2,
+ format,
+ args);
+ va_end (args);
+
+ nm_assert (l >= 0);
+ nm_assert (l == l2 - 1);
+ }
+
+ strbuf->_len += (gsize) l;
+}
diff --git a/shared/nm-glib-aux/nm-shared-utils.h b/shared/nm-glib-aux/nm-shared-utils.h
index b68e658c38..1d066451b3 100644
--- a/shared/nm-glib-aux/nm-shared-utils.h
+++ b/shared/nm-glib-aux/nm-shared-utils.h
@@ -1024,6 +1024,7 @@ typedef enum {
NM_UTILS_STR_UTF8_SAFE_FLAG_NONE = 0,
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL = 0x0001,
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII = 0x0002,
+ NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET = 0x0004,
} NMUtilsStrUtf8SafeFlags;
const char *nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags, char **to_free);
@@ -1696,6 +1697,10 @@ nm_utils_strdup_reset (char **dst, const char *src)
/*****************************************************************************/
+gsize nm_utils_get_next_realloc_size (gboolean true_realloc, gsize requested);
+
+/*****************************************************************************/
+
typedef enum {
NMU_IFACE_ANY,
NMU_IFACE_KERNEL,
diff --git a/shared/nm-glib-aux/nm-str-buf.h b/shared/nm-glib-aux/nm-str-buf.h
new file mode 100644
index 0000000000..e7f9219cd9
--- /dev/null
+++ b/shared/nm-glib-aux/nm-str-buf.h
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: LGPL-2.1+
+
+#ifndef __NM_STR_BUF_H__
+#define __NM_STR_BUF_H__
+
+#include "nm-shared-utils.h"
+#include "nm-secret-utils.h"
+
+/*****************************************************************************/
+
+/* NMStrBuf is not unlike GString. The main difference is that it can use
+ * nm_explicit_bzero() when growing the buffer. */
+typedef struct {
+ char *_str;
+ union {
+ const gsize len;
+ gsize _len;
+ };
+ gsize _allocated;
+ bool _do_bzero_mem;
+} NMStrBuf;
+
+/*****************************************************************************/
+
+static inline void
+_nm_str_buf_assert (NMStrBuf *strbuf)
+{
+ nm_assert (strbuf);
+ nm_assert (strbuf->_str);
+ nm_assert (strbuf->_allocated > 0);
+ nm_assert (strbuf->_len <= strbuf->_allocated);
+}
+
+static inline void
+nm_str_buf_init (NMStrBuf *strbuf,
+ gsize len,
+ bool do_bzero_mem)
+{
+ nm_assert (strbuf);
+ nm_assert (len > 0);
+
+ strbuf->_do_bzero_mem = do_bzero_mem;
+ strbuf->_allocated = len;
+ strbuf->_str = g_malloc (len);
+ strbuf->_len = 0;
+
+ _nm_str_buf_assert (strbuf);
+}
+
+void _nm_str_buf_ensure_size (NMStrBuf *strbuf,
+ gsize new_size,
+ gboolean reserve_exact);
+
+static inline void
+nm_str_buf_maybe_expand (NMStrBuf *strbuf,
+ gsize reserve,
+ gboolean reserve_exact)
+{
+ _nm_str_buf_assert (strbuf);
+
+ /* currently we always require to reserve a non-zero number of bytes. */
+ nm_assert (reserve > 0);
+ nm_assert (strbuf->_len < G_MAXSIZE - reserve);
+
+ /* @reserve is the extra space that we require. */
+ if (G_UNLIKELY (reserve > strbuf->_allocated - strbuf->_len))
+ _nm_str_buf_ensure_size (strbuf, strbuf->_len + reserve, reserve_exact);
+}
+
+static inline void
+nm_str_buf_append_c (NMStrBuf *strbuf,
+ char ch)
+{
+ nm_str_buf_maybe_expand (strbuf, 2, FALSE);
+ strbuf->_str[strbuf->_len++] = ch;
+}
+
+static inline void
+nm_str_buf_append_c2 (NMStrBuf *strbuf,
+ char ch0,
+ char ch1)
+{
+ nm_str_buf_maybe_expand (strbuf, 3, FALSE);
+ strbuf->_str[strbuf->_len++] = ch0;
+ strbuf->_str[strbuf->_len++] = ch1;
+}
+
+static inline void
+nm_str_buf_append_c4 (NMStrBuf *strbuf,
+ char ch0,
+ char ch1,
+ char ch2,
+ char ch3)
+{
+ nm_str_buf_maybe_expand (strbuf, 5, FALSE);
+ strbuf->_str[strbuf->_len++] = ch0;
+ strbuf->_str[strbuf->_len++] = ch1;
+ strbuf->_str[strbuf->_len++] = ch2;
+ strbuf->_str[strbuf->_len++] = ch3;
+}
+
+static inline void
+nm_str_buf_append_len (NMStrBuf *strbuf,
+ const char *str,
+ gsize len)
+{
+ _nm_str_buf_assert (strbuf);
+
+ if (len > 0) {
+ nm_str_buf_maybe_expand (strbuf, len + 1, FALSE);
+ memcpy (&strbuf->_str[strbuf->_len], str, len);
+ strbuf->_len += len;
+ }
+}
+
+static inline void
+nm_str_buf_append (NMStrBuf *strbuf,
+ const char *str)
+{
+ nm_assert (str);
+
+ nm_str_buf_append_len (strbuf, str, strlen (str));
+}
+
+void nm_str_buf_append_printf (NMStrBuf *strbuf,
+ const char *format,
+ ...) _nm_printf (2, 3);
+
+/*****************************************************************************/
+
+/**
+ * nm_str_buf_get_str:
+ * @strbuf: the #NMStrBuf instance
+ *
+ * Returns the NUL terminated internal string.
+ *
+ * While constructing the string, the intermediate buffer
+ * is not NUL terminated (this makes it different from GString).
+ * Usually, one would build the string and retrieve it at the
+ * end with nm_str_buf_finalize(). This returns the NUL terminated
+ * buffer that was appended so far. Contrary to nm_str_buf_finalize(), you
+ * can still append more data to the buffer and this does not transfer ownership
+ * of the string.
+ *
+ * Returns: (transfer none): the internal string. The string
+ * is of length "strbuf->len", which may be larger if the
+ * returned string contains NUL characters (binary). The terminating
+ * NUL character is always present after "strbuf->len" characters.
+ */
+static inline const char *
+nm_str_buf_get_str (NMStrBuf *strbuf)
+{
+ nm_str_buf_maybe_expand (strbuf, 1, FALSE);
+ strbuf->_str[strbuf->_len] = '\0';
+ return strbuf->_str;
+}
+
+/**
+ * nm_str_buf_finalize:
+ * @strbuf: an initilized #NMStrBuf
+ * @out_len: (out): (allow-none): optional output
+ * argument with the length of the returned string.
+ *
+ * Returns: (transfer full): the string of the buffer
+ * which must be freed by the caller. The @strbuf
+ * is afterwards in undefined state, though it can be
+ * reused after nm_str_buf_init(). */
+static inline char *
+nm_str_buf_finalize (NMStrBuf *strbuf,
+ gsize *out_len)
+{
+ nm_str_buf_maybe_expand (strbuf, 1, TRUE);
+ strbuf->_str[strbuf->_len] = '\0';
+
+ NM_SET_OUT (out_len, strbuf->_len);
+
+ /* the buffer is in invalid state afterwards, however, we clear it
+ * so far, that nm_auto_str_buf and nm_str_buf_destroy() is happy. */
+ return g_steal_pointer (&strbuf->_str);
+}
+
+/**
+ * nm_str_buf_destroy:
+ * @strbuf: an initialized #NMStrBuf
+ *
+ * Frees the associated memory of @strbuf. The buffer
+ * afterwards is in undefined state, but can be re-initialized
+ * with nm_str_buf_init().
+ */
+static inline void
+nm_str_buf_destroy (NMStrBuf *strbuf)
+{
+ if (!strbuf->_str)
+ return;
+ _nm_str_buf_assert (strbuf);
+ if (strbuf->_do_bzero_mem)
+ nm_explicit_bzero (strbuf->_str, strbuf->_allocated);
+ g_free (strbuf->_str);
+
+ /* the buffer is in invalid state afterwards, however, we clear it
+ * so far, that nm_auto_str_buf is happy when calling
+ * nm_str_buf_destroy() again. */
+ strbuf->_str = NULL;
+}
+
+#define nm_auto_str_buf nm_auto (nm_str_buf_destroy)
+
+#endif /* __NM_STR_BUF_H__ */
diff --git a/shared/nm-glib-aux/tests/test-shared-general.c b/shared/nm-glib-aux/tests/test-shared-general.c
index 54a96ecb5b..5cf3da143b 100644
--- a/shared/nm-glib-aux/tests/test-shared-general.c
+++ b/shared/nm-glib-aux/tests/test-shared-general.c
@@ -584,6 +584,154 @@ test_string_table_lookup (void)
/*****************************************************************************/
+static void
+test_nm_utils_get_next_realloc_size (void)
+{
+ static const struct {
+ gsize requested;
+ gsize reserved_true;
+ gsize reserved_false;
+ } test_data[] = {
+ { 0, 8, 8 },
+ { 1, 8, 8 },
+ { 8, 8, 8 },
+ { 9, 16, 16 },
+ { 16, 16, 16 },
+ { 17, 32, 32 },
+ { 32, 32, 32 },
+ { 33, 40, 40 },
+ { 40, 40, 40 },
+ { 41, 104, 104 },
+ { 104, 104, 104 },
+ { 105, 232, 232 },
+ { 232, 232, 232 },
+ { 233, 488, 488 },
+ { 488, 488, 488 },
+ { 489, 1000, 1000 },
+ { 1000, 1000, 1000 },
+ { 1001, 2024, 2024 },
+ { 2024, 2024, 2024 },
+ { 2025, 4072, 4072 },
+ { 4072, 4072, 4072 },
+ { 4073, 8168, 8168 },
+ { 8168, 8168, 8168 },
+ { 8169, 12264, 16360 },
+ { 12263, 12264, 16360 },
+ { 12264, 12264, 16360 },
+ { 12265, 16360, 16360 },
+ { 16360, 16360, 16360 },
+ { 16361, 20456, 32744 },
+ { 20456, 20456, 32744 },
+ { 20457, 24552, 32744 },
+ { 24552, 24552, 32744 },
+ { 24553, 28648, 32744 },
+ { 28648, 28648, 32744 },
+ { 28649, 32744, 32744 },
+ { 32744, 32744, 32744 },
+ { 32745, 36840, 65512 },
+ { 36840, 36840, 65512 },
+ { G_MAXSIZE - 0x1000u, G_MAXSIZE, G_MAXSIZE },
+ { G_MAXSIZE - 25u, G_MAXSIZE, G_MAXSIZE },
+ { G_MAXSIZE - 24u, G_MAXSIZE, G_MAXSIZE },
+ { G_MAXSIZE - 1u, G_MAXSIZE, G_MAXSIZE },
+ { G_MAXSIZE, G_MAXSIZE, G_MAXSIZE },
+ };
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (test_data) + 5000u; i++) {
+ gsize requested0;
+
+ if (i < G_N_ELEMENTS (test_data))
+ requested0 = test_data[i].requested;
+ else {
+ /* find some interesting random values for testing. */
+ switch (nmtst_get_rand_uint32 () % 5) {
+ case 0:
+ requested0 = nmtst_get_rand_size ();
+ break;
+ case 1:
+ /* values close to G_MAXSIZE. */
+ requested0 = G_MAXSIZE - (nmtst_get_rand_uint32 () % 12000u);
+ break;
+ case 2:
+ /* values around G_MAXSIZE/2. */
+ requested0 = (G_MAXSIZE / 2u) + 6000u - (nmtst_get_rand_uint32 () % 12000u);
+ break;
+ case 3:
+ /* values around powers of 2. */
+ requested0 = (((gsize) 1) << (nmtst_get_rand_uint32 () % 64)) + 6000u - (nmtst_get_rand_uint32 () % 12000u);
+ break;
+ case 4:
+ /* values around 4k borders. */
+ requested0 = (nmtst_get_rand_size () & ~((gsize) 0xFFFu)) + 30u - (nmtst_get_rand_uint32 () % 60u);
+ break;
+ default: g_assert_not_reached ();
+ }
+ }
+
+ {
+ const gsize requested = requested0;
+ const gsize reserved_true = nm_utils_get_next_realloc_size (TRUE, requested);
+ const gsize reserved_false = nm_utils_get_next_realloc_size (FALSE, requested);
+
+ g_assert_cmpuint (reserved_true, >, 0);
+ g_assert_cmpuint (reserved_false, >, 0);
+ g_assert_cmpuint (reserved_true, >=, requested);
+ g_assert_cmpuint (reserved_false, >=, requested);
+ g_assert_cmpuint (reserved_false, >=, reserved_true);
+
+ if (i < G_N_ELEMENTS (test_data)) {
+ g_assert_cmpuint (reserved_true, ==, test_data[i].reserved_true);
+ g_assert_cmpuint (reserved_false, ==, test_data[i].reserved_false);
+ }
+
+ /* reserved_false is generally the next power of two - 24. */
+ if (reserved_false == G_MAXSIZE)
+ g_assert_cmpuint (requested, >, G_MAXSIZE / 2u - 24u);
+ else {
+ g_assert_cmpuint (reserved_false, <=, G_MAXSIZE - 24u);
+ if (reserved_false >= 40) {
+ const gsize _pow2 = reserved_false + 24u;
+
+ /* reserved_false must always be a power of two minus 24. */
+ g_assert_cmpuint (_pow2, >=, 64u);
+ g_assert_cmpuint (_pow2, >, requested);
+ g_assert (nm_utils_is_power_of_two (_pow2));
+
+ /* but _pow2/2 must also be smaller than what we requested. */
+ g_assert_cmpuint (_pow2 / 2u - 24u, <, requested);
+ } else {
+ /* smaller values are hard-coded. */
+ }
+ }
+
+ /* reserved_true is generally the next 4k border - 24. */
+ if (reserved_true == G_MAXSIZE)
+ g_assert_cmpuint (requested, >, G_MAXSIZE - 0x1000u - 24u);
+ else {
+ g_assert_cmpuint (reserved_true, <=, G_MAXSIZE - 24u);
+ if (reserved_true > 8168u) {
+ const gsize page_border = reserved_true + 24u;
+
+ /* reserved_true must always be aligned to 4k (minus 24). */
+ g_assert_cmpuint (page_border % 0x1000u, ==, 0);
+ if (requested > 0x1000u - 24u) {
+ /* page_border not be more than 4k above requested. */
+ g_assert_cmpuint (page_border, >=, 0x1000u - 24u);
+ g_assert_cmpuint (page_border - 0x1000u - 24u, <, requested);
+ }
+ } else {
+ /* for smaller sizes, reserved_true and reserved_false are the same. */
+ g_assert_cmpuint (reserved_true, ==, reserved_false);
+ }
+ }
+
+ }
+ }
+}
+
+/*****************************************************************************/
+
NMTST_DEFINE ();
int main (int argc, char **argv)
@@ -603,6 +751,7 @@ int main (int argc, char **argv)
g_test_add_func ("/general/test_nm_utils_bin2hexstr", test_nm_utils_bin2hexstr);
g_test_add_func ("/general/test_nm_ref_string", test_nm_ref_string);
g_test_add_func ("/general/test_string_table_lookup", test_string_table_lookup);
+ g_test_add_func ("/general/test_nm_utils_get_next_realloc_size", test_nm_utils_get_next_realloc_size);
return g_test_run ();
}
diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h
index 881fdcf89c..75af679965 100644
--- a/shared/nm-utils/nm-test-utils.h
+++ b/shared/nm-utils/nm-test-utils.h
@@ -864,6 +864,16 @@ nmtst_get_rand_uint (void)
return nmtst_get_rand_uint32 ();
}
+static inline gsize
+nmtst_get_rand_size (void)
+{
+ G_STATIC_ASSERT_EXPR ( sizeof (gsize) == sizeof (guint32)
+ || sizeof (gsize) == sizeof (guint64));
+ if (sizeof (gsize) == sizeof (guint32))
+ return nmtst_get_rand_uint32 ();
+ return nmtst_get_rand_uint64 ();
+}
+
static inline gboolean
nmtst_get_rand_bool (void)
{