diff options
Diffstat (limited to 'shared/nm-glib-aux/nm-str-buf.h')
| -rw-r--r-- | shared/nm-glib-aux/nm-str-buf.h | 332 |
1 files changed, 332 insertions, 0 deletions
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 00000000..8c73bfaf --- /dev/null +++ b/shared/nm-glib-aux/nm-str-buf.h @@ -0,0 +1,332 @@ +// 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 _NMStrBuf { + + char *_priv_str; + + /* The unions only exist because we allow/encourage read-only access + * to the "len" and "allocated" fields, but modifying the fields is + * only allowed to the NMStrBuf implementation itself. */ + union { + /*const*/ gsize len; + gsize _priv_len; + }; + union { + /*const*/ gsize allocated; + gsize _priv_allocated; + }; + + bool _priv_do_bzero_mem; +} NMStrBuf; + +/*****************************************************************************/ + +static inline void +_nm_str_buf_assert (NMStrBuf *strbuf) +{ + nm_assert (strbuf); + nm_assert (strbuf->_priv_str); + nm_assert (strbuf->_priv_allocated > 0); + nm_assert (strbuf->_priv_len <= strbuf->_priv_allocated); +} + +static inline void +nm_str_buf_init (NMStrBuf *strbuf, + gsize len, + bool do_bzero_mem) +{ + nm_assert (strbuf); + nm_assert (len > 0); + + strbuf->_priv_str = g_malloc (len); + strbuf->_priv_allocated = len; + strbuf->_priv_len = 0; + strbuf->_priv_do_bzero_mem = do_bzero_mem; + + _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->_priv_len < G_MAXSIZE - reserve); + + /* @reserve is the extra space that we require. */ + if (G_UNLIKELY (reserve > strbuf->_priv_allocated - strbuf->_priv_len)) + _nm_str_buf_ensure_size (strbuf, strbuf->_priv_len + reserve, reserve_exact); +} + +/*****************************************************************************/ + +/** + * nm_str_buf_set_size: + * @strbuf: the initialized #NMStrBuf + * @new_len: the new length + * @honor_do_bzero_mem: if %TRUE, the shrinked memory will be cleared, if + * do_bzero_mem is set. This should be usually set to %TRUE, unless + * you know that the shrinked memory does not contain data that requires to be + * cleared. When growing the size, this value has no effect. + * @reserve_exact: when growing the buffer, reserve the exact amount of bytes. + * If %FALSE, the buffer may allocate more memory than requested to grow + * exponentially. + * + * This is like g_string_set_size(). If new_len is smaller than the + * current length, the string gets truncated (excess memory will be cleared). + * + * When extending the length, the added bytes are undefined (like with + * g_string_set_size(). Likewise, if you first pre-allocate a buffer with + * nm_str_buf_maybe_expand(), then write to the bytes, and finally set + * the appropriate size, then that works as expected (by not clearing the + * pre-existing, grown buffer). + */ +static inline void +nm_str_buf_set_size (NMStrBuf *strbuf, + gsize new_len, + gboolean honor_do_bzero_mem, + gboolean reserve_exact) +{ + _nm_str_buf_assert (strbuf); + + if (new_len < strbuf->_priv_len) { + if ( honor_do_bzero_mem + && strbuf->_priv_do_bzero_mem) { + /* we only clear the memory that we wrote to. */ + nm_explicit_bzero (&strbuf->_priv_str[new_len], strbuf->_priv_len - new_len); + } + } else if (new_len > strbuf->_priv_len) { + nm_str_buf_maybe_expand (strbuf, + new_len - strbuf->_priv_len + (reserve_exact ? 0u : 1u), + reserve_exact); + } else + return; + + strbuf->_priv_len = new_len; +} + +/*****************************************************************************/ + +static inline void +nm_str_buf_erase (NMStrBuf *strbuf, + gsize pos, + gssize len, + gboolean honor_do_bzero_mem) +{ + gsize new_len; + + _nm_str_buf_assert (strbuf); + + nm_assert (pos <= strbuf->_priv_len); + + if (len == 0) + return; + + if (len < 0) { + /* truncate the string before pos */ + nm_assert (len == -1); + new_len = pos; + } else { + gsize l = len; + + nm_assert (l <= strbuf->_priv_len - pos); + + new_len = strbuf->_priv_len - l; + if (pos + l < strbuf->_priv_len) { + memmove (&strbuf->_priv_str[pos], + &strbuf->_priv_str[pos + l], + strbuf->_priv_len - (pos + l)); + } + } + + nm_assert (new_len <= strbuf->_priv_len); + nm_str_buf_set_size (strbuf, new_len, honor_do_bzero_mem, TRUE); +} + +/*****************************************************************************/ + +static inline void +nm_str_buf_append_c (NMStrBuf *strbuf, + char ch) +{ + nm_str_buf_maybe_expand (strbuf, 2, FALSE); + strbuf->_priv_str[strbuf->_priv_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->_priv_str[strbuf->_priv_len++] = ch0; + strbuf->_priv_str[strbuf->_priv_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->_priv_str[strbuf->_priv_len++] = ch0; + strbuf->_priv_str[strbuf->_priv_len++] = ch1; + strbuf->_priv_str[strbuf->_priv_len++] = ch2; + strbuf->_priv_str[strbuf->_priv_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->_priv_str[strbuf->_priv_len], str, len); + strbuf->_priv_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); + +static inline void +nm_str_buf_ensure_trailing_c (NMStrBuf *strbuf, char ch) +{ + _nm_str_buf_assert (strbuf); + + if ( strbuf->_priv_len == 0 + || strbuf->_priv_str[strbuf->_priv_len - 1] != ch) + nm_str_buf_append_c (strbuf, ch); +} + +/*****************************************************************************/ + +static inline gboolean +nm_str_buf_is_initalized (NMStrBuf *strbuf) +{ + nm_assert (strbuf); +#if NM_MORE_ASSERTS + if (strbuf->_priv_str) + _nm_str_buf_assert (strbuf); +#endif + return !!strbuf->_priv_str; +} + +/** + * 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->_priv_str[strbuf->_priv_len] = '\0'; + return strbuf->_priv_str; +} + +static inline char * +nm_str_buf_get_str_unsafe (NMStrBuf *strbuf) +{ + _nm_str_buf_assert (strbuf); + return strbuf->_priv_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->_priv_str[strbuf->_priv_len] = '\0'; + + NM_SET_OUT (out_len, strbuf->_priv_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->_priv_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->_priv_str) + return; + _nm_str_buf_assert (strbuf); + if (strbuf->_priv_do_bzero_mem) + nm_explicit_bzero (strbuf->_priv_str, strbuf->_priv_len); + g_free (strbuf->_priv_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->_priv_str = NULL; +} + +#define nm_auto_str_buf nm_auto (nm_str_buf_destroy) + +#endif /* __NM_STR_BUF_H__ */ |