Message ID | 20230309-remove-holders-recursively-v2-2-8a8120269cc1@avm.de (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | kmod: modprobe: Extend holders removal to multi-level dependencies | expand |
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@avm.de> >--- >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@intel.com> 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 >
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 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; }
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@avm.de> --- Changes in v2: * new patch --- Makefile.am | 1 + testsuite/delete_module.c | 262 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 261 insertions(+), 2 deletions(-)