diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index 2c7f0bd71f..33ac732ebf 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 fa12580ae1..90290a8b0e 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 9dfa1f8901..ac23681a11 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 54cc904feb..378028a73e 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 fc27924621..dcd1aea384 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 fe631fffeb..05e5b4f600 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]