From 3c702e82105bd8a12f9a03617b48fc6ba8537e33 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 28 Nov 2024 13:00:34 +0100 Subject: [PATCH] condition: add new ConditionKernelModuleLoaded= This introduces a new unit condition check: that matches if a specific kmod module is allowed. This should be generally useful, but there's one usecase in particular: we can optimize modprobe@.service with this and avoid forking out a bunch of modprobe requests during boot for the same kmods. Checking if a kernel module is loaded is more complicated than just checking if /sys/module/$MODULE/ exists, since kernel modules typically take a while to initialize and we must check that this is complete (by checking if the sysfs attr "initstate" is "live"). --- man/systemd.unit.xml | 11 ++++++ src/core/load-fragment-gperf.gperf.in | 2 + src/shared/condition.c | 56 +++++++++++++++++++++++++++ src/shared/condition.h | 1 + src/test/test-condition.c | 33 ++++++++++++++++ units/modprobe@.service | 1 + 6 files changed, 104 insertions(+) diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 2c7f0bd71ff..33ac732ebf3 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -1989,6 +1989,16 @@ + + ConditionKernelModuleLoaded= + + Test whether the specified kernel module has been loaded and is already fully + initialized. + + + + + AssertArchitecture= AssertVirtualization= @@ -2022,6 +2032,7 @@ AssertMemoryPressure= AssertCPUPressure= AssertIOPressure= + AssertKernelModuleLoaded= Similar to the ConditionArchitecture=, ConditionVirtualization=, …, condition settings described above, these settings diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index fa12580ae11..90290a8b0ee 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -374,6 +374,7 @@ Unit.ConditionOSRelease, config_parse_unit_condition_string Unit.ConditionMemoryPressure, config_parse_unit_condition_string, CONDITION_MEMORY_PRESSURE, offsetof(Unit, conditions) Unit.ConditionCPUPressure, config_parse_unit_condition_string, CONDITION_CPU_PRESSURE, offsetof(Unit, conditions) Unit.ConditionIOPressure, config_parse_unit_condition_string, CONDITION_IO_PRESSURE, offsetof(Unit, conditions) +Unit.ConditionKernelModuleLoaded, config_parse_unit_condition_string, CONDITION_KERNEL_MODULE_LOADED, offsetof(Unit, conditions) Unit.AssertPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, asserts) Unit.AssertPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, asserts) Unit.AssertPathIsDirectory, config_parse_unit_condition_path, CONDITION_PATH_IS_DIRECTORY, offsetof(Unit, asserts) @@ -406,6 +407,7 @@ Unit.AssertOSRelease, config_parse_unit_condition_string Unit.AssertMemoryPressure, config_parse_unit_condition_string, CONDITION_MEMORY_PRESSURE, offsetof(Unit, asserts) Unit.AssertCPUPressure, config_parse_unit_condition_string, CONDITION_CPU_PRESSURE, offsetof(Unit, asserts) Unit.AssertIOPressure, config_parse_unit_condition_string, CONDITION_IO_PRESSURE, offsetof(Unit, asserts) +Unit.AssertKernelModuleLoaded, config_parse_unit_condition_string, CONDITION_KERNEL_MODULE_LOADED, offsetof(Unit, asserts) Unit.CollectMode, config_parse_collect_mode, 0, offsetof(Unit, collect_mode) Service.PIDFile, config_parse_pid_file, 0, offsetof(Service, pid_file) Service.ExecCondition, config_parse_exec, SERVICE_EXEC_CONDITION, offsetof(Service, exec_command) diff --git a/src/shared/condition.c b/src/shared/condition.c index 9dfa1f8901f..ac23681a110 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -1155,6 +1155,59 @@ static int condition_test_psi(Condition *c, char **env) { return *current <= limit; } +static int condition_test_kernel_module_loaded(Condition *c, char **env) { + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_KERNEL_MODULE_LOADED); + + /* Checks whether a specific kernel module is fully loaded (i.e. with the full initialization routine + * complete). */ + + _cleanup_free_ char *normalized = strreplace(c->parameter, "-", "_"); + if (!normalized) + return log_oom_debug(); + + if (!filename_is_valid(normalized)) { + log_debug("Kernel module name '%s' is not valid, hence reporting it to not be loaded.", normalized); + return false; + } + + _cleanup_free_ char *p = path_join("/sys/module/", normalized); + if (!p) + return log_oom_debug(); + + _cleanup_close_ int dir_fd = open(p, O_PATH|O_DIRECTORY|O_CLOEXEC); + if (dir_fd < 0) { + if (errno == ENOENT) { + log_debug_errno(errno, "'%s/' does not exist, kernel module '%s' not loaded.", p, normalized); + return false; + } + + return log_debug_errno(errno, "Failed to open directory '%s/': %m", p); + } + + _cleanup_free_ char *initstate = NULL; + r = read_virtual_file_at(dir_fd, "initstate", SIZE_MAX, &initstate, NULL); + if (r == -ENOENT) { + log_debug_errno(r, "'%s/' exists but '%s/initstate' does not, kernel module '%s' is built-in, hence loaded.", p, p, normalized); + return true; + } + if (r < 0) + return log_debug_errno(r, "Failed to open '%s/initstate': %m", p); + + delete_trailing_chars(initstate, WHITESPACE); + + if (!streq(initstate, "live")) { + log_debug("Kernel module '%s' is reported as '%s', hence not loaded.", normalized, initstate); + return false; + } + + log_debug("Kernel module '%s' detected as loaded.", normalized); + return true; +} + int condition_test(Condition *c, char **env) { static int (*const condition_tests[_CONDITION_TYPE_MAX])(Condition *c, char **env) = { @@ -1191,6 +1244,7 @@ int condition_test(Condition *c, char **env) { [CONDITION_MEMORY_PRESSURE] = condition_test_psi, [CONDITION_CPU_PRESSURE] = condition_test_psi, [CONDITION_IO_PRESSURE] = condition_test_psi, + [CONDITION_KERNEL_MODULE_LOADED] = condition_test_kernel_module_loaded, }; int r, b; @@ -1315,6 +1369,7 @@ static const char* const condition_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_MEMORY_PRESSURE] = "ConditionMemoryPressure", [CONDITION_CPU_PRESSURE] = "ConditionCPUPressure", [CONDITION_IO_PRESSURE] = "ConditionIOPressure", + [CONDITION_KERNEL_MODULE_LOADED] = "ConditionKernelModuleLoaded", }; DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType); @@ -1353,6 +1408,7 @@ static const char* const assert_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_MEMORY_PRESSURE] = "AssertMemoryPressure", [CONDITION_CPU_PRESSURE] = "AssertCPUPressure", [CONDITION_IO_PRESSURE] = "AssertIOPressure", + [CONDITION_KERNEL_MODULE_LOADED] = "AssertKernelModuleLoaded", }; DEFINE_STRING_TABLE_LOOKUP(assert_type, ConditionType); diff --git a/src/shared/condition.h b/src/shared/condition.h index 54cc904feb5..378028a73e2 100644 --- a/src/shared/condition.h +++ b/src/shared/condition.h @@ -45,6 +45,7 @@ typedef enum ConditionType { CONDITION_GROUP, CONDITION_CONTROL_GROUP_CONTROLLER, + CONDITION_KERNEL_MODULE_LOADED, _CONDITION_TYPE_MAX, _CONDITION_TYPE_INVALID = -EINVAL, diff --git a/src/test/test-condition.c b/src/test/test-condition.c index fc27924621c..dcd1aea384c 100644 --- a/src/test/test-condition.c +++ b/src/test/test-condition.c @@ -1308,4 +1308,37 @@ TEST(condition_test_psi) { condition_free(condition); } +TEST(condition_test_kernel_module_loaded) { + Condition *condition; + int r; + + condition = condition_new(CONDITION_KERNEL_MODULE_LOADED, "", /* trigger= */ false, /* negate= */ false); + assert_se(condition); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_MODULE_LOADED, "..", /* trigger= */ false, /* negate= */ false); + assert_se(condition); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + if (access("/sys/module/", F_OK) < 0) + return (void) log_tests_skipped("/sys/module not available, skipping."); + + FOREACH_STRING(m, "random", "vfat", "fat", "cec", "binfmt_misc", "binfmt-misc") { + condition = condition_new(CONDITION_KERNEL_MODULE_LOADED, m, /* trigger= */ false, /* negate= */ false); + assert_se(condition); + r = condition_test(condition, environ); + ASSERT_OK(r); + condition_free(condition); + + log_notice("kmod %s is loaded: %s", m, yes_no(r)); + } + + condition = condition_new(CONDITION_KERNEL_MODULE_LOADED, "idefinitelydontexist", /* trigger= */ false, /* negate= */ false); + assert_se(condition); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/units/modprobe@.service b/units/modprobe@.service index fe631fffeb8..05e5b4f6001 100644 --- a/units/modprobe@.service +++ b/units/modprobe@.service @@ -13,6 +13,7 @@ DefaultDependencies=no Before=sysinit.target Documentation=man:modprobe(8) ConditionCapability=CAP_SYS_MODULE +ConditionKernelModuleLoaded=!%i StartLimitIntervalSec=0 [Service] -- 2.47.3