[LTP] [Patch v2 2/4] tst_kconfig: load and unload kernel modules for CONFIG_*=m
Li Wang
li.wang@linux.dev
Thu Oct 8 07:21:39 CEST 2026
Tests that depend on a config set to =m must currently load the
module themselves, and may fail with misleading errors when it is
not loaded, e.g. bind() returning EADDRNOTAVAIL for AF_VSOCK when
vsock_loopback is missing.
Record the module for every config_module_maps[] entry seen as =m
during kconfig_parse_line(), load them in
tst_kconfig_module_load() via tst_modprobe(), and unload them in
reverse order from tst_kconfig_module_unload_all(), called by
do_test_cleanup(). Modules still in use are skipped without
aborting the test.
Loading and unloading are performed only as root; otherwise the
recorded list is left untouched.
Signed-off-by: Li Wang <li.wang@linux.dev>
---
include/tst_kconfig.h | 28 +++++++++++++++
lib/tst_kconfig.c | 79 +++++++++++++++++++++++++++++++++++++++++++
lib/tst_test.c | 11 ++++--
3 files changed, 116 insertions(+), 2 deletions(-)
diff --git a/include/tst_kconfig.h b/include/tst_kconfig.h
index c454f3467..a525429d4 100644
--- a/include/tst_kconfig.h
+++ b/include/tst_kconfig.h
@@ -105,4 +105,32 @@ void tst_kcmdline_parse(struct tst_kcmdline_var params[], size_t params_len);
*/
int tst_has_slow_kconfig(void);
+/**
+ * tst_kconfig_module_load() - Load kernel modules for CONFIG_*=m entries.
+ *
+ * Loads every module recorded during the last kernel config check, in
+ * the order they were discovered. The list is populated by
+ * kconfig_module_record() from kconfig_parse_line() for each CONFIG_*
+ * entry that is set to =m and has a matching entry in
+ * config_module_maps[].
+ *
+ * Loading is skipped when not running as root, and does nothing when
+ * the recorded list is empty.
+ */
+void tst_kconfig_module_load(void);
+
+/**
+ * tst_kconfig_module_unload_all() - Unload modules loaded by
+ * tst_kconfig_module_load().
+ *
+ * Unloads every module recorded by tst_kconfig_module_load(), in
+ * reverse load order so that inter-module dependencies are respected.
+ * Modules still in use are skipped silently, leaving the system state
+ * as close as possible to what it was before the test started.
+ *
+ * Called from do_test_cleanup() so that the modules are released when
+ * the test finishes, regardless of the test result.
+ */
+void tst_kconfig_module_unload_all(void);
+
#endif /* TST_KCONFIG_H__ */
diff --git a/lib/tst_kconfig.c b/lib/tst_kconfig.c
index 49957e28d..fc03966ba 100644
--- a/lib/tst_kconfig.c
+++ b/lib/tst_kconfig.c
@@ -16,8 +16,15 @@
#include "tst_bool_expr.h"
#include "tst_safe_stdio.h"
+#include "tst_module.h"
#include "tst_kconfig_checks.h"
+#define MAX_LOADED_MODULES 16
+
+static const char *loaded_modules[MAX_LOADED_MODULES];
+static unsigned int loaded_module_count;
+static bool modules_loaded;
+
static int kconfig_skip_check(void)
{
char *skipped = getenv("KCONFIG_SKIP_CHECK");
@@ -189,6 +196,38 @@ static void kconfig_module_check(struct tst_kconfig_var *var)
}
}
+static const char *module_name_for_config(const char *cfg)
+{
+ size_t i;
+
+ for (i = 0; config_module_maps[i].config; i++) {
+ if (!strcmp(config_module_maps[i].config, cfg))
+ return config_module_maps[i].module_name;
+ }
+
+ return NULL;
+}
+
+static void kconfig_module_record(const char *cfg)
+{
+ unsigned int i;
+ const char *mod = module_name_for_config(cfg);
+
+ if (!mod)
+ return;
+
+ if (tst_module_is_loaded(mod))
+ return;
+
+ for (i = 0; i < loaded_module_count; i++) {
+ if (!strcmp(loaded_modules[i], mod))
+ return;
+ }
+
+ if (loaded_module_count < MAX_LOADED_MODULES)
+ loaded_modules[loaded_module_count++] = mod;
+}
+
static inline int kconfig_parse_line(const char *line,
struct tst_kconfig_var *vars,
unsigned int vars_len)
@@ -268,6 +307,7 @@ out:
vars[i].choice = 'm';
kconfig_runtime_check(&vars[i]);
kconfig_module_check(&vars[i]);
+ kconfig_module_record(vars[i].id);
return 1;
}
}
@@ -300,6 +340,45 @@ exit:
close_kconfig(fp);
}
+void tst_kconfig_module_load(void)
+{
+ unsigned int i;
+
+ if (geteuid() != 0)
+ return;
+
+ for (i = 0; i < loaded_module_count; i++) {
+ tst_res(TDEBUG, "Loading module '%s'", loaded_modules[i]);
+ tst_modprobe(loaded_modules[i], NULL);
+ }
+
+ modules_loaded = true;
+}
+
+void tst_kconfig_module_unload_all(void)
+{
+ unsigned int i;
+
+ if (!loaded_module_count)
+ return;
+
+ if (!modules_loaded)
+ return;
+
+ if (geteuid() != 0)
+ return;
+
+ for (i = loaded_module_count; i > 0; i--) {
+ const char *const argv[] = { "rmmod", loaded_modules[i - 1], NULL };
+
+ tst_res(TDEBUG, "Unloading module '%s'", loaded_modules[i - 1]);
+ tst_cmd(argv, "/dev/null", "/dev/null", TST_CMD_PASS_RETVAL);
+ }
+
+ loaded_module_count = 0;
+ modules_loaded = false;
+}
+
static size_t array_len(const char *const kconfigs[])
{
size_t i = 0;
diff --git a/lib/tst_test.c b/lib/tst_test.c
index 10ff73397..f55830983 100644
--- a/lib/tst_test.c
+++ b/lib/tst_test.c
@@ -1471,8 +1471,12 @@ static void do_setup(int argc, char *argv[])
check_supported_kver();
- if (tst_test->needs_kconfigs && tst_kconfig_check(tst_test->needs_kconfigs))
- tst_brk(TCONF, "Aborting due to unsuitable kernel config, see above!");
+ if (tst_test->needs_kconfigs) {
+ if (tst_kconfig_check(tst_test->needs_kconfigs))
+ tst_brk(TCONF, "Aborting due to unsuitable kernel config, see above!");
+
+ tst_kconfig_module_load();
+ }
if (tst_test->needs_root && geteuid() != 0)
tst_brk(TCONF, "Test needs to be run as root");
@@ -1690,6 +1694,9 @@ static void do_cleanup(void)
if (tst_test->restore_wallclock)
tst_wallclock_restore();
+ if (tst_test->needs_kconfigs)
+ tst_kconfig_module_unload_all();
+
cleanup_ipc();
}
--
2.56.0
More information about the ltp
mailing list