shared,core: merge branch 'th/strsplit-quoted-kernel-cmdline'

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/549
This commit is contained in:
Thomas Haller 2020-06-24 08:40:17 +02:00
commit 827dfacf0f
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
7 changed files with 400 additions and 17 deletions

View file

@ -13,6 +13,8 @@
#include "nm-std-aux/c-list-util.h"
#include "nm-glib-aux/nm-enum-utils.h"
#include "nm-glib-aux/nm-str-buf.h"
#include "systemd/nm-sd-utils-shared.h"
#include "nm-utils.h"
#include "nm-setting-private.h"
@ -8869,7 +8871,232 @@ test_connection_ovs_ifname (gconstpointer test_data)
}
}
/*****************************************************************************/
static gboolean
_strsplit_quoted_char_needs_escaping (char ch)
{
return NM_IN_SET (ch, '\'', '\"', '\\')
|| strchr (NM_ASCII_WHITESPACES, ch);
}
static char *
_strsplit_quoted_create_str_rand (gssize len)
{
NMStrBuf strbuf = NM_STR_BUF_INIT (nmtst_get_rand_uint32 () % 200, nmtst_get_rand_bool ());
g_assert (len >= -1);
if (len == -1)
len = nmtst_get_rand_word_length (NULL);
while (len-- > 0) {
char ch;
ch = nmtst_rand_select ('a', ' ', '\\', '"', '\'', nmtst_get_rand_uint32 () % 255 + 1);
g_assert (ch);
nm_str_buf_append_c (&strbuf, ch);
}
if (!strbuf.allocated)
nm_str_buf_maybe_expand (&strbuf, 1, nmtst_get_rand_bool ());
return nm_str_buf_finalize (&strbuf, NULL);
}
static char **
_strsplit_quoted_create_strv_rand (void)
{
guint len = nmtst_get_rand_word_length (NULL);
char **ptr;
guint i;
ptr = g_new (char *, len + 1);
for (i = 0; i < len; i++)
ptr[i] = _strsplit_quoted_create_str_rand (-1);
ptr[i] = NULL;
return ptr;
}
static char *
_strsplit_quoted_join_strv_rand (const char *const*strv)
{
NMStrBuf strbuf = NM_STR_BUF_INIT (nmtst_get_rand_uint32 () % 200, nmtst_get_rand_bool ());
char *result;
gsize l;
gsize l2;
gsize *p_l2 = nmtst_get_rand_bool () ? &l2 : NULL;
gsize i;
g_assert (strv);
nm_str_buf_append_c_repeated (&strbuf, ' ', nmtst_get_rand_word_length (NULL) / 4);
for (i = 0; strv[i]; i++) {
const char *s = strv[i];
gsize j;
char quote;
nm_str_buf_append_c_repeated (&strbuf, ' ', 1 + nmtst_get_rand_word_length (NULL) / 4);
j = 0;
quote = '\0';
while (TRUE) {
char ch = s[j++];
/* extract_first_word*/
if (quote != '\0') {
if (ch == '\0') {
nm_str_buf_append_c (&strbuf, quote);
break;
}
if ( ch == quote
|| ch == '\\'
|| nmtst_get_rand_uint32 () % 5 == 0)
nm_str_buf_append_c (&strbuf, '\\');
nm_str_buf_append_c (&strbuf, ch);
if (nmtst_get_rand_uint32 () % 3 == 0) {
nm_str_buf_append_c (&strbuf, quote);
quote = '\0';
goto next_maybe_quote;
}
continue;
}
if (ch == '\0') {
if (s == strv[i]) {
quote = nmtst_rand_select ('\'', '"');
nm_str_buf_append_c_repeated (&strbuf, quote, 2);
}
break;
}
if ( _strsplit_quoted_char_needs_escaping (ch)
|| nmtst_get_rand_uint32 () % 5 == 0)
nm_str_buf_append_c (&strbuf, '\\');
nm_str_buf_append_c (&strbuf, ch);
next_maybe_quote:
if (nmtst_get_rand_uint32 () % 5 == 0) {
quote = nmtst_rand_select ('\'', '\"');
nm_str_buf_append_c (&strbuf, quote);
if (nmtst_get_rand_uint32 () % 5 == 0) {
nm_str_buf_append_c (&strbuf, quote);
quote = '\0';
}
}
}
}
nm_str_buf_append_c_repeated (&strbuf, ' ', nmtst_get_rand_word_length (NULL) / 4);
nm_str_buf_maybe_expand (&strbuf, 1, nmtst_get_rand_bool ());
l = strbuf.len;
result = nm_str_buf_finalize (&strbuf, p_l2);
g_assert (!p_l2 || l == *p_l2);
g_assert (strlen (result) == l);
return result;
}
static void
_strsplit_quoted_assert_strv (const char *topic,
const char *str,
const char *const*strv1,
const char *const*strv2)
{
nm_auto_str_buf NMStrBuf s1 = { };
nm_auto_str_buf NMStrBuf s2 = { };
gs_free char *str_escaped = NULL;
int i;
g_assert (str);
g_assert (strv1);
g_assert (strv2);
if (_nm_utils_strv_equal ((char **) strv1, (char **) strv2))
return;
for (i = 0; strv1[i]; i++) {
gs_free char *s = g_strescape (strv1[i], NULL);
g_print (">>> [%s] strv1[%d] = \"%s\"\n", topic, i, s);
if (i > 0)
nm_str_buf_append_c (&s1, ' ');
nm_str_buf_append_printf (&s1, "\"%s\"", s);
}
for (i = 0; strv2[i]; i++) {
gs_free char *s = g_strescape (strv2[i], NULL);
g_print (">>> [%s] strv2[%d] = \"%s\"\n", topic, i, s);
if (i > 0)
nm_str_buf_append_c (&s2, ' ');
nm_str_buf_append_printf (&s2, "\"%s\"", s);
}
nm_str_buf_maybe_expand (&s1, 1, FALSE);
nm_str_buf_maybe_expand (&s2, 1, FALSE);
str_escaped = g_strescape (str, NULL);
g_error ("compared words differs: [%s] str=\"%s\"; strv1=%s; strv2=%s", topic, str_escaped, nm_str_buf_get_str (&s1), nm_str_buf_get_str (&s2));
}
static void
_strsplit_quoted_test (const char *str,
const char *const*strv_expected)
{
gs_strfreev char **strv_systemd = NULL;
gs_strfreev char **strv_nm = NULL;
int r;
g_assert (str);
r = nmtst_systemd_extract_first_word_all (str, &strv_systemd);
g_assert_cmpint (r, ==, 1);
g_assert (strv_systemd);
if (!strv_expected)
strv_expected = (const char *const*) strv_systemd;
_strsplit_quoted_assert_strv ("systemd", str, strv_expected, (const char *const*) strv_systemd);
strv_nm = nm_utils_strsplit_quoted (str);
g_assert (strv_nm);
_strsplit_quoted_assert_strv ("nm", str, strv_expected, (const char *const*) strv_nm);
}
static void
test_strsplit_quoted (void)
{
int i_run;
_strsplit_quoted_test ("", NM_MAKE_STRV ());
_strsplit_quoted_test (" ", NM_MAKE_STRV ());
_strsplit_quoted_test (" ", NM_MAKE_STRV ());
_strsplit_quoted_test (" \t", NM_MAKE_STRV ());
_strsplit_quoted_test ("a b", NM_MAKE_STRV ("a", "b"));
_strsplit_quoted_test ("a\\ b", NM_MAKE_STRV ("a b"));
_strsplit_quoted_test (" a\\ \"b\"", NM_MAKE_STRV ("a b"));
_strsplit_quoted_test (" a\\ \"b\" c \n", NM_MAKE_STRV ("a b", "c"));
for (i_run = 0; i_run < 1000; i_run++) {
gs_strfreev char **strv = NULL;
gs_free char *str = NULL;
/* create random strv array and join them carefully so that splitting
* them will yield the original value. */
strv = _strsplit_quoted_create_strv_rand ();
str = _strsplit_quoted_join_strv_rand ((const char *const*) strv);
_strsplit_quoted_test (str, (const char *const*) strv);
}
/* Create random words and assert that systemd and our implementation can
* both split them (and in the exact same way). */
for (i_run = 0; i_run < 1000; i_run++) {
gs_free char *s = _strsplit_quoted_create_str_rand (nmtst_get_rand_uint32 () % 150);
_strsplit_quoted_test (s, NULL);
}
}
/*****************************************************************************/
@ -9047,5 +9274,7 @@ int main (int argc, char **argv)
g_test_add_func ("/core/general/test_nm_ip_addr_zero", test_nm_ip_addr_zero);
g_test_add_func ("/core/general/test_strsplit_quoted", test_strsplit_quoted);
return g_test_run ();
}

View file

@ -2022,6 +2022,100 @@ nm_utils_escaped_tokens_options_split (char *str,
/*****************************************************************************/
/**
* nm_utils_strsplit_quoted:
* @str: the string to split (e.g. from /proc/cmdline).
*
* This basically does that systemd's extract_first_word() does
* with the flags "EXTRACT_UNQUOTE | EXTRACT_RELAX". This is what
* systemd uses to parse /proc/cmdline, and we do too.
*
* Splits the string. We have nm_utils_strsplit_set() which
* supports a variety of flags. However, extending that already
* complex code to also support quotation and escaping is hard.
* Instead, add a naive implementation.
*
* Returns: (transfer full): the split string.
*/
char **
nm_utils_strsplit_quoted (const char *str)
{
gs_unref_ptrarray GPtrArray *arr = NULL;
gs_free char *str_out = NULL;
guint8 ch_lookup[256];
nm_assert (str);
_char_lookup_table_init (ch_lookup, NM_ASCII_WHITESPACES);
for (;;) {
char quote;
gsize j;
while (_char_lookup_has (ch_lookup, str[0]))
str++;
if (str[0] == '\0')
break;
if (!str_out)
str_out = g_new (char, strlen (str) + 1);
quote = '\0';
j = 0;
for (;;) {
if (str[0] == '\\') {
str++;
if (str[0] == '\0')
break;
str_out[j++] = str[0];
str++;
continue;
}
if (quote) {
if (str[0] == '\0')
break;
if (str[0] == quote) {
quote = '\0';
str++;
continue;
}
str_out[j++] = str[0];
str++;
continue;
}
if (str[0] == '\0')
break;
if (NM_IN_SET (str[0], '\'', '"')) {
quote = str[0];
str++;
continue;
}
if (_char_lookup_has (ch_lookup, str[0])) {
str++;
break;
}
str_out[j++] = str[0];
str++;
}
if (!arr)
arr = g_ptr_array_new ();
g_ptr_array_add (arr, g_strndup (str_out, j));
}
if (!arr)
return g_new0 (char *, 1);
g_ptr_array_add (arr, NULL);
/* We want to return an optimally sized strv array, with no excess
* memory allocated. Hence, clone once more. */
return nm_memdup (arr->pdata, sizeof (char *) * arr->len);
}
/*****************************************************************************/
/**
* nm_utils_strv_find_first:
* @list: the strv list to search

View file

@ -681,6 +681,10 @@ nm_utils_escaped_tokens_escape_gstr (const char *str,
/*****************************************************************************/
char **nm_utils_strsplit_quoted (const char *str);
/*****************************************************************************/
static inline const char **
nm_utils_escaped_tokens_options_split_list (const char *str)
{

View file

@ -39,13 +39,18 @@ _nm_str_buf_assert (NMStrBuf *strbuf)
nm_assert (strbuf->_priv_len <= strbuf->_priv_allocated);
}
#define NM_STR_BUF_INIT(len, do_bzero_mem) \
((NMStrBuf) { \
._priv_str = (len) ? g_malloc (len) : NULL, \
._priv_allocated = (len), \
._priv_len = 0, \
._priv_do_bzero_mem = (do_bzero_mem), \
})
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,
@ -161,6 +166,19 @@ nm_str_buf_erase (NMStrBuf *strbuf,
/*****************************************************************************/
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)

View file

@ -137,3 +137,43 @@ nm_sd_http_url_is_valid_https (const char *url)
nm_assert (_http_url_is_valid (url, FALSE) == http_url_is_valid (url));
return _http_url_is_valid (url, TRUE);
}
/*****************************************************************************/
int
nmtst_systemd_extract_first_word_all (const char *str, char ***out_strv)
{
gs_unref_ptrarray GPtrArray *arr = NULL;
/* we implement a str split function to parse `/proc/cmdline`. This
* code should behave like systemd, which uses extract_first_word()
* for that.
*
* As we want to unit-test our implementation to match systemd,
* expose this function for testing. */
g_assert (out_strv);
g_assert (!*out_strv);
if (!str)
return 0;
arr = g_ptr_array_new_with_free_func (g_free);
for (;;) {
gs_free char *word = NULL;
int r;
r = extract_first_word (&str, &word, NULL, EXTRACT_UNQUOTE | EXTRACT_RELAX);
if (r < 0)
return r;
if (r == 0)
break;
g_ptr_array_add (arr, g_steal_pointer (&word));
}
g_ptr_array_add (arr, NULL);
*out_strv = (char **) g_ptr_array_free (g_steal_pointer (&arr), FALSE);
return 1;
}

View file

@ -38,4 +38,8 @@ gboolean nm_sd_hostname_is_valid(const char *s, bool allow_trailing_dot);
gboolean nm_sd_http_url_is_valid_https (const char *url);
/*****************************************************************************/
int nmtst_systemd_extract_first_word_all (const char *str, char ***out_strv);
#endif /* __NM_SD_UTILS_SHARED_H__ */

View file

@ -2778,19 +2778,13 @@ nm_utils_proc_cmdline_split (void)
again:
proc_cmdline = g_atomic_pointer_get (&proc_cmdline_cached);
if (G_UNLIKELY (!proc_cmdline)) {
gs_free const char **split = NULL;
gs_strfreev char **split = NULL;
/* FIXME(release-blocker): support quotation, like systemd's proc_cmdline_extract_first().
* For that, add a new NMUtilsStrsplitSetFlags flag. */
split = nm_utils_strsplit_set_full (nm_utils_proc_cmdline (),
NM_ASCII_WHITESPACES,
NM_UTILS_STRSPLIT_SET_FLAGS_NONE);
proc_cmdline = split
?: NM_PTRARRAY_EMPTY (const char *);
if (!g_atomic_pointer_compare_and_exchange (&proc_cmdline_cached, NULL, proc_cmdline))
split = nm_utils_strsplit_quoted (nm_utils_proc_cmdline ());
if (!g_atomic_pointer_compare_and_exchange (&proc_cmdline_cached, NULL, (gpointer) split))
goto again;
g_steal_pointer (&split);
proc_cmdline = (const char *const*) g_steal_pointer (&split);
}
return proc_cmdline;