]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
condition: add new ConditionKernelModuleLoaded=
authorLennart Poettering <lennart@poettering.net>
Thu, 28 Nov 2024 12:00:34 +0000 (13:00 +0100)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 11 Dec 2024 20:03:52 +0000 (05:03 +0900)
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
src/core/load-fragment-gperf.gperf.in
src/shared/condition.c
src/shared/condition.h
src/test/test-condition.c
units/modprobe@.service

index 2c7f0bd71fffbe46751abf0323b3dc0e6fb5df17..33ac732ebf371bc6c843029c8dafaa4245c97ebf 100644 (file)
           </listitem>
         </varlistentry>
 
+        <varlistentry>
+          <term><varname>ConditionKernelModuleLoaded=</varname></term>
+
+          <listitem><para>Test whether the specified kernel module has been loaded and is already fully
+          initialized.</para>
+
+          <xi:include href="version-info.xml" xpointer="v258"/>
+          </listitem>
+        </varlistentry>
+
         <varlistentry>
           <term><varname>AssertArchitecture=</varname></term>
           <term><varname>AssertVirtualization=</varname></term>
           <term><varname>AssertMemoryPressure=</varname></term>
           <term><varname>AssertCPUPressure=</varname></term>
           <term><varname>AssertIOPressure=</varname></term>
+          <term><varname>AssertKernelModuleLoaded=</varname></term>
 
           <listitem><para>Similar to the <varname>ConditionArchitecture=</varname>,
           <varname>ConditionVirtualization=</varname>, …, condition settings described above, these settings
index fa12580ae111349eefe808e77378be66daa3010a..90290a8b0eed918f87443df169ed13503d2060aa 100644 (file)
@@ -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)
index 9dfa1f8901ff2c796d09e8221c81afedbecac462..ac23681a110c14d63e222322e2faf3e4dbe1cd1b 100644 (file)
@@ -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);
index 54cc904feb55e4c052f287a558e23cb280668bb4..378028a73e2de42f438ba81a10e500049369629e 100644 (file)
@@ -45,6 +45,7 @@ typedef enum ConditionType {
         CONDITION_GROUP,
 
         CONDITION_CONTROL_GROUP_CONTROLLER,
+        CONDITION_KERNEL_MODULE_LOADED,
 
         _CONDITION_TYPE_MAX,
         _CONDITION_TYPE_INVALID = -EINVAL,
index fc27924621c59ddf0e33a2dd764dcdc568685788..dcd1aea384c6e50fdf24b847b6a3d45a9d5f80dc 100644 (file)
@@ -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);
index fe631fffeb8cd7a3439867578f7818acf211e1fb..05e5b4f60015c4989f8ad282f0ad4579c9721b5d 100644 (file)
@@ -13,6 +13,7 @@ DefaultDependencies=no
 Before=sysinit.target
 Documentation=man:modprobe(8)
 ConditionCapability=CAP_SYS_MODULE
+ConditionKernelModuleLoaded=!%i
 StartLimitIntervalSec=0
 
 [Service]