util: Introduce a parser for kernel cmdline arguments

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>
This commit is contained in:
Paulo de Rezende Pinatti 2020-06-15 10:28:06 +02:00 committed by Erik Skultety
parent 1eabe312ea
commit c5fffb959d
4 changed files with 358 additions and 0 deletions

View File

@ -3433,6 +3433,8 @@ virGetUserShell;
virHostGetDRMRenderNode;
virHostHasIOMMU;
virIndexToDiskName;
virKernelCmdlineMatchParam;
virKernelCmdlineNextParam;
virMemoryLimitIsSet;
virMemoryLimitTruncate;
virMemoryMaxValue;

View File

@ -1701,6 +1701,192 @@ virHostGetDRMRenderNode(void)
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;
}
/*
* Get a password from the console input stream.
* The caller must free the returned password.

View File

@ -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)

View File

@ -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, &param, &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;
}