On Wed, Mar 29, 2023 at 03:51:36PM +0200, Nicolas Schier wrote:
Extend delete_module() to simulate module removal in the testsuite's sysfs representation. During fake-removal, decrement refcnts on all modules that have the to-be-removed module as holder, and unlink the sysfs sub tree of the module itself. Signed-off-by: Nicolas Schier <n.schier@xxxxxx> --- Changes in v2: * new patch --- Makefile.am | 1 + testsuite/delete_module.c | 262 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 261 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index 8ba85c9..9a87824 100644 --- a/Makefile.am +++ b/Makefile.am @@ -293,6 +293,7 @@ testsuite_path_la_CPPFLAGS = $(AM_CPPFLAGS) \ testsuite_path_la_LDFLAGS = $(TESTSUITE_OVERRIDE_LIBS_LDFLAGS) testsuite_delete_module_la_LDFLAGS = $(TESTSUITE_OVERRIDE_LIBS_LDFLAGS) +testsuite_delete_module_la_LIBADD = libkmod/libkmod-internal.la
I skimmed through this and it looks good. One thing that we may need to rethink in future is if we want to keep delete_module.so linking to libkmod. We may hit a situation that we are are overriding stuff to test libkmod but for that we also use libkmod. I think that hardcoding here the kernel behavior would be ok instead of depending on what libkmod thinks it should do. if we keep libkmod, then it´d be better to get just 1 ctx: static void init_ctx(void) { ... kmod_ctx - kmod_new(NULL, NULL); } static void init_retcodes(void) { } static void init(void) { if (!need_init) return; need_init = false; init_ctx(); init_retcodes(); } but I like the additional coverage that you added here, so it's ok to keep it like this and improve in future. Reviewed-by: Lucas De Marchi <lucas.demarchi@xxxxxxxxx> Lucas De Marchi
testsuite_init_module_la_LDFLAGS = $(TESTSUITE_OVERRIDE_LIBS_LDFLAGS) testsuite_init_module_la_SOURCES = testsuite/init_module.c \ testsuite/stripped-module.h diff --git a/testsuite/delete_module.c b/testsuite/delete_module.c index f3ae20b..73fc71a 100644 --- a/testsuite/delete_module.c +++ b/testsuite/delete_module.c @@ -31,6 +31,7 @@ #include <unistd.h> #include <shared/util.h> +#include <libkmod/libkmod.h> #include "testsuite.h" @@ -135,11 +136,252 @@ static void init_retcodes(void) } } +static bool lstat_is_dir(const char *dir) +{ + struct stat st; + + if (!lstat(dir, &st)) + return S_ISDIR(st.st_mode); + + ERR("TRAP delete_module(): %s: lstat: %s\n", dir, strerror(errno)); + return false; +} + +static int unlink_tree(const char *dir) +{ + struct dirent *dent; + char *new_path; + bool is_dir; + DIR *dirp; + int ret; + + dirp = opendir(dir); + if (!dirp) { + ERR("TRAP delete_module(): %s: opendir: %s\n", dir, + strerror(errno)); + return errno; + } + + errno = ret = 0; + while (!ret && (dent = readdir(dirp))) { + if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) + continue; + + if (asprintf(&new_path, "%s/%s", dir, dent->d_name) < 0) { + ERR("TRAP delete_module(): unlink_tree: out-of-memory\n"); + return ENOMEM; + } + + if (dent->d_type == DT_UNKNOWN) + is_dir = lstat_is_dir(new_path); + else + is_dir = dent->d_type == DT_DIR; + + if (is_dir) + ret = unlink_tree(new_path); + else { + if (unlink(new_path)) { + ret = errno; + ERR("TRAP delete_module(): %s: unlink: %s\n", new_path, strerror(errno)); + } + } + + free(new_path); + } + + if (errno) { + if (!ret) + ret = errno; + ERR("TRAP delete_module(): %s: readdir: %s\n", dir, strerror(errno)); + } + + if (closedir(dirp)) { + if (!ret) + ret = errno; + ERR("TRAP delete_module(): %s: closedir: %s\n", dir, strerror(errno)); + } + + if (rmdir(dir)) { + if (ret) + ret = errno; + ERR("TRAP delete_module(): %s: rmdir: %s\n", dir, strerror(errno)); + } + + return ret; +} + +static const char *rootfs_path(void) +{ + char *rootfs; + + rootfs = getenv(S_TC_ROOTFS); + if (rootfs) + return rootfs; + + ERR("TRAP delete_module(): missing export %s?\n", + S_TC_ROOTFS); + return NULL; +} + +static char *sysfs_module_path_get(const char *module, const char *subpath) +{ + const char *rootfs = rootfs_path(); + char *sysfs_path; + int ret; + + if (!rootfs) + return NULL; + + ret = asprintf(&sysfs_path, "%s/sys/module/%s%s", + rootfs, module, subpath ? subpath : ""); + if (ret >= 0) + return sysfs_path; + + ERR("TRAP delete_module(): %s: out-of-memory\n", subpath); + return NULL; +} + +static int unlink_sysfs_module_tree(struct mod *mod) +{ + char *sysfs_path; + int ret; + + if (!(sysfs_path = sysfs_module_path_get(mod->name, NULL))) + return EFAULT; + + ret = unlink_tree(sysfs_path); + free(sysfs_path); + + return ret; +} + +static int sysfs_kmod_remove_holder(const struct kmod_module *kmod, + const char *holder) +{ + const char *name = kmod_module_get_name(kmod); + char *sysfs_mod_holders; + char *sysfs_mod_refcnt; + char refcnt_str[34]; + char *sysfs_mod; + int holders_fd; + int refcnt; + int ret; + int fd; + + if (!(sysfs_mod = sysfs_module_path_get(name, NULL)) || + !(sysfs_mod_refcnt = sysfs_module_path_get(name, "/refcnt")) || + !(sysfs_mod_holders = sysfs_module_path_get(name, "/holders"))) { + ERR("TRAP delete_module(): %s: out-of-memory\n", name); + return ENOMEM; + } + + holders_fd = open(sysfs_mod_holders, O_RDONLY|O_CLOEXEC); + if (holders_fd < 0) { + ret = errno; + ERR("TRAP delete_module(): %s: open: %s\n", sysfs_mod_holders, + strerror(errno)); + goto fail_free_pathnames; + } + + if (unlinkat(holders_fd, holder, 0)) { + ret = errno; + ERR("TRAP delete_module(): %s/%s: unlink: %s\n", + sysfs_mod_holders, holder, strerror(ret)); + goto fail_close_holders_fd; + } + + refcnt = kmod_module_get_refcnt(kmod); + if (refcnt < 0) { + ret = errno; + ERR("TRAP delete_module(): %s: Invalid refcnt or error: %d\n", + name, refcnt); + return ret; + } + + if (refcnt == 0) { + ERR("TRAP delete_module(): %s: refcnt already dropped to 0, refusing decrement\n", + name); + return EINVAL; + } + + refcnt--; + snprintf(refcnt_str, sizeof(refcnt_str), "%d\n", refcnt); + + fd = open(sysfs_mod_refcnt, O_WRONLY|O_TRUNC|O_CLOEXEC); + if (fd < 0) { + ret = errno; + ERR("TRAP delete_module(): %s: open: %s\n", sysfs_mod_refcnt, + strerror(ret)); + goto fail_free_pathnames; + } + + ret = write(fd, refcnt_str, strlen(refcnt_str)); + if (ret <= 0) { + ret = errno; + ERR("TRAP delete_module(): %s: write: %s\n", sysfs_mod_refcnt, + strerror(ret)); + } + + close(fd); + +fail_close_holders_fd: + close(holders_fd); + +fail_free_pathnames: + free(sysfs_mod_holders); + free(sysfs_mod_refcnt); + free(sysfs_mod); + + return ret; +} + +static int decrement_holders_refcnt(struct mod *removed_mod) +{ + struct kmod_list *list, *itr; + struct kmod_ctx *ctx; + int err; + + ctx = kmod_new(NULL, NULL); + if (ctx == NULL) { + ERR("TRAP delete_module(): Unable to get kmod ctx\n"); + return EFAULT; + } + + err = kmod_module_new_from_loaded(ctx, &list); + if (err < 0) { + ERR("TRAP delete_module(): Unable to get list of loaded modules: %s\n", + strerror(-err)); + goto fail_free_ctx; + } + + kmod_list_foreach(itr, list) { + struct kmod_module *kmod = kmod_module_get_module(itr); + struct kmod_list *holders, *hitr; + + holders = kmod_module_get_holders(kmod); + kmod_list_foreach(hitr, holders) { + struct kmod_module *hm = kmod_module_get_module(hitr); + const char *holder_name = kmod_module_get_name(hm); + + if (!strcmp(holder_name, removed_mod->name)) + sysfs_kmod_remove_holder(kmod, holder_name); + + kmod_module_unref(hm); + } + kmod_module_unref_list(holders); + kmod_module_unref(kmod); + } + kmod_module_unref_list(list); + +fail_free_ctx: + kmod_unref(ctx); + + return -err; +} + TS_EXPORT long delete_module(const char *name, unsigned int flags); /* - * FIXME: change /sys/module/<modname> to fake-remove a module - * * Default behavior is to exit successfully. If this is not the intended * behavior, set TESTSUITE_DELETE_MODULE_RETCODES env var. */ @@ -152,6 +394,22 @@ long delete_module(const char *modname, unsigned int flags) if (mod == NULL) return 0; + if (!mod->ret) { + /* Adjust sysfs tree after successful removal */ + + errno = decrement_holders_refcnt(mod); + if (errno) { + ERR("TRAP delete_module(): unable to decrement sysfs holders\n"); + return EFAULT; + } + + errno = unlink_sysfs_module_tree(mod); + if (errno) { + ERR("TRAP delete_module(): unable to adjust sysfs tree\n"); + return EFAULT; + } + } + errno = mod->errcode; return mod->ret; } -- 2.40.0