| From 8b59b9dab54c094dfc9bafd9d9f2c18f25877f36 Mon Sep 17 00:00:00 2001 |
| Message-Id: <8b59b9dab54c094dfc9bafd9d9f2c18f25877f36@dist-git> |
| From: Paulo de Rezende Pinatti <ppinatti@linux.ibm.com> |
| Date: Wed, 24 Jun 2020 13:16:17 +0200 |
| Subject: [PATCH] util: Introduce a parser for kernel cmdline arguments |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| Introduce two utility functions to parse a kernel command |
| line string according to the kernel code parsing rules in |
| order to enable the caller to perform operations such as |
| verifying whether certain argument=value combinations are |
| present or retrieving an argument's value. |
| |
| Signed-off-by: Paulo de Rezende Pinatti <ppinatti@linux.ibm.com> |
| Signed-off-by: Boris Fiuczynski <fiuczy@linux.ibm.com> |
| Reviewed-by: Erik Skultety <eskultet@redhat.com> |
| (cherry picked from commit c5fffb959d93b83d87e70b21d19424e9722700b0) |
| |
| https://bugzilla.redhat.com/show_bug.cgi?id=1848997 |
| https://bugzilla.redhat.com/show_bug.cgi?id=1850351 |
| |
| Conflicts: |
| src/util/virutil.c |
| - unrelated commits db72866310d and ab36f729470 were not |
| backported |
| |
| Signed-off-by: Jiri Denemark <jdenemar@redhat.com> |
| Message-Id: <784fbc062d41f991b6321ac051b05e6c80a470cd.1592996194.git.jdenemar@redhat.com> |
| Reviewed-by: Ján Tomko <jtomko@redhat.com> |
| |
| src/libvirt_private.syms | 2 + |
| src/util/virutil.c | 185 +++++++++++++++++++++++++++++++++++++++ |
| src/util/virutil.h | 34 +++++++ |
| tests/utiltest.c | 136 ++++++++++++++++++++++++++++ |
| 4 files changed, 357 insertions(+) |
| |
| diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms |
| index a3fe49ae33..9e290c7bdf 100644 |
| |
| |
| @@ -3395,6 +3395,8 @@ virHexToBin; |
| virHostGetDRMRenderNode; |
| virHostHasIOMMU; |
| virIndexToDiskName; |
| +virKernelCmdlineMatchParam; |
| +virKernelCmdlineNextParam; |
| virMemoryLimitIsSet; |
| virMemoryLimitTruncate; |
| virMemoryMaxValue; |
| diff --git a/src/util/virutil.c b/src/util/virutil.c |
| index 261b2d2af6..17fd06dbb2 100644 |
| |
| |
| @@ -1673,3 +1673,188 @@ virHostGetDRMRenderNode(void) |
| VIR_DIR_CLOSE(driDir); |
| return ret; |
| } |
| + |
| + |
| +static const char *virKernelCmdlineSkipQuote(const char *cmdline, |
| + bool *is_quoted) |
| +{ |
| + if (cmdline[0] == '"') { |
| + *is_quoted = !(*is_quoted); |
| + cmdline++; |
| + } |
| + return cmdline; |
| +} |
| + |
| + |
| +/** |
| + * virKernelCmdlineFindEqual: |
| + * @cmdline: target kernel command line string |
| + * @is_quoted: indicates whether the string begins with quotes |
| + * @res: pointer to the position immediately after the parsed parameter, |
| + * can be used in subsequent calls to process further parameters until |
| + * the end of the string. |
| + * |
| + * Iterate over the provided kernel command line string while honoring |
| + * the kernel quoting rules and returns the index of the equal sign |
| + * separating argument and value. |
| + * |
| + * Returns 0 for the cases where no equal sign is found or the argument |
| + * itself begins with the equal sign (both cases indicating that the |
| + * argument has no value). Otherwise, returns the index of the equal |
| + * sign in the string. |
| + */ |
| +static size_t virKernelCmdlineFindEqual(const char *cmdline, |
| + bool is_quoted, |
| + const char **res) |
| +{ |
| + size_t i; |
| + size_t equal_index = 0; |
| + |
| + for (i = 0; cmdline[i]; i++) { |
| + if (!(is_quoted) && g_ascii_isspace(cmdline[i])) |
| + break; |
| + if (equal_index == 0 && cmdline[i] == '=') { |
| + equal_index = i; |
| + continue; |
| + } |
| + virKernelCmdlineSkipQuote(cmdline + i, &is_quoted); |
| + } |
| + *res = cmdline + i; |
| + return equal_index; |
| +} |
| + |
| + |
| +static char* virKernelArgNormalize(const char *arg) |
| +{ |
| + return virStringReplace(arg, "_", "-"); |
| +} |
| + |
| + |
| +/** |
| + * virKernelCmdlineNextParam: |
| + * @cmdline: kernel command line string to be checked for next parameter |
| + * @param: pointer to hold retrieved parameter, will be NULL if none found |
| + * @val: pointer to hold retrieved value of @param |
| + * |
| + * Parse the kernel cmdline and store the next parameter in @param |
| + * and the value of @param in @val which can be NULL if @param has |
| + * no value. In addition returns the address right after @param=@value |
| + * for possible further processing. |
| + * |
| + * Returns a pointer to address right after @param=@val in the |
| + * kernel command line, will point to the string's end (NULL) |
| + * in case no next parameter is found |
| + */ |
| +const char *virKernelCmdlineNextParam(const char *cmdline, |
| + char **param, |
| + char **val) |
| +{ |
| + const char *next; |
| + int equal_index; |
| + bool is_quoted = false; |
| + *param = NULL; |
| + *val = NULL; |
| + |
| + virSkipSpaces(&cmdline); |
| + cmdline = virKernelCmdlineSkipQuote(cmdline, &is_quoted); |
| + equal_index = virKernelCmdlineFindEqual(cmdline, is_quoted, &next); |
| + |
| + if (next == cmdline) |
| + return next; |
| + |
| + /* param has no value */ |
| + if (equal_index == 0) { |
| + if (is_quoted && next[-1] == '"') |
| + *param = g_strndup(cmdline, next - cmdline - 1); |
| + else |
| + *param = g_strndup(cmdline, next - cmdline); |
| + return next; |
| + } |
| + |
| + *param = g_strndup(cmdline, equal_index); |
| + |
| + if (cmdline[equal_index + 1] == '"') { |
| + is_quoted = true; |
| + equal_index++; |
| + } |
| + |
| + if (is_quoted && next[-1] == '"') |
| + *val = g_strndup(cmdline + equal_index + 1, |
| + next - cmdline - equal_index - 2); |
| + else |
| + *val = g_strndup(cmdline + equal_index + 1, |
| + next - cmdline - equal_index - 1); |
| + return next; |
| +} |
| + |
| + |
| +static bool virKernelCmdlineStrCmp(const char *kernel_val, |
| + const char *caller_val, |
| + virKernelCmdlineFlags flags) |
| +{ |
| + if (flags & VIR_KERNEL_CMDLINE_FLAGS_CMP_PREFIX) |
| + return STRPREFIX(kernel_val, caller_val); |
| + return STREQ(kernel_val, caller_val); |
| +} |
| + |
| + |
| +/** |
| + * virKernelCmdlineMatchParam: |
| + * @cmdline: kernel command line string to be checked for @arg |
| + * @arg: kernel command line argument |
| + * @values: array of possible values to match @arg |
| + * @len_values: size of array, it can be 0 meaning a match will be positive if |
| + * the argument has no value. |
| + * @flags: bitwise-OR of virKernelCmdlineFlags |
| + * |
| + * Try to match the provided kernel cmdline string with the provided @arg |
| + * and the list @values of possible values according to the matching strategy |
| + * defined in @flags. |
| + * |
| + * |
| + * Returns true if a match is found, false otherwise |
| + */ |
| +bool virKernelCmdlineMatchParam(const char *cmdline, |
| + const char *arg, |
| + const char **values, |
| + size_t len_values, |
| + virKernelCmdlineFlags flags) |
| +{ |
| + bool match = false; |
| + size_t i; |
| + const char *next = cmdline; |
| + g_autofree char *arg_norm = virKernelArgNormalize(arg); |
| + |
| + while (next[0] != '\0') { |
| + g_autofree char *kparam = NULL; |
| + g_autofree char *kparam_norm = NULL; |
| + g_autofree char *kval = NULL; |
| + |
| + next = virKernelCmdlineNextParam(next, &kparam, &kval); |
| + |
| + if (!kparam) |
| + break; |
| + |
| + kparam_norm = virKernelArgNormalize(kparam); |
| + |
| + if (STRNEQ(kparam_norm, arg_norm)) |
| + continue; |
| + |
| + if (!kval) { |
| + match = (len_values == 0) ? true : false; |
| + } else { |
| + match = false; |
| + for (i = 0; i < len_values; i++) { |
| + if (virKernelCmdlineStrCmp(kval, values[i], flags)) { |
| + match = true; |
| + break; |
| + } |
| + } |
| + } |
| + |
| + if (match && (flags & VIR_KERNEL_CMDLINE_FLAGS_SEARCH_FIRST)) |
| + break; |
| + } |
| + |
| + return match; |
| +} |
| diff --git a/src/util/virutil.h b/src/util/virutil.h |
| index 0dcaff79ac..f1d2ccdd1f 100644 |
| |
| |
| @@ -145,6 +145,40 @@ bool virHostHasIOMMU(void); |
| |
| char *virHostGetDRMRenderNode(void) G_GNUC_NO_INLINE; |
| |
| +/* Kernel cmdline match and comparison strategy for arg=value pairs */ |
| +typedef enum { |
| + /* substring comparison of argument values */ |
| + VIR_KERNEL_CMDLINE_FLAGS_CMP_PREFIX = 1, |
| + |
| + /* strict string comparison of argument values */ |
| + VIR_KERNEL_CMDLINE_FLAGS_CMP_EQ = 2, |
| + |
| + /* look for any occurrence of the argument with the expected value, |
| + * this should be used when an argument set to the expected value overrides |
| + * all the other occurrences of the argument, e.g. when looking for 'arg=1' |
| + * in 'arg=0 arg=1 arg=0' the search would succeed with this flag |
| + */ |
| + VIR_KERNEL_CMDLINE_FLAGS_SEARCH_FIRST = 4, |
| + |
| + /* look for the last occurrence of argument with the expected value, |
| + * this should be used when the last occurrence of the argument overrides |
| + * all the other ones, e.g. when looking for 'arg=1' in 'arg=0 arg=1' the |
| + * search would succeed with this flag, but in 'arg=1 arg=0' it would not, |
| + * because 'arg=0' overrides all the previous occurrences of 'arg' |
| + */ |
| + VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST = 8, |
| +} virKernelCmdlineFlags; |
| + |
| +const char *virKernelCmdlineNextParam(const char *cmdline, |
| + char **param, |
| + char **val); |
| + |
| +bool virKernelCmdlineMatchParam(const char *cmdline, |
| + const char *arg, |
| + const char **values, |
| + size_t len_values, |
| + virKernelCmdlineFlags flags); |
| + |
| /** |
| * VIR_ASSIGN_IS_OVERFLOW: |
| * @rvalue: value that is checked (evaluated twice) |
| diff --git a/tests/utiltest.c b/tests/utiltest.c |
| index 5ae04585cb..2bff7859dc 100644 |
| |
| |
| @@ -254,6 +254,140 @@ testOverflowCheckMacro(const void *data G_GNUC_UNUSED) |
| } |
| |
| |
| +struct testKernelCmdlineNextParamData |
| +{ |
| + const char *cmdline; |
| + const char *param; |
| + const char *val; |
| + const char *next; |
| +}; |
| + |
| +static struct testKernelCmdlineNextParamData kEntries[] = { |
| + { "arg1 arg2 arg3=val1", "arg1", NULL, " arg2 arg3=val1" }, |
| + { "arg1=val1 arg2 arg3=val3 arg4", "arg1", "val1", " arg2 arg3=val3 arg4" }, |
| + { "arg1=sub1=val1,sub2=val2 arg3=val3 arg4", "arg1", "sub1=val1,sub2=val2", " arg3=val3 arg4" }, |
| + { "arg3=val3 ", "arg3", "val3", " " }, |
| + { "arg3=val3", "arg3", "val3", "" }, |
| + { "arg-3=val3 arg4", "arg-3", "val3", " arg4" }, |
| + { " arg_3=val3 arg4", "arg_3", "val3", " arg4" }, |
| + { "arg2=\"value with space\" arg3=val3", "arg2", "value with space", " arg3=val3" }, |
| + { " arg2=\"value with space\" arg3=val3", "arg2", "value with space", " arg3=val3" }, |
| + { " \"arg2=value with space\" arg3=val3", "arg2", "value with space", " arg3=val3" }, |
| + { "arg2=\"val\"ue arg3", "arg2", "val\"ue", " arg3" }, |
| + { "arg2=value\" long\" arg3", "arg2", "value\" long\"", " arg3" }, |
| + { " \"arg2 with space=value with space\" arg3", "arg2 with space", "value with space", " arg3" }, |
| + { " arg2\" with space=val2\" arg3", "arg2\" with space", "val2\"", " arg3" }, |
| + { " arg2longer=someval\" long\" arg2=val2", "arg2longer", "someval\" long\"", " arg2=val2" }, |
| + { "=val1 arg2=val2", "=val1", NULL, " arg2=val2" }, |
| + { " ", NULL, NULL, "" }, |
| + { "", NULL, NULL, "" }, |
| +}; |
| + |
| +static int |
| +testKernelCmdlineNextParam(const void *data G_GNUC_UNUSED) |
| +{ |
| + const char *next; |
| + size_t i; |
| + |
| + for (i = 0; i < G_N_ELEMENTS(kEntries); ++i) { |
| + g_autofree char * param = NULL; |
| + g_autofree char * val = NULL; |
| + |
| + next = virKernelCmdlineNextParam(kEntries[i].cmdline, ¶m, &val); |
| + |
| + if (STRNEQ_NULLABLE(param, kEntries[i].param) || |
| + STRNEQ_NULLABLE(val, kEntries[i].val) || |
| + STRNEQ(next, kEntries[i].next)) { |
| + VIR_TEST_DEBUG("\nKernel cmdline [%s]", kEntries[i].cmdline); |
| + VIR_TEST_DEBUG("Expect param [%s]", kEntries[i].param); |
| + VIR_TEST_DEBUG("Actual param [%s]", param); |
| + VIR_TEST_DEBUG("Expect value [%s]", kEntries[i].val); |
| + VIR_TEST_DEBUG("Actual value [%s]", val); |
| + VIR_TEST_DEBUG("Expect next [%s]", kEntries[i].next); |
| + VIR_TEST_DEBUG("Actual next [%s]", next); |
| + |
| + return -1; |
| + } |
| + } |
| + |
| + return 0; |
| +} |
| + |
| + |
| +struct testKernelCmdlineMatchData |
| +{ |
| + const char *cmdline; |
| + const char *arg; |
| + const char *values[2]; |
| + virKernelCmdlineFlags flags; |
| + bool result; |
| +}; |
| + |
| +static struct testKernelCmdlineMatchData kMatchEntries[] = { |
| + {"arg1 myarg=no arg2=val2 myarg=yes arg4=val4 myarg=no arg5", "myarg", {"1", "y"}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_FIRST | VIR_KERNEL_CMDLINE_FLAGS_CMP_EQ, false }, |
| + {"arg1 myarg=no arg2=val2 myarg=yes arg4=val4 myarg=no arg5", "myarg", {"on", "yes"}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_FIRST | VIR_KERNEL_CMDLINE_FLAGS_CMP_EQ, true }, |
| + {"arg1 myarg=no arg2=val2 myarg=yes arg4=val4 myarg=no arg5", "myarg", {"1", "y"}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_FIRST | VIR_KERNEL_CMDLINE_FLAGS_CMP_PREFIX, true }, |
| + {"arg1 myarg=no arg2=val2 myarg=yes arg4=val4 myarg=no arg5", "myarg", {"a", "b"}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_FIRST | VIR_KERNEL_CMDLINE_FLAGS_CMP_PREFIX, false }, |
| + {"arg1 myarg=no arg2=val2 myarg=yes arg4=val4 myarg=no arg5", "myarg", {"on", "yes"}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST | VIR_KERNEL_CMDLINE_FLAGS_CMP_EQ, false }, |
| + {"arg1 myarg=no arg2=val2 myarg=yes arg4=val4 myarg=no arg5", "myarg", {"1", "y"}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST | VIR_KERNEL_CMDLINE_FLAGS_CMP_PREFIX, false }, |
| + {"arg1 myarg=no arg2=val2 arg4=val4 myarg=yes arg5", "myarg", {"on", "yes"}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST | VIR_KERNEL_CMDLINE_FLAGS_CMP_EQ, true }, |
| + {"arg1 myarg=no arg2=val2 arg4=val4 myarg=yes arg5", "myarg", {"1", "y"}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST | VIR_KERNEL_CMDLINE_FLAGS_CMP_PREFIX, true }, |
| + {"arg1 myarg=no arg2=val2 arg4=val4 myarg arg5", "myarg", {NULL, NULL}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST, true }, |
| + {"arg1 myarg arg2=val2 arg4=val4 myarg=yes arg5", "myarg", {NULL, NULL}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_FIRST, true }, |
| + {"arg1 myarg arg2=val2 arg4=val4 myarg=yes arg5", "myarg", {NULL, NULL}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST, false }, |
| + {"arg1 my-arg=no arg2=val2 arg4=val4 my_arg=yes arg5", "my-arg", {"on", "yes"}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST, true }, |
| + {"arg1 my-arg=no arg2=val2 arg4=val4 my_arg=yes arg5 ", "my-arg", {"on", "yes"}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST | VIR_KERNEL_CMDLINE_FLAGS_CMP_EQ, true }, |
| + {"arg1 my-arg arg2=val2 arg4=val4 my_arg=yes arg5", "my_arg", {NULL, NULL}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_FIRST, true }, |
| + {"arg1 my-arg arg2=val2 arg4=val4 my-arg=yes arg5", "my_arg", {NULL, NULL}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_FIRST, true }, |
| + {"=arg1 my-arg arg2=val2 arg4=val4 my-arg=yes arg5", "my_arg", {NULL, NULL}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_FIRST, true }, |
| + {"my-arg =arg1 arg2=val2 arg4=val4 my-arg=yes arg5", "=arg1", {NULL, NULL}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST, true }, |
| + {"arg1 arg2=val2 myarg=sub1=val1 arg5", "myarg", {"sub1=val1", NULL}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST, true }, |
| + {"arg1 arg2=", "arg2", {"", ""}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST | VIR_KERNEL_CMDLINE_FLAGS_CMP_EQ, true }, |
| + {" ", "myarg", {NULL, NULL}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST, false }, |
| + {"", "", {NULL, NULL}, VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST, false }, |
| +}; |
| + |
| + |
| +static int |
| +testKernelCmdlineMatchParam(const void *data G_GNUC_UNUSED) |
| +{ |
| + bool result; |
| + size_t i, lenValues; |
| + |
| + for (i = 0; i < G_N_ELEMENTS(kMatchEntries); ++i) { |
| + if (kMatchEntries[i].values[0] == NULL) |
| + lenValues = 0; |
| + else |
| + lenValues = G_N_ELEMENTS(kMatchEntries[i].values); |
| + |
| + result = virKernelCmdlineMatchParam(kMatchEntries[i].cmdline, |
| + kMatchEntries[i].arg, |
| + kMatchEntries[i].values, |
| + lenValues, |
| + kMatchEntries[i].flags); |
| + |
| + if (result != kMatchEntries[i].result) { |
| + VIR_TEST_DEBUG("\nKernel cmdline [%s]", kMatchEntries[i].cmdline); |
| + VIR_TEST_DEBUG("Kernel argument [%s]", kMatchEntries[i].arg); |
| + VIR_TEST_DEBUG("Kernel values [%s] [%s]", kMatchEntries[i].values[0], |
| + kMatchEntries[i].values[1]); |
| + if (kMatchEntries[i].flags & VIR_KERNEL_CMDLINE_FLAGS_CMP_PREFIX) |
| + VIR_TEST_DEBUG("Flag [VIR_KERNEL_CMDLINE_FLAGS_CMP_PREFIX]"); |
| + if (kMatchEntries[i].flags & VIR_KERNEL_CMDLINE_FLAGS_CMP_EQ) |
| + VIR_TEST_DEBUG("Flag [VIR_KERNEL_CMDLINE_FLAGS_CMP_EQ]"); |
| + if (kMatchEntries[i].flags & VIR_KERNEL_CMDLINE_FLAGS_SEARCH_FIRST) |
| + VIR_TEST_DEBUG("Flag [VIR_KERNEL_CMDLINE_FLAGS_SEARCH_FIRST]"); |
| + if (kMatchEntries[i].flags & VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST) |
| + VIR_TEST_DEBUG("Flag [VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST]"); |
| + VIR_TEST_DEBUG("Expect result [%d]", kMatchEntries[i].result); |
| + VIR_TEST_DEBUG("Actual result [%d]", result); |
| + |
| + return -1; |
| + } |
| + } |
| + |
| + return 0; |
| +} |
| |
| |
| static int |
| @@ -277,6 +411,8 @@ mymain(void) |
| DO_TEST(ParseVersionString); |
| DO_TEST(RoundValueToPowerOfTwo); |
| DO_TEST(OverflowCheckMacro); |
| + DO_TEST(KernelCmdlineNextParam); |
| + DO_TEST(KernelCmdlineMatchParam); |
| |
| return result == 0 ? EXIT_SUCCESS : EXIT_FAILURE; |
| } |
| -- |
| 2.27.0 |
| |