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(a)linux.ibm.com>
Signed-off-by: Boris Fiuczynski <fiuczy(a)linux.ibm.com>
---
src/libvirt_private.syms | 2 +
src/util/virutil.c | 188 +++++++++++++++++++++++++++++++++++++++
src/util/virutil.h | 17 ++++
tests/utiltest.c | 144 ++++++++++++++++++++++++++++++
4 files changed, 351 insertions(+)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index a6af44fe1c..a206a943c5 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -3433,6 +3433,8 @@ virHostGetDRMRenderNode;
virHostHasIOMMU;
virIndexToDiskName;
virIsDevMapperDevice;
+virKernelCmdlineMatchParam;
+virKernelCmdlineNextParam;
virMemoryLimitIsSet;
virMemoryLimitTruncate;
virMemoryMaxValue;
diff --git a/src/util/virutil.c b/src/util/virutil.c
index fb46501142..b7ea643e4a 100644
--- a/src/util/virutil.c
+++ b/src/util/virutil.c
@@ -1725,6 +1725,194 @@ virHostGetDRMRenderNode(void)
return ret;
}
+
+static const char *virKernelCmdlineSkipQuote(const char *cmdline,
+ bool *is_quoted)
+{
+ if (cmdline[0] == '"') {
+ *is_quoted = !(*is_quoted);
+ cmdline++;
+ }
+ return cmdline;
+}
+
+
+/*
+ * 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.
+ *
+ * @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.
+ *
+ * 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, "_", "-");
+}
+
+
+/*
+ * 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.
+ *
+ * @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
+ *
+ * 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);
+}
+
+
+/*
+ * 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. Possible options include:
+ * - VIR_KERNEL_CMDLINE_FLAGS_CMP_PREFIX: do a substring comparison of values
+ * (uses size of value provided as input)
+ * - VIR_KERNEL_CMDLINE_FLAGS_CMP_EQ: do a strict string comparison of values,
+ * this is the default if no CMP flag was specified
+ * - VIR_KERNEL_CMDLINE_FLAGS_SEARCH_FIRST: first match satisfies search, no matter
+ * the order (in case of multiple argument occurrences)
+ * - VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST: use the result of last argument occurrence
+ * (in case of multiple argument occurrences)
+ *
+ * @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: flag mask defining the strategy for matching and comparing
+ *
+ * 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;
+}
+
+
/*
* Get a password from the console input stream.
* The caller must free the returned password.
diff --git a/src/util/virutil.h b/src/util/virutil.h
index 49b4bf440f..b1f6cfdfe4 100644
--- a/src/util/virutil.h
+++ b/src/util/virutil.h
@@ -147,6 +147,23 @@ bool virHostHasIOMMU(void);
char *virHostGetDRMRenderNode(void) G_GNUC_NO_INLINE;
+typedef enum {
+ VIR_KERNEL_CMDLINE_FLAGS_CMP_PREFIX = 1,
+ VIR_KERNEL_CMDLINE_FLAGS_CMP_EQ = 2,
+ VIR_KERNEL_CMDLINE_FLAGS_SEARCH_FIRST = 4,
+ 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..e6c1bb1050 100644
--- a/tests/utiltest.c
+++ b/tests/utiltest.c
@@ -254,6 +254,148 @@ 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)
+{
+ char *param = NULL;
+ char *val = NULL;
+ const char *next;
+ size_t i;
+
+ for (i = 0; i < G_N_ELEMENTS(kEntries); ++i) {
+ VIR_FREE(param);
+ VIR_FREE(val);
+
+ 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);
+
+ VIR_FREE(param);
+ VIR_FREE(val);
+
+ return -1;
+ }
+ }
+
+ VIR_FREE(param);
+ VIR_FREE(val);
+
+ 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 +419,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.26.2