// 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) == (strbuf->_priv_allocated > 0)); nm_assert (strbuf->_priv_len <= strbuf->_priv_allocated); } static inline NMStrBuf NM_STR_BUF_INIT (gsize allocated, gboolean do_bzero_mem) { NMStrBuf strbuf = { ._priv_str = allocated ? g_malloc (allocated) : NULL, ._priv_allocated = allocated, ._priv_len = 0, ._priv_do_bzero_mem = do_bzero_mem, }; return strbuf; } static inline void nm_str_buf_init (NMStrBuf *strbuf, gsize len, bool do_bzero_mem) { nm_assert (strbuf); *strbuf = NM_STR_BUF_INIT (len, 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); 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_repeated (NMStrBuf *strbuf, char ch, guint len) { if (len > 0) { nm_str_buf_maybe_expand (strbuf, len + 1, FALSE); do { strbuf->_priv_str[strbuf->_priv_len++] = ch; } while (--len > 0); } } 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. * If currently no buffer is allocated, this will return %NULL. */ static inline char * nm_str_buf_get_str (NMStrBuf *strbuf) { _nm_str_buf_assert (strbuf); if (!strbuf->_priv_str) return NULL; 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(). * Note that if no string is allocated yet (after nm_str_buf_init() with * length zero), this will return %NULL. */ static inline char * nm_str_buf_finalize (NMStrBuf *strbuf, gsize *out_len) { _nm_str_buf_assert (strbuf); NM_SET_OUT (out_len, strbuf->_priv_len); if (!strbuf->_priv_str) return NULL; nm_str_buf_maybe_expand (strbuf, 1, TRUE); strbuf->_priv_str[strbuf->_priv_len] = '\0'; /* 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__ */