summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2020-06-23 09:39:48 +0200
committerThomas Haller <thaller@redhat.com>2020-06-26 13:29:01 +0200
commit824ad6275df1f00daa57a002c46a87257ef218a2 (patch)
tree8ad108b7ce6a400a51541c6e224fd3d01503017a
parentfa56e52a4f07a33408d3f3b47bbbc80d63c03c60 (diff)
downloadNetworkManager-824ad6275df1f00daa57a002c46a87257ef218a2.tar.gz
libnm/match: extend syntax for match patterns with '|', '&', '!' and '\\'
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 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. This change is mostly backward compatible, except for existing matches that started with one of the special characters '|', '&', '!', and '\\'.
-rw-r--r--clients/common/settings-docs.h.in8
-rw-r--r--libnm-core/nm-setting-match.c43
-rw-r--r--src/nm-core-utils.c187
-rw-r--r--src/tests/test-core.c22
4 files changed, 163 insertions, 97 deletions
diff --git a/clients/common/settings-docs.h.in b/clients/common/settings-docs.h.in
index d9bb757515..7b891500d8 100644
--- a/clients/common/settings-docs.h.in
+++ b/clients/common/settings-docs.h.in
@@ -280,10 +280,10 @@
#define DESCRIBE_DOC_NM_SETTING_MACVLAN_PARENT N_("If given, specifies the parent interface name or parent connection UUID from which this MAC-VLAN interface should be created. If this property is not specified, the connection must contain an \"802-3-ethernet\" setting with a \"mac-address\" property.")
#define DESCRIBE_DOC_NM_SETTING_MACVLAN_PROMISCUOUS N_("Whether the interface should be put in promiscuous mode.")
#define DESCRIBE_DOC_NM_SETTING_MACVLAN_TAP N_("Whether the interface should be a MACVTAP.")
-#define DESCRIBE_DOC_NM_SETTING_MATCH_DRIVER N_("A list of driver names to match. Each element is a shell wildcard pattern. When an element is prefixed with exclamation mark (!) the condition is inverted. A candidate driver name is considered matching when both these conditions are satisfied: (a) any of the elements not prefixed with '!' matches or there aren't such elements; (b) none of the elements prefixed with '!' match.")
-#define DESCRIBE_DOC_NM_SETTING_MATCH_INTERFACE_NAME N_("A list of interface names to match. Each element is a shell wildcard pattern. When an element is prefixed with exclamation mark (!) the condition is inverted. A candidate interface name is considered matching when both these conditions are satisfied: (a) any of the elements not prefixed with '!' matches or there aren't such elements; (b) none of the elements prefixed with '!' match.")
-#define DESCRIBE_DOC_NM_SETTING_MATCH_KERNEL_COMMAND_LINE N_("A list of kernel command line arguments to match. This may be used to check whether a specific kernel command line option is set (or if prefixed with the exclamation mark unset). The argument must either be a single word, or an assignment (i.e. two words, separated \"=\"). In the former case the kernel command line is searched for the word appearing as is, or as left hand side of an assignment. In the latter case, the exact assignment is looked for with right and left hand side matching.")
-#define DESCRIBE_DOC_NM_SETTING_MATCH_PATH N_("A list of paths to match against the ID_PATH udev property of devices. ID_PATH represents the topological persistent path of a device. It typically contains a subsystem string (pci, usb, platform, etc.) and a subsystem-specific identifier. For PCI devices the path has the form \"pci-$domain:$bus:$device.$function\", where each variable is an hexadecimal value; for example \"pci-0000:0a:00.0\". The path of a device can be obtained with \"udevadm info /sys/class/net/$dev | grep ID_PATH=\" or by looking at the \"path\" property exported by NetworkManager (\"nmcli -f general.path device show $dev\"). Each element of the list is a shell wildcard pattern. When an element is prefixed with exclamation mark (!) the condition is inverted. A candidate path is considered matching when both these conditions are satisfied: (a) any of the elements not prefixed with '!' matches or there aren't such elements; (b) none of the elements prefixed with '!' match.")
+#define DESCRIBE_DOC_NM_SETTING_MATCH_DRIVER N_("A list of driver names to match. Each element is a shell wildcard pattern. See NMSettingMatch:interface-name for how special characters '|', '&', '!' and '\\' are used for optional and mandatory matches and inverting the pattern.")
+#define DESCRIBE_DOC_NM_SETTING_MATCH_INTERFACE_NAME N_("A list of interface names to match. Each element is a shell wildcard pattern. An element can be prefixed with a pipe symbol (|) or an ampersand (&). The former means that the element is optional and the latter means that it is mandatory. If there are any optional elements, than the match evaluates to true if at least one of the optional element matches (logical OR). If there are any mandatory elements, then they all must match (logical AND). By default, an element is optional. This means that an element \"foo\" behaves the same as \"|foo\". An element can also be inverted with exclamation mark (!) between the pipe symbol (or the ampersand) and before the pattern. Note that \"!foo\" is a shortcut for the mandatory match \"&!foo\". Finally, a backslash can be used at the beginning of the element (after the optional special characters) to escape the start of the pattern. For example, \"&\\!a\" is an mandatory match for literally \"!a\".")
+#define DESCRIBE_DOC_NM_SETTING_MATCH_KERNEL_COMMAND_LINE N_("A list of kernel command line arguments to match. This may be used to check whether a specific kernel command line option is set (or if prefixed with the exclamation mark unset). The argument must either be a single word, or an assignment (i.e. two words, separated \"=\"). In the former case the kernel command line is searched for the word appearing as is, or as left hand side of an assignment. In the latter case, the exact assignment is looked for with right and left hand side matching. See NMSettingMatch:interface-name for how special characters '|', '&', '!' and '\\' are used for optional and mandatory matches and inverting the pattern.")
+#define DESCRIBE_DOC_NM_SETTING_MATCH_PATH N_("A list of paths to match against the ID_PATH udev property of devices. ID_PATH represents the topological persistent path of a device. It typically contains a subsystem string (pci, usb, platform, etc.) and a subsystem-specific identifier. For PCI devices the path has the form \"pci-$domain:$bus:$device.$function\", where each variable is an hexadecimal value; for example \"pci-0000:0a:00.0\". The path of a device can be obtained with \"udevadm info /sys/class/net/$dev | grep ID_PATH=\" or by looking at the \"path\" property exported by NetworkManager (\"nmcli -f general.path device show $dev\"). Each element of the list is a shell wildcard pattern. See NMSettingMatch:interface-name for how special characters '|', '&', '!' and '\\' are used for optional and mandatory matches and inverting the pattern.")
#define DESCRIBE_DOC_NM_SETTING_OVS_BRIDGE_DATAPATH_TYPE N_("The data path type. One of \"system\", \"netdev\" or empty.")
#define DESCRIBE_DOC_NM_SETTING_OVS_BRIDGE_FAIL_MODE N_("The bridge failure mode. One of \"secure\", \"standalone\" or empty.")
#define DESCRIBE_DOC_NM_SETTING_OVS_BRIDGE_MCAST_SNOOPING_ENABLE N_("Enable or disable multicast snooping.")
diff --git a/libnm-core/nm-setting-match.c b/libnm-core/nm-setting-match.c
index 599fcf9916..940cc68dda 100644
--- a/libnm-core/nm-setting-match.c
+++ b/libnm-core/nm-setting-match.c
@@ -817,13 +817,19 @@ nm_setting_match_class_init (NMSettingMatchClass *klass)
* NMSettingMatch:interface-name
*
* A list of interface names to match. Each element is a shell wildcard
- * pattern. When an element is prefixed with exclamation mark (!) the
- * condition is inverted.
+ * pattern.
*
- * A candidate interface name is considered matching when both these
- * conditions are satisfied: (a) any of the elements not prefixed with '!'
- * matches or there aren't such elements; (b) none of the elements
- * prefixed with '!' match.
+ * An element can be prefixed with a pipe symbol (|) or an ampersand (&).
+ * The former means that the element is optional and the latter means that
+ * it is mandatory. If there are any optional elements, than the match
+ * evaluates to true if at least one of the optional element matches
+ * (logical OR). If there are any mandatory elements, then they all
+ * must match (logical AND). By default, an element is optional. This means
+ * that an element "foo" behaves the same as "|foo". An element can also be inverted
+ * with exclamation mark (!) between the pipe symbol (or the ampersand) and before
+ * the pattern. Note that "!foo" is a shortcut for the mandatory match "&!foo". Finally,
+ * a backslash can be used at the beginning of the element (after the optional special characters)
+ * to escape the start of the pattern. For example, "&\\!a" is an mandatory match for literally "!a".
*
* Since: 1.14
**/
@@ -845,6 +851,10 @@ nm_setting_match_class_init (NMSettingMatchClass *klass)
* of an assignment. In the latter case, the exact assignment is looked for
* with right and left hand side matching.
*
+ * See NMSettingMatch:interface-name for how special characters '|', '&',
+ * '!' and '\\' are used for optional and mandatory matches and inverting the
+ * pattern.
+ *
* Since: 1.26
**/
obj_properties[PROP_KERNEL_COMMAND_LINE] =
@@ -858,11 +868,10 @@ nm_setting_match_class_init (NMSettingMatchClass *klass)
* NMSettingMatch:driver
*
* A list of driver names to match. Each element is a shell wildcard pattern.
- * When an element is prefixed with exclamation mark (!) the condition is
- * inverted. A candidate driver name is considered matching when both these
- * conditions are satisfied: (a) any of the elements not prefixed with '!'
- * matches or there aren't such elements; (b) none of the elements prefixed
- * with '!' match.
+ *
+ * See NMSettingMatch:interface-name for how special characters '|', '&',
+ * '!' and '\\' are used for optional and mandatory matches and inverting the
+ * pattern.
*
* Since: 1.26
**/
@@ -873,7 +882,6 @@ nm_setting_match_class_init (NMSettingMatchClass *klass)
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS);
-
/**
* NMSettingMatch:path
*
@@ -891,14 +899,11 @@ nm_setting_match_class_init (NMSettingMatchClass *klass)
* property exported by NetworkManager ("nmcli -f general.path device
* show $dev").
*
- * Each element of the list is a shell wildcard pattern. When an
- * element is prefixed with exclamation mark (!) the condition is
- * inverted.
+ * Each element of the list is a shell wildcard pattern.
*
- * A candidate path is considered matching when both these
- * conditions are satisfied: (a) any of the elements not prefixed with '!'
- * matches or there aren't such elements; (b) none of the elements
- * prefixed with '!' match.
+ * See NMSettingMatch:interface-name for how special characters '|', '&',
+ * '!' and '\\' are used for optional and mandatory matches and inverting the
+ * pattern.
*
* Since: 1.26
**/
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;
diff --git a/src/tests/test-core.c b/src/tests/test-core.c
index d1bf28743e..099786ef68 100644
--- a/src/tests/test-core.c
+++ b/src/tests/test-core.c
@@ -968,10 +968,17 @@ test_wildcard_match (void)
do_test_wildcard_match ("b", TRUE, "!!a");
do_test_wildcard_match ("!a", FALSE, "!!a");
- do_test_wildcard_match ("\\", TRUE, "\\\\");
+ do_test_wildcard_match ("\\", TRUE, "\\\\\\");
do_test_wildcard_match ("\\\\", FALSE, "\\\\");
do_test_wildcard_match ("", FALSE, "\\\\");
+ do_test_wildcard_match ("\\a", TRUE, "\\\\\\a");
+ do_test_wildcard_match ("b", TRUE, "&!a");
+ do_test_wildcard_match ("a", FALSE, "&!a");
+ do_test_wildcard_match ("!a", TRUE, "&\\!a");
+ do_test_wildcard_match ("!a", TRUE, "|\\!a");
+ do_test_wildcard_match ("!a", TRUE, "\\!a");
+
do_test_wildcard_match ("name", FALSE, "name[123]");
do_test_wildcard_match ("name1", TRUE, "name[123]");
do_test_wildcard_match ("name2", TRUE, "name[123]");
@@ -979,6 +986,12 @@ test_wildcard_match (void)
do_test_wildcard_match ("name4", FALSE, "name[123]");
do_test_wildcard_match ("[a]", TRUE, "\\[a\\]");
+
+ do_test_wildcard_match ("aa", FALSE, "!a*");
+ do_test_wildcard_match ("aa", FALSE, "&!a*");
+ do_test_wildcard_match ("aa", FALSE, "|!a*");
+ do_test_wildcard_match ("aa", FALSE, "&!a*", "aa");
+ do_test_wildcard_match ("aa", TRUE, "|!a*", "aa");
}
static NMConnection *
@@ -2146,6 +2159,13 @@ test_kernel_cmdline_match_check (void)
_kernel_cmdline_match (TRUE, NM_MAKE_STRV ("a"), NM_MAKE_STRV ("a"));
_kernel_cmdline_match (TRUE, NM_MAKE_STRV ("a=b"), NM_MAKE_STRV ("a"));
_kernel_cmdline_match (TRUE, NM_MAKE_STRV ("a=b", "b"), NM_MAKE_STRV ("a", "b"));
+ _kernel_cmdline_match (TRUE, NM_MAKE_STRV ("a=b", "b"), NM_MAKE_STRV ("&a", "&b"));
+ _kernel_cmdline_match (FALSE, NM_MAKE_STRV ("a=b", "bc"), NM_MAKE_STRV ("&a", "&b"));
+ _kernel_cmdline_match (FALSE, NM_MAKE_STRV ("a=b", "b"), NM_MAKE_STRV ("&a", "&b", "c"));
+ _kernel_cmdline_match (TRUE, NM_MAKE_STRV ("a=b", "b"), NM_MAKE_STRV ("&a", "&b", "b", "c"));
+ _kernel_cmdline_match (TRUE, NM_MAKE_STRV ("a=b", "b", "c=dd"), NM_MAKE_STRV ("&a", "&b", "c"));
+ _kernel_cmdline_match (FALSE, NM_MAKE_STRV ("a", "b"), NM_MAKE_STRV ("a", "&c"));
+ _kernel_cmdline_match (TRUE, NM_MAKE_STRV ("a", "b"), NM_MAKE_STRV ("a", "|\\c"));
}
/*****************************************************************************/