summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2020-06-29 09:52:31 +0200
committerThomas Haller <thaller@redhat.com>2020-07-03 10:34:27 +0200
commite2bd72235863504e899c173b510739ac24ff19a0 (patch)
tree5fe3efd7af6aa71e739048cc245bb324084ef058
parente7357419cd767aff36807ea8cabc0155271ddd88 (diff)
downloadNetworkManager-e2bd72235863504e899c173b510739ac24ff19a0.tar.gz
shared: refactor nm_utils_parse_next_line() and add tests
- add unit test for nm_utils_parse_next_line() - as line delimiter also accept "\r\n" and "\r" (beside "\n", "\0" and EOF). - fix returning lines with embedded "\0" characters. The line ends on the first "\n" or "\0", whatever comes first. The code before didn't ensure that with: line_end = memchr (line_start, '\n', *inout_len); if (!line_end) line_end = memchr (line_start, '\0', *inout_len);
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.c64
-rw-r--r--shared/nm-glib-aux/tests/test-shared-general.c104
2 files changed, 143 insertions, 25 deletions
diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c
index 1569662f8c..9fc4e510ac 100644
--- a/shared/nm-glib-aux/nm-shared-utils.c
+++ b/shared/nm-glib-aux/nm-shared-utils.c
@@ -1080,39 +1080,53 @@ nm_utils_parse_next_line (const char **inout_ptr,
const char **out_line,
gsize *out_line_len)
{
+ gboolean eol_is_carriage_return;
const char *line_start;
- const char *line_end;
+ gsize line_len;
- g_return_val_if_fail (inout_ptr, FALSE);
- g_return_val_if_fail (inout_len, FALSE);
- g_return_val_if_fail (out_line, FALSE);
+ nm_assert (inout_ptr);
+ nm_assert (inout_len);
+ nm_assert (*inout_len == 0 || *inout_ptr);
+ nm_assert (out_line);
+ nm_assert (out_line_len);
- if (*inout_len <= 0)
- goto error;
+ if (G_UNLIKELY (*inout_len == 0))
+ return FALSE;
line_start = *inout_ptr;
- line_end = memchr (line_start, '\n', *inout_len);
- if (!line_end)
- line_end = memchr (line_start, '\0', *inout_len);
- if (!line_end) {
- line_end = line_start + *inout_len;
- NM_SET_OUT (inout_len, 0);
- } else
- NM_SET_OUT (inout_len, *inout_len - (line_end - line_start) - 1);
- NM_SET_OUT (out_line, line_start);
- NM_SET_OUT (out_line_len, (gsize) (line_end - line_start));
+ eol_is_carriage_return = FALSE;
+ for (line_len = 0; ; line_len++) {
+ if (line_len >= *inout_len) {
+ /* if we consumed the entire line, we place the pointer at
+ * one character after the end. */
+ *inout_ptr = &line_start[line_len];
+ *inout_len = 0;
+ goto done;
+ }
+ switch (line_start[line_len]) {
+ case '\r':
+ eol_is_carriage_return = TRUE;
+ /* fall-through*/
+ case '\0':
+ case '\n':
+ *inout_ptr = &line_start[line_len + 1];
+ *inout_len = *inout_len - line_len - 1u;
+ if ( eol_is_carriage_return
+ && *inout_len > 0
+ && (*inout_ptr)[0] == '\n') {
+ /* also consume "\r\n" as one. */
+ (*inout_len)--;
+ (*inout_ptr)++;
+ }
+ goto done;
+ }
+ }
- if (*inout_len > 0)
- NM_SET_OUT (inout_ptr, line_end + 1);
- else
- NM_SET_OUT (inout_ptr, NULL);
+done:
+ *out_line = line_start;
+ *out_line_len = line_len;
return TRUE;
-
-error:
- NM_SET_OUT (out_line, NULL);
- NM_SET_OUT (out_line_len, 0);
- return FALSE;
}
/*****************************************************************************/
diff --git a/shared/nm-glib-aux/tests/test-shared-general.c b/shared/nm-glib-aux/tests/test-shared-general.c
index a435e28195..f389770a7a 100644
--- a/shared/nm-glib-aux/tests/test-shared-general.c
+++ b/shared/nm-glib-aux/tests/test-shared-general.c
@@ -773,6 +773,109 @@ test_nm_str_buf (void)
/*****************************************************************************/
+static void
+test_nm_utils_parse_next_line (void)
+{
+ const char *data;
+ const char *data0;
+ gsize data_len;
+ const char *line_start;
+ gsize line_len;
+ int i_run;
+ gsize j, k;
+
+ data = NULL;
+ data_len = 0;
+ g_assert (!nm_utils_parse_next_line (&data, &data_len, &line_start, &line_len));
+
+ for (i_run = 0; i_run < 1000; i_run++) {
+ gs_unref_ptrarray GPtrArray *strv = g_ptr_array_new_with_free_func (g_free);
+ gs_unref_ptrarray GPtrArray *strv2 = g_ptr_array_new_with_free_func (g_free);
+ gsize strv_len = nmtst_get_rand_word_length (NULL);
+ nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT (0, nmtst_get_rand_bool ());
+
+ /* create a list of random words. */
+ for (j = 0; j < strv_len; j++) {
+ gsize w_len = nmtst_get_rand_word_length (NULL);
+ NMStrBuf w_buf = NM_STR_BUF_INIT (nmtst_get_rand_uint32 () % (w_len + 1), nmtst_get_rand_bool ());
+
+ for (k = 0; k < w_len; k++)
+ nm_str_buf_append_c (&w_buf, '0' + (k % 10));
+ nm_str_buf_maybe_expand (&w_buf, 1, TRUE);
+ g_ptr_array_add (strv, nm_str_buf_finalize (&w_buf, NULL));
+ }
+
+ /* join the list of random words with (random) line delimiters
+ * ("\0", "\n", "\r" or EOF). */
+ for (j = 0; j < strv_len; j++) {
+ nm_str_buf_append (&strbuf, strv->pdata[j]);
+again:
+ switch (nmtst_get_rand_uint32 () % 5) {
+ case 0:
+ nm_str_buf_append_c (&strbuf, '\0');
+ break;
+ case 1:
+ if ( strbuf.len > 0
+ && (nm_str_buf_get_str_unsafe (&strbuf))[strbuf.len - 1] == '\r') {
+ /* the previous line was empty and terminated by "\r". We
+ * must not join with "\n". Retry. */
+ goto again;
+ }
+ nm_str_buf_append_c (&strbuf, '\n');
+ break;
+ case 2:
+ nm_str_buf_append_c (&strbuf, '\r');
+ break;
+ case 3:
+ nm_str_buf_append (&strbuf, "\r\n");
+ break;
+ case 4:
+ /* the last word randomly is delimited or not, but not if the last
+ * word is "". */
+ if (j + 1 < strv_len) {
+ /* it's not the last word. Retry. */
+ goto again;
+ }
+ g_assert (j == strv_len - 1);
+ if (((const char *) strv->pdata[j])[0] == '\0') {
+ /* if the last word was "", we need a delimiter (to parse it back).
+ * Retry. */
+ goto again;
+ }
+ /* The final delimiter gets omitted. It's EOF. */
+ break;
+ }
+ }
+
+ data0 = nm_str_buf_get_str_unsafe (&strbuf);
+ if ( !data0
+ && nmtst_get_rand_bool ()) {
+ nm_str_buf_maybe_expand (&strbuf, 1, TRUE);
+ data0 = nm_str_buf_get_str_unsafe (&strbuf);
+ g_assert (data0);
+ }
+ data_len = strbuf.len;
+ g_assert ((data_len > 0 && data0) || data_len == 0);
+ data = data0;
+ while (nm_utils_parse_next_line (&data, &data_len, &line_start, &line_len)) {
+ g_assert (line_start);
+ g_assert (line_start >= data0);
+ g_assert (line_start < &data0[strbuf.len]);
+ g_assert (!memchr (line_start, '\0', line_len));
+ g_ptr_array_add (strv2, g_strndup (line_start, line_len));
+ }
+ g_assert (data_len == 0);
+ if (data0)
+ g_assert (data == &data0[strbuf.len]);
+ else
+ g_assert (!data);
+
+ g_assert (_nm_utils_strv_cmp_n ((const char *const*) strv->pdata, strv->len, (const char *const*) strv2->pdata, strv2->len) == 0);
+ }
+}
+
+/*****************************************************************************/
+
NMTST_DEFINE ();
int main (int argc, char **argv)
@@ -794,6 +897,7 @@ int main (int argc, char **argv)
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);
g_test_add_func ("/general/test_nm_str_buf", test_nm_str_buf);
+ g_test_add_func ("/general/test_nm_utils_parse_next_line", test_nm_utils_parse_next_line);
return g_test_run ();
}