diff options
author | Thomas Haller <thaller@redhat.com> | 2020-06-23 09:39:48 +0200 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2020-06-26 10:21:40 +0200 |
commit | 843d01584a8a78c506cf5b34e95e38e5d019f4d5 (patch) | |
tree | ad96191bb4f765bec5389381cc080ef6bff82e34 /src/nm-core-utils.c | |
parent | 999f5920d777664dfc2827fd678a64c69f5e0b41 (diff) | |
download | NetworkManager-th/kernel-cmdline-match.tar.gz |
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\'th/kernel-cmdline-match
For simple matches like match.interface-name, match.driver, and
match.path, arguably what we had was fine. There each element
(like "eth*") is a wildcard for a single name (like "eth1").
However, for match.kernel-command-line, the elements match individual
command line options, so we should have more flexibility of whether
a parameter is optional or mandatory. Extend the syntax (in a backward
compatible manner) for that.
- the elements can now be prefixed by either '|' or '&'. This makes
optional or mandatory elements, respectively. The entire match
evaluates to true if all mandatory elements match (if any) and
at least one of the optional elements (if any).
As before, if neither '|' nor '&' is specified, then the element
is optional (that means, "foo" is the same as "|foo").
- the exclamation mark is still used to invert the match. If used
alone (like "!foo") it is a shortcut for defining a mandatory match
("&!foo").
- the backslash can now be used to escape the special characters
above. Basically, the special characters ('|', '&', '!') are
stripped from the start of the element. If what is left afterwards
is a backslash, it also gets stripped and the remainder is the
pattern. For example, "\\&foo" has the pattern "&foo" where
'&' is no longer treated specially. This special handling of
the backslash is only done at the beginning of the element (after
the optional special characters). The remaining string is part
of the pattern, where backslashes might have their own meaning.
Diffstat (limited to 'src/nm-core-utils.c')
-rw-r--r-- | src/nm-core-utils.c | 187 |
1 files changed, 114 insertions, 73 deletions
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c index 8019775a96..f5699b7e95 100644 --- a/src/nm-core-utils.c +++ b/src/nm-core-utils.c @@ -1698,30 +1698,109 @@ nm_match_spec_join (GSList *specs) return g_string_free (str, FALSE); } +static void +_pattern_parse (const char *input, + const char **out_pattern, + gboolean *out_is_inverted, + gboolean *out_is_mandatory) +{ + gboolean is_inverted = FALSE; + gboolean is_mandatory = FALSE; + + if (input[0] == '&') { + input++; + is_mandatory = TRUE; + if (input[0] == '!') { + input++; + is_inverted = TRUE; + } + goto out; + } + + if (input[0] == '|') { + input++; + if (input[0] == '!') { + input++; + is_inverted = TRUE; + } + goto out; + } + + if (input[0] == '!') { + input++; + is_inverted = TRUE; + is_mandatory = TRUE; + goto out; + } + +out: + if (input[0] == '\\') + input++; + + *out_pattern = input; + *out_is_inverted = is_inverted; + *out_is_mandatory = is_mandatory; +} + gboolean nm_wildcard_match_check (const char *str, const char *const *patterns, guint num_patterns) { - gsize i, neg = 0; + gboolean has_optional = FALSE; + gboolean has_any_optional = FALSE; + guint i; for (i = 0; i < num_patterns; i++) { - if (patterns[i][0] == '!') { - neg++; - if (!str) - continue; - if (!fnmatch (patterns[i] + 1, str, 0)) + gboolean is_inverted; + gboolean is_mandatory; + gboolean match; + const char *p; + + _pattern_parse (patterns[i], &p, &is_inverted, &is_mandatory); + + match = (fnmatch (p, str, 0) == 0); + if (is_inverted) + match = !match; + + if (is_mandatory) { + if (!match) return FALSE; + } else { + has_any_optional = TRUE; + if (match) + has_optional = TRUE; } } - if (neg == num_patterns) - return TRUE; + return has_optional + || !has_any_optional; +} - if (str) { - for (i = 0; i < num_patterns; i++) { - if ( patterns[i][0] != '!' - && !fnmatch (patterns[i], str, 0)) +/*****************************************************************************/ + +static gboolean +_kernel_cmdline_match (const char *const*proc_cmdline, + const char *pattern) +{ + + if (proc_cmdline) { + gboolean has_equal = (!!strchr (pattern, '=')); + gsize pattern_len = strlen (pattern); + + for (; proc_cmdline[0]; proc_cmdline++) { + const char *c = proc_cmdline[0]; + + if (has_equal) { + /* if pattern contains '=' compare full key=value */ + if (nm_streq (c, pattern)) + return TRUE; + continue; + } + + /* otherwise consider pattern as key only */ + if ( strncmp (c, pattern, pattern_len) == 0 + && NM_IN_SET (c[pattern_len], '\0', '=')) return TRUE; } } @@ -1729,86 +1808,48 @@ nm_wildcard_match_check (const char *str, return FALSE; } -/*****************************************************************************/ - gboolean nm_utils_kernel_cmdline_match_check (const char *const*proc_cmdline, const char *const*patterns, guint num_patterns, GError **error) { - gboolean pos_patterns = FALSE; + gboolean has_optional = FALSE; + gboolean has_any_optional = FALSE; guint i; for (i = 0; i < num_patterns; i++) { - const char *patterns_i = patterns[i]; - const char *const*proc_cmdline_i; - gboolean negative = FALSE; - gboolean found = FALSE; - const char *equal; - - if (patterns_i[0] == '!') { - ++patterns_i; - negative = TRUE; - } else - pos_patterns = TRUE; + const char *element = patterns[i]; + gboolean is_inverted = FALSE; + gboolean is_mandatory = FALSE; + gboolean match; + const char *p; - equal = strchr (patterns_i, '='); + _pattern_parse (element, &p, &is_inverted, &is_mandatory); - proc_cmdline_i = proc_cmdline; - while (*proc_cmdline_i) { - if (equal) { - /* if pattern contains = compare full key=value */ - found = nm_streq (*proc_cmdline_i, patterns_i); - } else { - gsize l = strlen (patterns_i); + match = _kernel_cmdline_match (proc_cmdline, p); + if (is_inverted) + match = !match; - /* otherwise consider pattern as key only */ - if ( strncmp (*proc_cmdline_i, patterns_i, l) == 0 - && NM_IN_SET ((*proc_cmdline_i)[l], '\0', '=')) - found = TRUE; - } - if ( found - && negative) { - /* first negative match */ + if (is_mandatory) { + if (!match) { nm_utils_error_set (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, "device does not satisfy match.kernel-command-line property %s", patterns[i]); return FALSE; } - proc_cmdline_i++; + } else { + has_any_optional = TRUE; + if (match) + has_optional = TRUE; } + } - /* FIXME(release-blocker): match.interface-name and match.driver have the meaning, - * that any of the matches may yield success. For match.kernel-command-line, we - * do here that all must match. This inconsistency is undesired. - * - * 1) improve gtk-doc documentation explaining how these options match. - * - * 2) possibly unify the behavior so that kernel-command-line behaves like other - * matches (and ANY may match). Note that this would be contrary to systemd's - * Conditions, which by default requires that ALL conditions match (AND). We - * should be consistent within our match options, and not with systemd here. - * - * 2b) Note that systemd supports special token like "=|", to indicate that - * ANY behavior. If we want, we could also introduce two special prefixes - * "&..." and "|...", to support either. It's slightly complicated how - * these work in combinations with "!". - * Unless we fully decide what we do about this, NMSettingMatch.verify() should - * reject matches that start with '&' or '|', because these will be reserved for - * future use. - * - * 3) while fixing this, this code should move to a separate function so we - * can unit test the match of kernel command lines. - */ - if ( pos_patterns - && !found) { - /* positive patterns configured but no match */ - nm_utils_error_set (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, - "device does not satisfy any match.kernel-command-line property %s...", - patterns[0]); - return FALSE; - } + if ( !has_optional + && has_any_optional) { + nm_utils_error_set (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "device does not satisfy any match.kernel-command-line property"); + return FALSE; } return TRUE; |