mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
pid1: extend "ConditionFirmware=" for checking SMBIOS system identification information
This commit is contained in:
parent
de9b57a130
commit
bf07a12516
6
NEWS
6
NEWS
@ -44,6 +44,12 @@ CHANGES WITH 252 in spe:
|
||||
|
||||
* C.UTF-8 is used as the default locale if nothing else has been configured.
|
||||
|
||||
* Extend [Condition|Assert]Firmware= to conditionalize on certain SMBIOS
|
||||
fields. For example
|
||||
ConditionFirmware=smbios-field(board_name = "Custom Board") will
|
||||
conditionalize a unit so that it is only run when
|
||||
/sys/class/dmi/id/board_name contains "Custom Board" (without quotes).
|
||||
|
||||
Changes in sd-boot, bootctl, and the Boot Loader Specification:
|
||||
|
||||
* The Boot Loader Specification has been cleaned up and clarified.
|
||||
|
@ -1235,10 +1235,24 @@
|
||||
<varlistentry>
|
||||
<term><varname>ConditionFirmware=</varname></term>
|
||||
|
||||
<listitem><para>Check whether the system's firmware is of a certain type. Possible values are:
|
||||
<literal>uefi</literal> (for systems with EFI),
|
||||
<literal>device-tree</literal> (for systems with a device tree) and
|
||||
<literal>device-tree-compatible(xyz)</literal> (for systems with a device tree that is compatible to <literal>xyz</literal>).</para>
|
||||
<listitem><para>Check whether the system's firmware is of a certain type. Multiple values are possible.</para>
|
||||
|
||||
<para><literal>uefi</literal> for systems with EFI.</para>
|
||||
|
||||
<para><literal>device-tree</literal> for systems with a device tree.</para>
|
||||
|
||||
<para><literal>device-tree-compatible(<replaceable>value</replaceable>)</literal> for systems with a device tree that is compatible to
|
||||
<literal>value</literal>.</para>
|
||||
|
||||
<para><literal>smbios-field(<replaceable>field</replaceable> <replaceable>operator</replaceable> <replaceable>value</replaceable>)</literal>
|
||||
for systems with a SMBIOS field containing a certain value.
|
||||
<literal>field</literal> is the name of the SMBIOS field exposed as <literal>sysfs</literal> attribute file
|
||||
below <filename>/sys/class/dmi/id/</filename>.
|
||||
<literal>operator</literal> is one of <literal><</literal>, <literal><=</literal>,
|
||||
<literal>>=</literal>, <literal>></literal>, <literal>=</literal>, <literal>!=</literal> for version
|
||||
comparison, or <literal>=$</literal>, <literal>!=$</literal> for string comparison.
|
||||
<literal>value</literal> is the expected value of the SMBIOS field (shell-style globs are possible if
|
||||
<literal>=$</literal> or<literal>!=$</literal> is used).</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <fnmatch.h>
|
||||
@ -184,6 +185,10 @@ static int condition_test_credential(Condition *c, char **env) {
|
||||
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,
|
||||
@ -194,8 +199,10 @@ typedef enum {
|
||||
_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] = "<",
|
||||
@ -209,6 +216,8 @@ static OrderOperator parse_order(const char **s) {
|
||||
|
||||
e = startswith(*s, prefix[i]);
|
||||
if (e) {
|
||||
if (!allow_fnmatch && (i >= _ORDER_FNMATCH_FIRST && i <= _ORDER_FNMATCH_LAST))
|
||||
break;
|
||||
*s = e;
|
||||
return i;
|
||||
}
|
||||
@ -268,7 +277,7 @@ static int condition_test_kernel_version(Condition *c, char **env) {
|
||||
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)) {
|
||||
@ -329,7 +338,7 @@ static int condition_test_osrelease(Condition *c, char **env) {
|
||||
"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");
|
||||
@ -366,7 +375,7 @@ static int condition_test_memory(Condition *c, char **env) {
|
||||
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. */
|
||||
|
||||
@ -392,7 +401,7 @@ static int condition_test_cpus(Condition *c, char **env) {
|
||||
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. */
|
||||
|
||||
@ -578,8 +587,62 @@ static int condition_test_firmware_devicetree_compatible(const char *dtcarg) {
|
||||
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);
|
||||
@ -592,24 +655,40 @@ static int condition_test_firmware(Condition *c, char **env) {
|
||||
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;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
#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"
|
||||
@ -305,6 +306,136 @@ TEST(condition_test_architecture) {
|
||||
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;
|
||||
@ -427,7 +558,7 @@ TEST(condition_test_kernel_version) {
|
||||
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);
|
||||
@ -1042,6 +1173,19 @@ TEST(condition_test_os_release) {
|
||||
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;
|
||||
|
Loading…
Reference in New Issue
Block a user