shared: add nm_utils_strsplit_quoted()

We want to parse "/proc/cmdline". That is space separated with support
for quoting and escaping. Our implementation becomes part of stable
behavior, and we should interpret the kernel command line the same way
as the system does. That means, our implementation should match
systemd's.
This commit is contained in:
Thomas Haller
2020-06-22 19:43:35 +02:00
parent 2a6ecf2128
commit 10779d545a
3 changed files with 327 additions and 0 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)
{