diff options
author | Thomas Haller <thaller@redhat.com> | 2020-04-04 17:58:54 +0200 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2020-04-04 17:58:54 +0200 |
commit | 2d83df38200121c9c0db56d62d07264da6c22d20 (patch) | |
tree | 468c73167bf2d41672ed616604f5d21f12748f4e | |
parent | 9a54111cc040ebd868d7a6695d12c4204e7092ea (diff) | |
parent | 09dcb18381b2b837cfe28a629907a120e66f5fe2 (diff) | |
download | NetworkManager-2d83df38200121c9c0db56d62d07264da6c22d20.tar.gz |
merge branch 'th/strbuf-v2'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/432
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | libnm-core/tests/test-general.c | 7 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-enum-utils.c | 23 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-io-utils.c | 32 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-secret-utils.c | 2 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-secret-utils.h | 121 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-shared-utils.c | 199 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-shared-utils.h | 5 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-str-buf.h | 208 | ||||
-rw-r--r-- | shared/nm-glib-aux/tests/test-shared-general.c | 149 | ||||
-rw-r--r-- | shared/nm-utils/nm-test-utils.h | 10 |
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) { |