diff --git a/docs/TRANSIENT-SETTINGS.md b/docs/TRANSIENT-SETTINGS.md index 9fa856ec21..070ad5a285 100644 --- a/docs/TRANSIENT-SETTINGS.md +++ b/docs/TRANSIENT-SETTINGS.md @@ -68,6 +68,7 @@ Most generic unit settings are available for transient units. ✓ ConditionKernelCommandLine= ✓ ConditionKernelVersion= ✓ ConditionArchitecture= +✓ ConditionFirmware= ✓ ConditionVirtualization= ✓ ConditionSecurity= ✓ ConditionCapability= diff --git a/man/systemd.link.xml b/man/systemd.link.xml index 5918a32189..6be115026b 100644 --- a/man/systemd.link.xml +++ b/man/systemd.link.xml @@ -222,6 +222,18 @@ + + + Firmware= + + Checks whether the system is running on a machine with the specified firmware. See + ConditionFirmware= in + systemd.unit5 + for details. When prefixed with an exclamation mark (!), the result is negated. + If an empty string is assigned, then previously assigned value is cleared. + + + diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml index c1b1980bac..d93ee8bb30 100644 --- a/man/systemd.netdev.xml +++ b/man/systemd.netdev.xml @@ -258,6 +258,17 @@ + + Firmware= + + Checks whether the system is running on a machine with the specified firmware. See + ConditionFirmware= in + systemd.unit5 + for details. When prefixed with an exclamation mark (!), the result is negated. + If an empty string is assigned, then previously assigned value is cleared. + + + diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 631658d88a..7f37f01ef9 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -1172,6 +1172,16 @@ + + ConditionFirmware= + + Check whether the system's firmware is of a certain type. Possible values are: + uefi (for systems with EFI), + device-tree (for systems with a device tree) and + device-tree-compatible(xyz) (for systems with a device tree that is compatible to xyz). + + + ConditionVirtualization= diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index c531380401..7c3c1c9095 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -297,6 +297,7 @@ Unit.ConditionFileIsExecutable, config_parse_unit_condition_path, Unit.ConditionNeedsUpdate, config_parse_unit_condition_path, CONDITION_NEEDS_UPDATE, offsetof(Unit, conditions) Unit.ConditionFirstBoot, config_parse_unit_condition_string, CONDITION_FIRST_BOOT, offsetof(Unit, conditions) Unit.ConditionArchitecture, config_parse_unit_condition_string, CONDITION_ARCHITECTURE, offsetof(Unit, conditions) +Unit.ConditionFirmware, config_parse_unit_condition_string, CONDITION_FIRMWARE, offsetof(Unit, conditions) Unit.ConditionVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, conditions) Unit.ConditionHost, config_parse_unit_condition_string, CONDITION_HOST, offsetof(Unit, conditions) Unit.ConditionKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, conditions) diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf index 8abe044890..6381bcfe0c 100644 --- a/src/network/netdev/netdev-gperf.gperf +++ b/src/network/netdev/netdev-gperf.gperf @@ -44,6 +44,7 @@ Match.Virtualization, config_parse_net_condition, Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(NetDev, conditions) Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(NetDev, conditions) Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(NetDev, conditions) +Match.Firmware, config_parse_net_condition, CONDITION_FIRMWARE, offsetof(NetDev, conditions) NetDev.Description, config_parse_string, 0, offsetof(NetDev, description) NetDev.Name, config_parse_ifname, 0, offsetof(NetDev, ifname) NetDev.Kind, config_parse_netdev_kind, 0, offsetof(NetDev, kind) diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 19d52a9296..91c21525fb 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -56,6 +56,7 @@ Match.Virtualization, config_parse_net_condition, Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(Network, conditions) Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(Network, conditions) Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(Network, conditions) +Match.Firmware, config_parse_net_condition, CONDITION_FIRMWARE, offsetof(Network, conditions) Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac) Link.MTUBytes, config_parse_mtu, AF_UNSPEC, offsetof(Network, mtu) Link.Group, config_parse_uint32, 0, offsetof(Network, group) diff --git a/src/shared/condition.c b/src/shared/condition.c index d84377c269..d4d9eacc4b 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -450,6 +451,81 @@ static int condition_test_architecture(Condition *c, char **env) { return a == b; } +#define DTCOMPAT_FILE "/sys/firmware/devicetree/base/compatible" +static int condition_test_firmware_devicetree_compatible(const char *dtcarg) { + int r; + _cleanup_free_ char *dtcompat = NULL; + _cleanup_strv_free_ char **dtcompatlist = NULL; + size_t size; + + r = read_full_virtual_file(DTCOMPAT_FILE, &dtcompat, &size); + if (r < 0) { + /* if the path doesn't exist it is incompatible */ + if (r != -ENOENT) + log_debug_errno(r, "Failed to open() '%s', assuming machine is incompatible: %m", DTCOMPAT_FILE); + return false; + } + + /* Not sure this can happen, but play safe. */ + if (size == 0) { + log_debug("%s has zero length, assuming machine is incompatible", DTCOMPAT_FILE); + return false; + } + + /* + * /sys/firmware/devicetree/base/compatible consists of one or more + * strings, each ending in '\0'. So the last character in dtcompat must + * be a '\0'. + */ + if (dtcompat[size - 1] != '\0') { + log_debug("%s is in an unknown format, assuming machine is incompatible", DTCOMPAT_FILE); + return false; + } + + dtcompatlist = strv_parse_nulstr(dtcompat, size); + if (!dtcompatlist) + return -ENOMEM; + + return strv_contains(dtcompatlist, dtcarg); +} + +static int condition_test_firmware(Condition *c, char **env) { + sd_char *dtc; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_FIRMWARE); + + if (streq(c->parameter, "device-tree")) { + if (access("/sys/firmware/device-tree/", F_OK) < 0) { + if (errno != ENOENT) + log_debug_errno(errno, "Unexpected error when checking for /sys/firmware/device-tree/: %m"); + return false; + } else + return true; + } else if ((dtc = startswith(c->parameter, "device-tree-compatible("))) { + _cleanup_free_ char *dtcarg = NULL; + char *end; + + end = strchr(dtc, ')'); + if (!end || *(end + 1) != '\0') { + log_debug("Malformed Firmware condition \"%s\"", c->parameter); + return false; + } + + dtcarg = strndup(dtc, end - dtc); + if (!dtcarg) + return -ENOMEM; + + return condition_test_firmware_devicetree_compatible(dtcarg); + } else if (streq(c->parameter, "uefi")) + return is_efi_boot(); + else { + log_debug("Unsupported Firmware condition \"%s\"", c->parameter); + return false; + } +} + static int condition_test_host(Condition *c, char **env) { _cleanup_free_ char *h = NULL; sd_id128_t x, y; @@ -843,6 +919,7 @@ int condition_test(Condition *c, char **env) { [CONDITION_HOST] = condition_test_host, [CONDITION_AC_POWER] = condition_test_ac_power, [CONDITION_ARCHITECTURE] = condition_test_architecture, + [CONDITION_FIRMWARE] = condition_test_firmware, [CONDITION_NEEDS_UPDATE] = condition_test_needs_update, [CONDITION_FIRST_BOOT] = condition_test_first_boot, [CONDITION_USER] = condition_test_user, @@ -949,6 +1026,7 @@ void condition_dump_list(Condition *first, FILE *f, const char *prefix, conditio static const char* const condition_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_ARCHITECTURE] = "ConditionArchitecture", + [CONDITION_FIRMWARE] = "ConditionFirmware", [CONDITION_VIRTUALIZATION] = "ConditionVirtualization", [CONDITION_HOST] = "ConditionHost", [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine", @@ -981,6 +1059,7 @@ DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType); static const char* const assert_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_ARCHITECTURE] = "AssertArchitecture", + [CONDITION_FIRMWARE] = "AssertFirmware", [CONDITION_VIRTUALIZATION] = "AssertVirtualization", [CONDITION_HOST] = "AssertHost", [CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine", diff --git a/src/shared/condition.h b/src/shared/condition.h index 75c430e9e0..6678689d7c 100644 --- a/src/shared/condition.h +++ b/src/shared/condition.h @@ -9,6 +9,7 @@ typedef enum ConditionType { CONDITION_ARCHITECTURE, + CONDITION_FIRMWARE, CONDITION_VIRTUALIZATION, CONDITION_HOST, CONDITION_KERNEL_COMMAND_LINE, diff --git a/test/fuzz/fuzz-netdev-parser/directives.netdev b/test/fuzz/fuzz-netdev-parser/directives.netdev index d4e5598409..8373ac7e89 100644 --- a/test/fuzz/fuzz-netdev-parser/directives.netdev +++ b/test/fuzz/fuzz-netdev-parser/directives.netdev @@ -22,6 +22,7 @@ Mode= SourceMACAddress= [Match] Architecture= +Firmware= Host= KernelVersion= Virtualization= diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index d6c1cc7f92..096ba9a8a9 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -18,6 +18,7 @@ KernelVersion= Type= Driver= Architecture= +Firmware= Path= WLANInterfaceType= SSID= diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service index 9fc83f04d2..73e1707c5c 100644 --- a/test/fuzz/fuzz-unit-file/directives.service +++ b/test/fuzz/fuzz-unit-file/directives.service @@ -36,6 +36,7 @@ CollectMode= ConditionACPower= ConditionArchitecture= ConditionCPUs= +ConditionFirmware= ConditionCapability= ConditionControlGroupController= ConditionDirectoryNotEmpty=