/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
typedef enum {
/* Listed in order of checking. Note that some comparators are prefixes of others, hence the longest
* should be listed first. */
+ _ORDER_FNMATCH_FIRST,
+ ORDER_FNMATCH_EQUAL = _ORDER_FNMATCH_FIRST,
+ ORDER_FNMATCH_UNEQUAL,
+ _ORDER_FNMATCH_LAST = ORDER_FNMATCH_UNEQUAL,
ORDER_LOWER_OR_EQUAL,
ORDER_GREATER_OR_EQUAL,
ORDER_LOWER,
_ORDER_INVALID = -EINVAL,
} OrderOperator;
-static OrderOperator parse_order(const char **s) {
+static OrderOperator parse_order(const char **s, bool allow_fnmatch) {
static const char *const prefix[_ORDER_MAX] = {
+ [ORDER_FNMATCH_EQUAL] = "=$",
+ [ORDER_FNMATCH_UNEQUAL] = "!=$",
[ORDER_LOWER_OR_EQUAL] = "<=",
[ORDER_GREATER_OR_EQUAL] = ">=",
[ORDER_LOWER] = "<",
e = startswith(*s, prefix[i]);
if (e) {
+ if (!allow_fnmatch && (i >= _ORDER_FNMATCH_FIRST && i <= _ORDER_FNMATCH_LAST))
+ break;
*s = e;
return i;
}
break;
s = strstrip(word);
- order = parse_order(&s);
+ order = parse_order(&s, /* allow_fnmatch= */ false);
if (order >= 0) {
s += strspn(s, WHITESPACE);
if (isempty(s)) {
"Failed to parse parameter, key/value format expected: %m");
/* Do not allow whitespace after the separator, as that's not a valid os-release format */
- order = parse_order(&word);
+ order = parse_order(&word, /* allow_fnmatch= */ false);
if (order < 0 || isempty(word) || strchr(WHITESPACE, *word) != NULL)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Failed to parse parameter, key/value format expected: %m");
m = physical_memory();
p = c->parameter;
- order = parse_order(&p);
+ order = parse_order(&p, /* allow_fnmatch= */ false);
if (order < 0)
order = ORDER_GREATER_OR_EQUAL; /* default to >= check, if nothing is specified. */
return log_debug_errno(n, "Failed to determine CPUs in affinity mask: %m");
p = c->parameter;
- order = parse_order(&p);
+ order = parse_order(&p, /* allow_fnmatch= */ false);
if (order < 0)
order = ORDER_GREATER_OR_EQUAL; /* default to >= check, if nothing is specified. */
return strv_contains(dtcompatlist, dtcarg);
}
+static int condition_test_firmware_smbios_field(const char *expression) {
+ _cleanup_free_ char *field = NULL, *expected_value = NULL, *actual_value = NULL;
+ OrderOperator operator;
+ int r;
+
+ assert(expression);
+
+ /* Parse SMBIOS field */
+ r = extract_first_word(&expression, &field, "!<=>$", EXTRACT_RETAIN_SEPARATORS);
+ if (r < 0)
+ return r;
+ if (r == 0 || isempty(expression))
+ return -EINVAL;
+
+ /* Remove trailing spaces from SMBIOS field */
+ delete_trailing_chars(field, WHITESPACE);
+
+ /* Parse operator */
+ operator = parse_order(&expression, /* allow_fnmatch= */ true);
+ if (operator < 0)
+ return operator;
+
+ /* Parse expected value */
+ r = extract_first_word(&expression, &expected_value, NULL, EXTRACT_UNQUOTE);
+ if (r < 0)
+ return r;
+ if (r == 0 || !isempty(expression))
+ return -EINVAL;
+
+ /* Read actual value from sysfs */
+ if (!filename_is_valid(field))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid SMBIOS field name");
+
+ const char *p = strjoina("/sys/class/dmi/id/", field);
+ r = read_virtual_file(p, SIZE_MAX, &actual_value, NULL);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to read %s: %m", p);
+ if (r == -ENOENT)
+ return false;
+ return r;
+ }
+
+ /* Remove trailing newline */
+ delete_trailing_chars(actual_value, WHITESPACE);
+
+ /* Finally compare actual and expected value */
+ if (operator == ORDER_FNMATCH_EQUAL)
+ return fnmatch(expected_value, actual_value, FNM_EXTMATCH) != FNM_NOMATCH;
+ if (operator == ORDER_FNMATCH_UNEQUAL)
+ return fnmatch(expected_value, actual_value, FNM_EXTMATCH) == FNM_NOMATCH;
+ return test_order(strverscmp_improved(actual_value, expected_value), operator);
+}
+
static int condition_test_firmware(Condition *c, char **env) {
- sd_char *dtc;
+ sd_char *arg;
+ int r;
assert(c);
assert(c->parameter);
return false;
} else
return true;
- } else if ((dtc = startswith(c->parameter, "device-tree-compatible("))) {
- _cleanup_free_ char *dtcarg = NULL;
+ } else if ((arg = startswith(c->parameter, "device-tree-compatible("))) {
+ _cleanup_free_ char *dtc_arg = NULL;
char *end;
- end = strchr(dtc, ')');
+ end = strchr(arg, ')');
if (!end || *(end + 1) != '\0') {
- log_debug("Malformed Firmware condition \"%s\"", c->parameter);
+ log_debug("Malformed ConditionFirmware=%s", c->parameter);
return false;
}
- dtcarg = strndup(dtc, end - dtc);
- if (!dtcarg)
+ dtc_arg = strndup(arg, end - arg);
+ if (!dtc_arg)
return -ENOMEM;
- return condition_test_firmware_devicetree_compatible(dtcarg);
+ return condition_test_firmware_devicetree_compatible(dtc_arg);
} else if (streq(c->parameter, "uefi"))
return is_efi_boot();
- else {
+ else if ((arg = startswith(c->parameter, "smbios-field("))) {
+ _cleanup_free_ char *smbios_arg = NULL;
+ char *end;
+
+ end = strchr(arg, ')');
+ if (!end || *(end + 1) != '\0')
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed ConditionFirmware=%s: %m", c->parameter);
+
+ smbios_arg = strndup(arg, end - arg);
+ if (!smbios_arg)
+ return log_oom_debug();
+
+ r = condition_test_firmware_smbios_field(smbios_arg);
+ if (r < 0)
+ return log_debug_errno(r, "Malformed ConditionFirmware=%s: %m", c->parameter);
+ return r;
+ } else {
log_debug("Unsupported Firmware condition \"%s\"", c->parameter);
return false;
}
#include "efi-loader.h"
#include "env-util.h"
#include "errno-util.h"
+#include "fileio.h"
#include "fs-util.h"
#include "hostname-util.h"
#include "id128-util.h"
condition_free(condition);
}
+TEST(condition_test_firmware_smbios_field) {
+ _cleanup_free_ char *bios_vendor = NULL, *bios_version = NULL;
+ const char *expression;
+ Condition *condition;
+
+ /* Test some malformed smbios-field arguments */
+ condition = condition_new(CONDITION_FIRMWARE, "smbios-field()", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == -EINVAL);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed)", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == -EINVAL);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == -EINVAL);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed=)", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == -EINVAL);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed=)", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == -EINVAL);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_FIRMWARE, "smbios-field(not_existing=nothing garbage)", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == -EINVAL);
+ condition_free(condition);
+
+ /* Test not existing SMBIOS field */
+ condition = condition_new(CONDITION_FIRMWARE, "smbios-field(not_existing=nothing)", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == 0);
+ condition_free(condition);
+
+ /* Test with bios_vendor, if available */
+ if (read_virtual_file("/sys/class/dmi/id/bios_vendor", SIZE_MAX, &bios_vendor, NULL) <= 0)
+ return;
+
+ /* remove trailing newline */
+ strstrip(bios_vendor);
+
+ /* Check if the bios_vendor contains any spaces we should quote */
+ const char *quote = strchr(bios_vendor, ' ') ? "\"" : "";
+
+ /* Test equality / inequality using fnmatch() */
+ expression = strjoina("smbios-field(bios_vendor =$ ", quote, bios_vendor, quote, ")");
+ condition = condition_new(CONDITION_FIRMWARE, expression, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ));
+ condition_free(condition);
+
+ expression = strjoina("smbios-field(bios_vendor=$", quote, bios_vendor, quote, ")");
+ condition = condition_new(CONDITION_FIRMWARE, expression, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ));
+ condition_free(condition);
+
+ expression = strjoina("smbios-field(bios_vendor !=$ ", quote, bios_vendor, quote, ")");
+ condition = condition_new(CONDITION_FIRMWARE, expression, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == 0);
+ condition_free(condition);
+
+ expression = strjoina("smbios-field(bios_vendor!=$", quote, bios_vendor, quote, ")");
+ condition = condition_new(CONDITION_FIRMWARE, expression, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == 0);
+ condition_free(condition);
+
+ expression = strjoina("smbios-field(bios_vendor =$ ", quote, bios_vendor, "*", quote, ")");
+ condition = condition_new(CONDITION_FIRMWARE, expression, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ));
+ condition_free(condition);
+
+ /* Test version comparison with bios_version, if available */
+ if (read_virtual_file("/sys/class/dmi/id/bios_version", SIZE_MAX, &bios_version, NULL) <= 0)
+ return;
+
+ /* remove trailing newline */
+ strstrip(bios_version);
+
+ /* Check if the bios_version contains any spaces we should quote */
+ quote = strchr(bios_version, ' ') ? "\"" : "";
+
+ expression = strjoina("smbios-field(bios_version = ", quote, bios_version, quote, ")");
+ condition = condition_new(CONDITION_FIRMWARE, expression, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ));
+ condition_free(condition);
+
+ expression = strjoina("smbios-field(bios_version != ", quote, bios_version, quote, ")");
+ condition = condition_new(CONDITION_FIRMWARE, expression, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == 0);
+ condition_free(condition);
+
+ expression = strjoina("smbios-field(bios_version <= ", quote, bios_version, quote, ")");
+ condition = condition_new(CONDITION_FIRMWARE, expression, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ));
+ condition_free(condition);
+
+ expression = strjoina("smbios-field(bios_version >= ", quote, bios_version, quote, ")");
+ condition = condition_new(CONDITION_FIRMWARE, expression, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ));
+ condition_free(condition);
+
+ expression = strjoina("smbios-field(bios_version < ", quote, bios_version, ".1", quote, ")");
+ condition = condition_new(CONDITION_FIRMWARE, expression, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ));
+ condition_free(condition);
+
+ expression = strjoina("smbios-field(bios_version > ", quote, bios_version, ".1", quote, ")");
+ condition = condition_new(CONDITION_FIRMWARE, expression, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == 0);
+ condition_free(condition);
+}
+
TEST(condition_test_kernel_command_line) {
Condition *condition;
int r;
assert_se(condition_test(condition, environ) == 0);
condition_free(condition);
- condition = condition_new(CONDITION_KERNEL_VERSION, ">= 4711.8.15", false, false);
+ condition = condition_new(CONDITION_KERNEL_VERSION, " >= 4711.8.15", false, false);
assert_se(condition);
assert_se(condition_test(condition, environ) == 0);
condition_free(condition);
assert_se(condition_test(condition, environ) == 0);
condition_free(condition);
+ /* Test fnmatch() operators */
+ key_value_pair = strjoina(os_release_pairs[0], "=$", quote, os_release_pairs[1], quote);
+ condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == -EINVAL);
+ condition_free(condition);
+
+ key_value_pair = strjoina(os_release_pairs[0], "!=$", quote, os_release_pairs[1], quote);
+ condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == -EINVAL);
+ condition_free(condition);
+
/* Some distros (eg: Arch) do not set VERSION_ID */
if (parse_os_release(NULL, "VERSION_ID", &version_id) <= 0)
return;