summaryrefslogtreecommitdiffstats
path: root/testsuite/init_module.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:03:56 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:03:56 +0000
commit18da3ffcd7f3c8a0c5f790c801b5813503c2273d (patch)
tree84caf98dc5cef3d123c56ba12e35fd67026e0693 /testsuite/init_module.c
parentInitial commit. (diff)
downloadkmod-18da3ffcd7f3c8a0c5f790c801b5813503c2273d.tar.xz
kmod-18da3ffcd7f3c8a0c5f790c801b5813503c2273d.zip
Adding upstream version 31+20240202.upstream/31+20240202
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testsuite/init_module.c')
-rw-r--r--testsuite/init_module.c403
1 files changed, 403 insertions, 0 deletions
diff --git a/testsuite/init_module.c b/testsuite/init_module.c
new file mode 100644
index 0000000..503e703
--- /dev/null
+++ b/testsuite/init_module.c
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2012-2013 ProFUSION embedded systems
+ * Copyright (C) 2012-2013 Lucas De Marchi <lucas.de.marchi@gmail.com>
+ *
+ * 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/>.
+ */
+
+#ifndef HAVE_FINIT_MODULE
+#define HAVE_FINIT_MODULE 1
+#endif
+
+#include <assert.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include <elf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+
+#include <shared/util.h>
+
+/* kmod_elf_get_section() is not exported, we need the private header */
+#include <libkmod/libkmod-internal.h>
+
+/* FIXME: hack, change name so we don't clash */
+#undef ERR
+#include "testsuite.h"
+#include "stripped-module.h"
+
+struct mod {
+ struct mod *next;
+ int ret;
+ int errcode;
+ char name[];
+};
+
+static struct mod *modules;
+static bool need_init = true;
+static struct kmod_ctx *ctx;
+
+static void parse_retcodes(struct mod *_modules, const char *s)
+{
+ const char *p;
+
+ if (s == NULL)
+ return;
+
+ for (p = s;;) {
+ struct mod *mod;
+ const char *modname;
+ char *end;
+ size_t modnamelen;
+ int ret, errcode;
+ long l;
+
+ modname = p;
+ if (modname == NULL || modname[0] == '\0')
+ break;
+
+ modnamelen = strcspn(s, ":");
+ if (modname[modnamelen] != ':')
+ break;
+
+ p = modname + modnamelen + 1;
+ if (p == NULL)
+ break;
+
+ l = strtol(p, &end, 0);
+ if (end == p || *end != ':')
+ break;
+ ret = (int) l;
+ p = end + 1;
+
+ l = strtol(p, &end, 0);
+ if (*end == ':')
+ p = end + 1;
+ else if (*end != '\0')
+ break;
+
+ errcode = (int) l;
+
+ mod = malloc(sizeof(*mod) + modnamelen + 1);
+ if (mod == NULL)
+ break;
+
+ memcpy(mod->name, modname, modnamelen);
+ mod->name[modnamelen] = '\0';
+ mod->ret = ret;
+ mod->errcode = errcode;
+ mod->next = _modules;
+ _modules = mod;
+ }
+}
+
+static int write_one_line_file(const char *fn, const char *line, int len)
+{
+ FILE *f;
+ int r;
+
+ assert(fn);
+ assert(line);
+
+ f = fopen(fn, "we");
+ if (!f)
+ return -errno;
+
+ errno = 0;
+ if (fputs(line, f) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ fflush(f);
+
+ if (ferror(f)) {
+ if (errno != 0)
+ r = -errno;
+ else
+ r = -EIO;
+ } else
+ r = 0;
+
+finish:
+ fclose(f);
+ return r;
+}
+
+static int create_sysfs_files(const char *modname)
+{
+ char buf[PATH_MAX];
+ const char *sysfsmod = "/sys/module/";
+ int len = strlen(sysfsmod);
+
+ memcpy(buf, sysfsmod, len);
+ strcpy(buf + len, modname);
+ len += strlen(modname);
+
+ assert(mkdir_p(buf, len, 0755) >= 0);
+
+ strcpy(buf + len, "/initstate");
+ return write_one_line_file(buf, "live\n", strlen("live\n"));
+}
+
+static struct mod *find_module(struct mod *_modules, const char *modname)
+{
+ struct mod *mod;
+
+ for (mod = _modules; mod != NULL; mod = mod->next) {
+ if (streq(mod->name, modname))
+ return mod;
+ }
+
+ return NULL;
+}
+
+static void init_retcodes(void)
+{
+ const char *s;
+
+ if (!need_init)
+ return;
+
+ need_init = false;
+ s = getenv(S_TC_INIT_MODULE_RETCODES);
+ if (s == NULL) {
+ fprintf(stderr, "TRAP init_module(): missing export %s?\n",
+ S_TC_INIT_MODULE_RETCODES);
+ }
+
+ ctx = kmod_new(NULL, NULL);
+
+ parse_retcodes(modules, s);
+}
+
+static inline bool module_is_inkernel(const char *modname)
+{
+ struct kmod_module *mod;
+ int state;
+ bool ret;
+
+ if (kmod_module_new_from_name(ctx, modname, &mod) < 0)
+ return false;
+
+ state = kmod_module_get_initstate(mod);
+
+ if (state == KMOD_MODULE_LIVE ||
+ state == KMOD_MODULE_BUILTIN)
+ ret = true;
+ else
+ ret = false;
+
+ kmod_module_unref(mod);
+
+ return ret;
+}
+
+static uint8_t elf_identify(void *mem)
+{
+ uint8_t *p = mem;
+ return p[EI_CLASS];
+}
+
+TS_EXPORT long init_module(void *mem, unsigned long len, const char *args);
+
+/*
+ * Default behavior is to try to mimic init_module behavior inside the kernel.
+ * If it is a simple test that you know the error code, set the return code
+ * in TESTSUITE_INIT_MODULE_RETCODES env var instead.
+ *
+ * The exception is when the module name is not find in the memory passed.
+ * This is because we want to be able to pass dummy modules (and not real
+ * ones) and it still work.
+ */
+long init_module(void *mem, unsigned long len, const char *args)
+{
+ const char *modname;
+ struct kmod_elf *elf;
+ struct mod *mod;
+ const void *buf;
+ uint64_t bufsize;
+ int err;
+ uint8_t class;
+ off_t offset;
+
+ init_retcodes();
+
+ elf = kmod_elf_new(mem, len);
+ if (elf == NULL)
+ return 0;
+
+ err = kmod_elf_get_section(elf, ".gnu.linkonce.this_module", &buf,
+ &bufsize);
+ kmod_elf_unref(elf);
+
+ /* We couldn't parse the ELF file. Just exit as if it was successful */
+ if (err < 0)
+ return 0;
+
+ /* We need to open both 32 and 64 bits module - hack! */
+ class = elf_identify(mem);
+ if (class == ELFCLASS64)
+ offset = MODULE_NAME_OFFSET_64;
+ else
+ offset = MODULE_NAME_OFFSET_32;
+
+ modname = (char *)buf + offset;
+ mod = find_module(modules, modname);
+ if (mod != NULL) {
+ errno = mod->errcode;
+ err = mod->ret;
+ } else if (module_is_inkernel(modname)) {
+ err = -1;
+ errno = EEXIST;
+ } else
+ err = 0;
+
+ if (err == 0)
+ create_sysfs_files(modname);
+
+ return err;
+}
+
+static int check_kernel_version(int major, int minor)
+{
+ struct utsname u;
+ const char *p;
+ int maj = 0, min = 0;
+
+ if (uname(&u) < 0)
+ return false;
+ for (p = u.release; *p >= '0' && *p <= '9'; p++)
+ maj = maj * 10 + *p - '0';
+ if (*p == '.')
+ for (p++; *p >= '0' && *p <= '9'; p++)
+ min = min * 10 + *p - '0';
+ if (maj > major || (maj == major && min >= minor))
+ return true;
+ return false;
+}
+
+
+TS_EXPORT int finit_module(const int fd, const char *args, const int flags);
+
+int finit_module(const int fd, const char *args, const int flags)
+{
+ int err;
+ void *mem;
+ unsigned long len;
+ struct stat st;
+
+ if (!check_kernel_version(3, 8)) {
+ errno = ENOSYS;
+ return -1;
+ }
+ if (fstat(fd, &st) < 0)
+ return -1;
+
+ len = st.st_size;
+ mem = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (mem == MAP_FAILED)
+ return -1;
+
+ err = init_module(mem, len, args);
+ munmap(mem, len);
+
+ return err;
+}
+
+TS_EXPORT long int syscall(long int __sysno, ...)
+{
+ va_list ap;
+ long ret;
+
+ if (__sysno == -1) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ if (__sysno == __NR_finit_module) {
+ const char *args;
+ int flags;
+ int fd;
+
+ va_start(ap, __sysno);
+
+ fd = va_arg(ap, int);
+ args = va_arg(ap, const char *);
+ flags = va_arg(ap, int);
+
+ ret = finit_module(fd, args, flags);
+
+ va_end(ap);
+ return ret;
+ }
+
+ if (__sysno == __NR_gettid) {
+ static void *nextlib = NULL;
+ static long (*nextlib_syscall)(long number, ...);
+
+ if (nextlib_syscall == NULL) {
+#ifdef RTLD_NEXT
+ nextlib = RTLD_NEXT;
+#else
+ nextlib = dlopen("libc.so.6", RTLD_LAZY);
+#endif
+ nextlib_syscall = dlsym(nextlib, "syscall");
+ if (nextlib_syscall == NULL) {
+ fprintf(stderr, "FIXME FIXME FIXME: could not load syscall symbol: %s\n",
+ dlerror());
+ abort();
+ }
+ }
+
+ return nextlib_syscall(__NR_gettid);
+ }
+
+ /*
+ * FIXME: no way to call the libc function due since this is a
+ * variadic argument function and we don't have a vsyscall() variant
+ * this may fail if a library or process is trying to call syscall()
+ * directly, for example to implement gettid().
+ */
+ fprintf(stderr, "FIXME FIXME FIXME: could not wrap call to syscall(%ld), this should not happen\n",
+ __sysno);
+
+ abort();
+}
+
+/* the test is going away anyway, but lets keep valgrind happy */
+void free_resources(void) __attribute__((destructor));
+void free_resources(void)
+{
+ while (modules) {
+ struct mod *mod = modules->next;
+ free(modules);
+ modules = mod;
+ }
+
+ if (ctx)
+ kmod_unref(ctx);
+}