testsuite: add tests for weak dependencies

The following tests to verify weak dependencies have been implemented:
1) modprobe test to check that related weakdep modules are not loaded
   due to being a weakdep.
2) depmod test to check weakdep output.
3) user test to check that configuration files with weakdep are parsed
   correctly and related weakdep modules can be read correctly from user
   applications.

Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
Link: https://lore.kernel.org/r/20240530070836.9438-1-jtornosm@redhat.com
[ Minor whitespace issues and define MODULE_WEAKDEP if it's not defined
  already ]
Signed-off-by: Lucas De Marchi <lucas.de.marchi@gmail.com>
This commit is contained in:
Jose Ignacio Tornos Martinez 2024-05-30 09:08:27 +02:00 committed by Lucas De Marchi
parent a0ed4f8438
commit d06712b514
36 changed files with 309 additions and 10 deletions

View File

@ -262,7 +262,7 @@ TESTSUITE = \
testsuite/test-modinfo testsuite/test-util testsuite/test-new-module \
testsuite/test-modprobe testsuite/test-blacklist \
testsuite/test-dependencies testsuite/test-depmod \
testsuite/test-list
testsuite/test-list testsuite/test-user
check_PROGRAMS = $(TESTSUITE)
TESTS = $(TESTSUITE)
@ -305,6 +305,8 @@ testsuite_test_depmod_LDADD = $(TESTSUITE_LDADD)
testsuite_test_depmod_CPPFLAGS = $(TESTSUITE_CPPFLAGS)
testsuite_test_list_LDADD = $(TESTSUITE_LDADD)
testsuite_test_list_CPPFLAGS = $(TESTSUITE_CPPFLAGS)
testsuite_test_user_LDADD = $(TESTSUITE_LDADD)
testsuite_test_user_CPPFLAGS = $(TESTSUITE_CPPFLAGS)
testsuite-distclean:
$(RM) -r $(ROOTFS)

View File

@ -18,6 +18,7 @@
/test-modprobe
/test-hash
/test-list
/test-user
/rootfs
/stamp-rootfs
/test-scratchbuf.log
@ -52,3 +53,5 @@
/test-testsuite.trs
/test-list.log
/test-list.trs
/test-user.log
/test-user.trs

View File

@ -37,6 +37,8 @@ obj-m += mod-fake-hpsa.o
obj-m += mod-fake-scsi-mod.o
obj-m += mod-fake-cciss.o
obj-m += mod-weakdep.o
else
# only build ARCH-specific module
ifeq ($(ARCH),)

View File

@ -0,0 +1,19 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/module.h>
#ifndef MODULE_WEAKDEP
#define MODULE_WEAKDEP(_weakdep) MODULE_INFO(weakdep, _weakdep)
#endif
static int __init weakdep_init(void)
{
return 0;
}
module_init(weakdep_init);
MODULE_AUTHOR("Jose Ignacio Tornos Martinez <jtornosm@redhat.com>");
MODULE_LICENSE("LGPL");
MODULE_WEAKDEP("mod-simple");

View File

@ -0,0 +1,2 @@
# Weak dependencies extracted from modules themselves.
weakdep mod_weakdep mod-simple

View File

@ -0,0 +1 @@
weakdep mod-loop-b mod-loop-a mod-simple

View File

@ -0,0 +1 @@
# Aliases extracted from modules themselves.

View File

@ -0,0 +1,3 @@
kernel/mod-loop-b.ko:
kernel/mod-loop-a.ko:
kernel/mod-simple.ko:

View File

@ -0,0 +1 @@
# Device nodes to trigger on-demand module loading.

View File

@ -0,0 +1 @@
# Soft dependencies extracted from modules themselves.

View File

@ -0,0 +1,3 @@
# Aliases for symbols, used by symbol_request().
alias symbol:printB mod_loop_b
alias symbol:printA mod_loop_a

View File

@ -0,0 +1 @@
# Weak dependencies extracted from modules themselves.

View File

@ -0,0 +1,2 @@
mod-loop-b: mod_loop_a mod_simple
mod-weakdep: mod_simple

View File

@ -0,0 +1 @@
weakdep mod-loop-b mod-loop-a mod-simple

View File

@ -0,0 +1 @@
# Aliases extracted from modules themselves.

View File

@ -0,0 +1,4 @@
kernel/mod-weakdep.ko:
kernel/mod-simple.ko:
kernel/mod-loop-b.ko:
kernel/mod-loop-a.ko: kernel/mod-loop-b.ko

View File

@ -0,0 +1 @@
# Soft dependencies extracted from modules themselves.

View File

@ -0,0 +1,3 @@
# Aliases for symbols, used by symbol_request().
alias symbol:printB mod_loop_b
alias symbol:printA mod_loop_a

View File

@ -0,0 +1,2 @@
# Weak dependencies extracted from modules themselves.
weakdep mod_weakdep mod-simple

View File

@ -68,6 +68,8 @@ map=(
["test-depmod/search-order-external-last/lib/modules/external/"]="mod-simple.ko"
["test-depmod/search-order-override$MODULE_DIRECTORY/4.4.4/foo/"]="mod-simple.ko"
["test-depmod/search-order-override$MODULE_DIRECTORY/4.4.4/override/"]="mod-simple.ko"
["test-depmod/check-weakdep$MODULE_DIRECTORY/4.4.4/kernel/mod-weakdep.ko"]="mod-weakdep.ko"
["test-depmod/check-weakdep$MODULE_DIRECTORY/4.4.4/kernel/mod-simple.ko"]="mod-simple.ko"
["test-dependencies$MODULE_DIRECTORY/4.0.20-kmod/kernel/fs/foo/"]="mod-foo-b.ko"
["test-dependencies$MODULE_DIRECTORY/4.0.20-kmod/kernel/"]="mod-foo-c.ko"
["test-dependencies$MODULE_DIRECTORY/4.0.20-kmod/kernel/lib/"]="mod-foo-a.ko"
@ -80,6 +82,9 @@ map=(
["test-modprobe/show-exports/mod-loop-a.ko"]="mod-loop-a.ko"
["test-modprobe/softdep-loop$MODULE_DIRECTORY/4.4.4/kernel/mod-loop-a.ko"]="mod-loop-a.ko"
["test-modprobe/softdep-loop$MODULE_DIRECTORY/4.4.4/kernel/mod-loop-b.ko"]="mod-loop-b.ko"
["test-modprobe/weakdep-loop$MODULE_DIRECTORY/4.4.4/kernel/mod-loop-a.ko"]="mod-loop-a.ko"
["test-modprobe/weakdep-loop$MODULE_DIRECTORY/4.4.4/kernel/mod-loop-b.ko"]="mod-loop-b.ko"
["test-modprobe/weakdep-loop$MODULE_DIRECTORY/4.4.4/kernel/mod-simple.ko"]="mod-simple.ko"
["test-modprobe/install-cmd-loop$MODULE_DIRECTORY/4.4.4/kernel/mod-loop-a.ko"]="mod-loop-a.ko"
["test-modprobe/install-cmd-loop$MODULE_DIRECTORY/4.4.4/kernel/mod-loop-b.ko"]="mod-loop-b.ko"
["test-modprobe/force$MODULE_DIRECTORY/4.4.4/kernel/"]="mod-simple.ko"
@ -103,6 +108,10 @@ map=(
["test-modinfo/mod-simple-sha256.ko"]="mod-simple.ko"
["test-modinfo/mod-simple-pkcs7.ko"]="mod-simple.ko"
["test-modinfo/external/lib/modules/external/mod-simple.ko"]="mod-simple.ko"
["test-user$MODULE_DIRECTORY/4.4.4/kernel/mod-loop-a.ko"]="mod-loop-a.ko"
["test-user$MODULE_DIRECTORY/4.4.4/kernel/mod-loop-b.ko"]="mod-loop-b.ko"
["test-user$MODULE_DIRECTORY/4.4.4/kernel/mod-simple.ko"]="mod-simple.ko"
["test-user$MODULE_DIRECTORY/4.4.4/kernel/mod-weakdep.ko"]="mod-weakdep.ko"
)
gzip_array=(

View File

@ -244,4 +244,31 @@ DEFINE_TEST(depmod_search_order_override,
},
});
#define CHECK_WEAKDEP_ROOTFS TESTSUITE_ROOTFS "test-depmod/check-weakdep"
#define CHECK_WEAKDEP_LIB_MODULES CHECK_WEAKDEP_ROOTFS MODULE_DIRECTORY "/" MODULES_UNAME
static noreturn int depmod_check_weakdep(const struct test *t)
{
const char *progname = ABS_TOP_BUILDDIR "/tools/depmod";
const char *const args[] = {
progname,
NULL,
};
test_spawn_prog(progname, args);
exit(EXIT_FAILURE);
}
DEFINE_TEST(depmod_check_weakdep,
.description = "check weakdep output",
.config = {
[TC_UNAME_R] = MODULES_UNAME,
[TC_ROOTFS] = CHECK_WEAKDEP_ROOTFS,
},
.output = {
.files = (const struct keyval[]) {
{ CHECK_WEAKDEP_LIB_MODULES "/correct-modules.weakdep",
CHECK_WEAKDEP_LIB_MODULES "/modules.weakdep" },
{ }
},
});
TESTSUITE_MAIN();

View File

@ -181,6 +181,29 @@ DEFINE_TEST(modprobe_softdep_loop,
.modules_loaded = "mod-loop-a,mod-loop-b",
);
static noreturn int modprobe_weakdep_loop(const struct test *t)
{
const char *progname = ABS_TOP_BUILDDIR "/tools/modprobe";
const char *const args[] = {
progname,
"mod-loop-b",
NULL,
};
test_spawn_prog(progname, args);
exit(EXIT_FAILURE);
}
DEFINE_TEST(modprobe_weakdep_loop,
.description = "check if modprobe breaks weakdep loop",
.config = {
[TC_UNAME_R] = "4.4.4",
[TC_ROOTFS] = TESTSUITE_ROOTFS "test-modprobe/weakdep-loop",
[TC_INIT_MODULE_RETCODES] = "",
},
.modules_loaded = "mod-loop-b",
.modules_not_loaded = "mod-loop-a,mod-simple-c",
);
static noreturn int modprobe_install_cmd_loop(const struct test *t)
{
const char *progname = ABS_TOP_BUILDDIR "/tools/modprobe";

110
testsuite/test-user.c Normal file
View File

@ -0,0 +1,110 @@
/*
* Copyright Red Hat
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <inttypes.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libkmod/libkmod.h>
#include "testsuite.h"
#define TEST_USER_ROOTFS TESTSUITE_ROOTFS "test-user/"
#define TEST_USER_KERNEL_DIR TEST_USER_ROOTFS "lib/modules/4.4.4/"
static const char *const test_user_config_paths[] = {
TEST_USER_ROOTFS "etc/modprobe.d",
NULL
};
static const char *const mod_name[] = {
"mod-loop-b",
"mod-weakdep",
NULL
};
static int test_user_weakdep(const struct test *t)
{
struct kmod_ctx *ctx;
int mod_name_index = 0;
int err;
ctx = kmod_new(TEST_USER_KERNEL_DIR, test_user_config_paths);
if (ctx == NULL)
exit(EXIT_FAILURE);
while (mod_name[mod_name_index]) {
struct kmod_list *list = NULL;
struct kmod_module *mod = NULL;
struct kmod_list *mod_list = NULL;
struct kmod_list *itr = NULL;
printf("%s:", mod_name[mod_name_index]);
err = kmod_module_new_from_lookup(ctx, mod_name[mod_name_index], &list);
if (list == NULL || err < 0) {
fprintf(stderr, "module %s not found in directory %s\n",
mod_name[mod_name_index],
ctx ? kmod_get_dirname(ctx) : "(missing)");
exit(EXIT_FAILURE);
}
mod = kmod_module_get_module(list);
err = kmod_module_get_weakdeps(mod, &mod_list);
if (err) {
fprintf(stderr, "weak dependencies can not be read for %s (%d)\n",
mod_name[mod_name_index], err);
exit(EXIT_FAILURE);
}
kmod_list_foreach(itr, mod_list) {
struct kmod_module *weakdep_mod = kmod_module_get_module(itr);
const char *weakdep_name = kmod_module_get_name(weakdep_mod);
printf(" %s", weakdep_name);
kmod_module_unref(weakdep_mod);
}
printf("\n");
kmod_module_unref_list(mod_list);
kmod_module_unref(mod);
kmod_module_unref_list(list);
mod_name_index++;
}
kmod_unref(ctx);
return EXIT_SUCCESS;
}
DEFINE_TEST(test_user_weakdep,
.description = "check if modprobe breaks weakdep2",
.config = {
[TC_UNAME_R] = "4.4.4",
[TC_ROOTFS] = TESTSUITE_ROOTFS "test-user",
[TC_INIT_MODULE_RETCODES] = "",
},
.need_spawn = true,
.output = {
.out = TESTSUITE_ROOTFS "test-user/correct-weakdep.txt",
});
TESTSUITE_MAIN();

View File

@ -773,10 +773,10 @@ static int cmp_modnames(const void *m1, const void *m2)
}
/*
* Store the expected module names in buf and return a list of pointers to
* them.
* Auxiliary function to store the module names in buf and return a list
* of pointers to them.
*/
static const char **read_expected_modules(const struct test *t,
static const char **read_modules(const char* modules,
char **buf, int *count)
{
const char **res;
@ -784,12 +784,8 @@ static const char **read_expected_modules(const struct test *t,
int i;
char *p;
if (t->modules_loaded[0] == '\0') {
*count = 0;
*buf = NULL;
return NULL;
}
*buf = strdup(t->modules_loaded);
*buf = strdup(modules);
if (!*buf) {
*count = -1;
return NULL;
@ -817,6 +813,36 @@ static const char **read_expected_modules(const struct test *t,
return res;
}
/*
* Store the expected module names in buf and return a list of pointers to
* them.
*/
static const char **read_expected_modules(const struct test *t,
char **buf, int *count)
{
if (t->modules_loaded[0] == '\0') {
*count = 0;
*buf = NULL;
return NULL;
}
return read_modules(t->modules_loaded, buf, count);
}
/*
* Store the unexpected module names in buf and return a list of pointers to
* them.
*/
static const char **read_unexpected_modules(const struct test *t,
char **buf, int *count)
{
if (t->modules_not_loaded[0] == '\0') {
*count = 0;
*buf = NULL;
return NULL;
}
return read_modules(t->modules_not_loaded, buf, count);
}
static char **read_loaded_modules(const struct test *t, char **buf, int *count)
{
char dirname[PATH_MAX];
@ -931,6 +957,54 @@ out_a1:
return err;
}
static int check_not_loaded_modules(const struct test *t)
{
int l1, l2, i1, i2;
const char **a1;
char **a2;
char *buf1, *buf2;
int err = false;
a1 = read_unexpected_modules(t, &buf1, &l1);
if (l1 < 0)
return err;
a2 = read_loaded_modules(t, &buf2, &l2);
if (l2 < 0)
goto out_a1;
qsort(a1, l1, sizeof(char *), cmp_modnames);
qsort(a2, l2, sizeof(char *), cmp_modnames);
i1 = i2 = 0;
err = true;
while (i1 < l1 || i2 < l2) {
int cmp;
if (i1 >= l1)
cmp = 1;
else if (i2 >= l2)
cmp = -1;
else
cmp = cmp_modnames(&a1[i1], &a2[i2]);
if (cmp == 0) {
err = false;
ERR("module %s loaded\n", a1[i1]);
i1++;
} else if (cmp < 0) {
i1++;
i2++;
} else {
err = false;
ERR("module %s is loaded but should not be\n", a2[i2]);
i2++;
}
}
free(a2);
free(buf2);
out_a1:
free(a1);
free(buf1);
return err;
}
static inline int test_run_parent(const struct test *t, int fdout[2],
int fderr[2], int fdmonitor[2], pid_t child)
{
@ -995,6 +1069,8 @@ static inline int test_run_parent(const struct test *t, int fdout[2],
match_modules = check_loaded_modules(t);
else
match_modules = true;
if (match_modules && t->modules_not_loaded)
match_modules = check_not_loaded_modules(t);
if (t->expected_fail == false) {
if (err == 0) {

View File

@ -103,6 +103,7 @@ struct test {
} output;
/* comma-separated list of loaded modules at the end of the test */
const char *modules_loaded;
const char *modules_not_loaded;
testfunc func;
const char *config[_TC_LAST];
const char *path;