Verified Commit 3ced486f authored by Thomas Haller's avatar Thomas Haller
Browse files

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 '\\'.

(cherry picked from commit 824ad627)
parent 1149dff6
......@@ -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.")
......
......@@ -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
**/
......
......@@ -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;
......
......@@ -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"));
}
/*****************************************************************************/
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment