[PATCH 1/6] util: introduce a parser for kernel cmdline arguments

Boris Fiuczynski fiuczy at linux.ibm.com
Mon May 11 16:41:56 UTC 2020


From: Paulo de Rezende Pinatti <ppinatti at linux.ibm.com>

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 at linux.ibm.com>
Reviewed-by: Boris Fiuczynski <fiuczy at linux.ibm.com>
---
 src/libvirt_private.syms |   2 +
 src/util/virutil.c       | 173 +++++++++++++++++++++++++++++++++++++++
 src/util/virutil.h       |  18 ++++
 tests/utiltest.c         | 130 +++++++++++++++++++++++++++++
 4 files changed, 323 insertions(+)

diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 935ef7303b..25b22a3e3f 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -3428,6 +3428,8 @@ virHostGetDRMRenderNode;
 virHostHasIOMMU;
 virIndexToDiskName;
 virIsDevMapperDevice;
+virKernelCmdlineGetValue;
+virKernelCmdlineMatchParam;
 virMemoryLimitIsSet;
 virMemoryLimitTruncate;
 virMemoryMaxValue;
diff --git a/src/util/virutil.c b/src/util/virutil.c
index fb46501142..264aae8d01 100644
--- a/src/util/virutil.c
+++ b/src/util/virutil.c
@@ -1725,6 +1725,179 @@ virHostGetDRMRenderNode(void)
     return ret;
 }
 
+
+#define VIR_IS_CMDLINE_SPACE(value) \
+    (g_ascii_isspace(value) || (unsigned char) value == 160)
+
+
+static bool virKernelArgsAreEqual(const char *arg1,
+                                  const char *arg2,
+                                  size_t n)
+{
+    size_t i;
+
+    for (i = 0; i < n; i++) {
+        if ((arg1[i] == '-' && arg2[i] == '_') ||
+            (arg1[i] == '_' && arg2[i] == '-') || (arg1[i] == arg2[i])) {
+            if (arg1[i] == '\0')
+                return true;
+            continue;
+        }
+        return false;
+    }
+    return true;
+}
+
+
+/*
+ * Parse the kernel cmdline and store the value of @arg in @val
+ * which can be NULL if @arg has no value or if it's not found.
+ * In addition, store in @next the address right after
+ * @arg=@value for possible further processing.
+ *
+ * @arg: kernel command line argument
+ * @cmdline: kernel command line string to be checked for @arg
+ * @val: pointer to hold retrieved value of @arg
+ * @next: pointer to hold address right after @arg=@val, will
+ * point to the string's end (NULL) in case @arg is not found
+ *
+ * Returns 0 if @arg is present in @cmdline, 1 otherwise
+ */
+int virKernelCmdlineGetValue(const char *arg,
+                             const char *cmdline,
+                             char **val,
+                             const char **next)
+{
+    size_t i = 0, param_i;
+    size_t arg_len = strlen(arg);
+    bool is_quoted, match;
+
+    *next = cmdline;
+    *val = NULL;
+
+    while (cmdline[i] != '\0') {
+        /* remove leading spaces */
+        while (VIR_IS_CMDLINE_SPACE(cmdline[i]))
+            i++;
+        if (cmdline[i] == '"') {
+            i++;
+            is_quoted = true;
+        } else {
+            is_quoted = false;
+        }
+        for (param_i = i; cmdline[param_i]; param_i++) {
+            if ((VIR_IS_CMDLINE_SPACE(cmdline[param_i]) && !is_quoted) ||
+                cmdline[param_i] == '=')
+                break;
+            if (cmdline[param_i] == '"')
+                is_quoted = !is_quoted;
+        }
+        if (param_i-i == arg_len && virKernelArgsAreEqual(cmdline+i, arg, arg_len))
+            match = true;
+        else
+            match = false;
+        /* arg has no value */
+        if (cmdline[param_i] != '=') {
+            if (match) {
+                *next = cmdline+param_i;
+                return 0;
+            }
+            i = param_i;
+            continue;
+        }
+        param_i++;
+        if (cmdline[param_i] == '\0')
+            break;
+
+        if (cmdline[param_i] == '"') {
+            param_i++;
+            is_quoted = !is_quoted;
+        }
+        i = param_i;
+
+        for (; cmdline[param_i]; param_i++) {
+            if (VIR_IS_CMDLINE_SPACE(cmdline[param_i]) && !is_quoted)
+                break;
+            if (cmdline[param_i] == '"')
+                is_quoted = !is_quoted;
+        }
+        if (match) {
+            *next = cmdline+param_i;
+            if (cmdline[param_i-1] == '"')
+                *val = g_strndup(cmdline+i, param_i-i-1);
+            else
+                *val = g_strndup(cmdline+i, param_i-i);
+            return 0;
+        }
+        i = param_i;
+    }
+    *next = cmdline+i;
+    return 1;
+}
+
+
+#define VIR_CMDLINE_STR_CMP(kernel_val, caller_val, flags) \
+    (((flags & VIR_KERNEL_CMDLINE_FLAGS_CMP_EQ) && \
+      STREQ(kernel_val, caller_val)) || ((flags & VIR_KERNEL_CMDLINE_FLAGS_CMP_PREFIX) && \
+                                         STRPREFIX(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
+ * - VIR_KERNEL_CMDLINE_FLAGS_SEARCH_STICKY: first positive match satifies search
+ *   (in case of multiple argument occurrences)
+ * - VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST: use the result of last argument occurence
+ *   (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 *kval = NULL;
+
+    while (next[0] != '\0') {
+        VIR_FREE(kval);
+        if (virKernelCmdlineGetValue(arg, next, &kval, &next) != 0)
+            break;
+        if (!kval) {
+            match = (len_values == 0) ? true : false;
+        } else {
+            match = false;
+            for (i = 0; i < len_values; i++) {
+                if (VIR_CMDLINE_STR_CMP(kval, values[i], flags)) {
+                    match = true;
+                    break;
+                }
+            }
+        }
+        if (match && (flags & VIR_KERNEL_CMDLINE_FLAGS_SEARCH_STICKY))
+            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..1edb415fbe 100644
--- a/src/util/virutil.h
+++ b/src/util/virutil.h
@@ -147,6 +147,24 @@ 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_STICKY = 4,
+    VIR_KERNEL_CMDLINE_FLAGS_SEARCH_LAST = 8,
+} virKernelCmdlineFlags;
+
+int virKernelCmdlineGetValue(const char *arg,
+                             const char *cmdline,
+                             char **val,
+                             const char **next);
+
+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..1edc6a728e 100644
--- a/tests/utiltest.c
+++ b/tests/utiltest.c
@@ -254,6 +254,134 @@ testOverflowCheckMacro(const void *data G_GNUC_UNUSED)
 }
 
 
+struct testKernelCmdlineGetValueData
+{
+    const char *cmdline;
+    const char *arg;
+    int rc;
+    const char *val;
+    size_t next;
+};
+
+static struct testKernelCmdlineGetValueData kEntries[] = {
+    { "arg1 arg2 arg3=val1",                            "arg4",           1, NULL,                19 },
+    { "arg1=val1 arg2 arg3=val3 arg4",                  "arg2",           0, NULL,                14 },
+    { "arg1=val1 arg2 arg3=val3 arg4",                  "arg3",           0, "val3",              24 },
+    { "arg1=val1 arg2 arg-3=val3 arg4",                 "arg_3",          0, "val3",              25 },
+    { "arg1=val1 arg2 arg_3=val3 arg4",                 "arg-3",          0, "val3",              25 },
+    { "arg1=val1 arg2 arg_3=val3 arg4",                 "arg_3",          0, "val3",              25 },
+    { "arg1=val1 arg2 arg-3=val3 arg4",                 "arg-3",          0, "val3",              25 },
+    { "arg1=val1 arg2=\"value with spaces\" arg3=val3", "arg2",           0, "value with spaces", 34 },
+    { "arg1=val1 arg2=\"value with spaces\" arg3=val3", "arg3",           0, "val3",              44 },
+    { "arg1=val1 \"arg2=value with spaces\" arg3=val3", "arg2",           0, "value with spaces", 34 },
+    { "arg1=val1 \"arg2=value with spaces\" arg3=val3", "arg3",           0, "val3",              44 },
+    { "arg1=val1 arg2=\"val\"ue arg3",                  "arg2",           0, "val\"ue",           22 },
+    { "arg1=val1 arg2=\"val\"ue arg3\" escaped=val2\"", "arg3\" escaped", 0, "val2",              42 },
+    { "arg1=val1 arg2longer=someval arg2=val2 arg3",    "arg2",           0, "val2",              38 },
+};
+
+static int
+testKernelCmdlineGetValue(const void *data G_GNUC_UNUSED)
+{
+    int rc;
+    char *val = NULL;
+    const char *next;
+    size_t i;
+
+    for (i = 0; i < G_N_ELEMENTS(kEntries); ++i) {
+        VIR_FREE(val);
+
+        rc = virKernelCmdlineGetValue(kEntries[i].arg, kEntries[i].cmdline,
+                                      &val, &next);
+
+        if (rc != kEntries[i].rc || STRNEQ_NULLABLE(val, kEntries[i].val) ||
+            (next - kEntries[i].cmdline) != kEntries[i].next) {
+            VIR_TEST_DEBUG("\nKernel cmdline [%s]", kEntries[i].cmdline);
+            VIR_TEST_DEBUG("Kernel argument [%s]", kEntries[i].arg);
+            VIR_TEST_DEBUG("Expect rc [%d]", kEntries[i].rc);
+            VIR_TEST_DEBUG("Actual rc [%d]", rc);
+            VIR_TEST_DEBUG("Expect value [%s]", kEntries[i].val);
+            VIR_TEST_DEBUG("Actual value [%s]", val);
+            VIR_TEST_DEBUG("Expect next index [%lu]", kEntries[i].next);
+            VIR_TEST_DEBUG("Actual next index [%lu]",
+                           (size_t)(next - kEntries[i].cmdline));
+
+            VIR_FREE(val);
+
+            return -1;
+        }
+    }
+
+    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_STICKY | 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_STICKY | 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_STICKY | 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_STICKY | 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_STICKY,                                       true },
+    {"arg1 myarg arg2=val2 arg4=val4 myarg=yes arg5",             "myarg", {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_STICKY)
+                VIR_TEST_DEBUG("Flag [VIR_KERNEL_CMDLINE_FLAGS_SEARCH_STICKY]");
+            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 +405,8 @@ mymain(void)
     DO_TEST(ParseVersionString);
     DO_TEST(RoundValueToPowerOfTwo);
     DO_TEST(OverflowCheckMacro);
+    DO_TEST(KernelCmdlineGetValue);
+    DO_TEST(KernelCmdlineMatchParam);
 
     return result == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
 }
-- 
2.25.1




More information about the libvir-list mailing list