]> git.ipfire.org Git - thirdparty/kmod.git/blobdiff - libkmod/libkmod-module.c
libkmod: Fix use of sizeof instead of ARRAY_SIZE
[thirdparty/kmod.git] / libkmod / libkmod-module.c
index cc2c076f27a0eb95311b0e81510153cacf800301..efdd679851ce1134e4f7df50cc8073afa33168e2 100644 (file)
  * 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, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
  */
 
 #include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stddef.h>
-#include <stdarg.h>
-#include <unistd.h>
-#include <errno.h>
-#include <string.h>
 #include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fnmatch.h>
 #include <inttypes.h>
 #include <limits.h>
-#include <dirent.h>
-#include <sys/stat.h>
-#include <sys/types.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/wait.h>
-#include <string.h>
-#include <fnmatch.h>
-
 #ifdef HAVE_LINUX_MODULE_H
 #include <linux/module.h>
 #endif
 
+#include <shared/util.h>
+
 #include "libkmod.h"
-#include "libkmod-private.h"
+#include "libkmod-internal.h"
 
 /**
  * SECTION:libkmod-module
  * @short_description: operate on kernel modules
  */
 
+enum kmod_module_builtin {
+    KMOD_MODULE_BUILTIN_UNKNOWN,
+    KMOD_MODULE_BUILTIN_NO,
+    KMOD_MODULE_BUILTIN_YES,
+};
+
 /**
  * kmod_module:
  *
@@ -75,6 +80,13 @@ struct kmod_module {
                bool remove_commands : 1;
        } init;
 
+       /*
+        * mark if module is builtin, i.e. it's present on modules.builtin
+        * file. This is set as soon as it is needed or as soon as we know
+        * about it, i.e. the module was created from builtin lookup.
+        */
+       enum kmod_module_builtin builtin;
+
        /*
         * private field used by kmod_module_get_probe_list() to detect
         * dependency loops
@@ -88,11 +100,11 @@ struct kmod_module {
        bool ignorecmd : 1;
 
        /*
-        * if module was created by searching the modules.builtin file, this
-        * is set. There's nothing much useful one can do with such a
-        * "module", except knowing it's builtin.
+        * set by kmod_module_get_probe_list: indicates whether this is the
+        * module the user asked for or its dependency, or whether this
+        * is a softdep only
         */
-       bool builtin : 1;
+       bool required : 1;
 };
 
 static inline const char *path_join(const char *path, size_t prefixlen,
@@ -164,7 +176,7 @@ int kmod_module_parse_depline(struct kmod_module *mod, char *line)
        p++;
        for (p = strtok_r(p, " \t", &saveptr); p != NULL;
                                        p = strtok_r(NULL, " \t", &saveptr)) {
-               struct kmod_module *depmod;
+               struct kmod_module *depmod = NULL;
                const char *path;
 
                path = path_join(p, dirnamelen, buf);
@@ -206,9 +218,24 @@ void kmod_module_set_visited(struct kmod_module *mod, bool visited)
 
 void kmod_module_set_builtin(struct kmod_module *mod, bool builtin)
 {
-       mod->builtin = builtin;
+       mod->builtin =
+               builtin ? KMOD_MODULE_BUILTIN_YES : KMOD_MODULE_BUILTIN_NO;
 }
 
+void kmod_module_set_required(struct kmod_module *mod, bool required)
+{
+       mod->required = required;
+}
+
+bool kmod_module_is_builtin(struct kmod_module *mod)
+{
+       if (mod->builtin == KMOD_MODULE_BUILTIN_UNKNOWN) {
+               kmod_module_set_builtin(mod,
+                                       kmod_lookup_alias_is_builtin(mod->ctx, mod->name));
+       }
+
+       return mod->builtin == KMOD_MODULE_BUILTIN_YES;
+}
 /*
  * Memory layout with alias:
  *
@@ -252,10 +279,8 @@ static int kmod_module_new(struct kmod_ctx *ctx, const char *key,
                keylen = namelen + aliaslen + 1;
 
        m = malloc(sizeof(*m) + (alias == NULL ? 1 : 2) * (keylen + 1));
-       if (m == NULL) {
-               free(m);
+       if (m == NULL)
                return -ENOMEM;
-       }
 
        memset(m, 0, sizeof(*m));
 
@@ -406,15 +431,18 @@ KMOD_EXPORT int kmod_module_new_from_path(struct kmod_ctx *ctx,
                        return -EEXIST;
                }
 
-               *mod = kmod_module_ref(m);
-               return 0;
-       }
+               kmod_module_ref(m);
+       } else {
+               err = kmod_module_new(ctx, name, name, namelen, NULL, 0, &m);
+               if (err < 0) {
+                       free(abspath);
+                       return err;
+               }
 
-       err = kmod_module_new(ctx, name, name, namelen, NULL, 0, &m);
-       if (err < 0)
-               return err;
+               m->path = abspath;
+       }
 
-       m->path = abspath;
+       m->builtin = KMOD_MODULE_BUILTIN_NO;
        *mod = m;
 
        return 0;
@@ -471,13 +499,26 @@ KMOD_EXPORT struct kmod_module *kmod_module_ref(struct kmod_module *mod)
        return mod;
 }
 
-#define CHECK_ERR_AND_FINISH(_err, _label_err, _list, label_finish)    \
-       do {                                                            \
-               if ((_err) < 0)                                         \
-                       goto _label_err;                                \
-               if (*(_list) != NULL)                                   \
-                       goto finish;                                    \
-       } while (0)
+typedef int (*lookup_func)(struct kmod_ctx *ctx, const char *name, struct kmod_list **list) __attribute__((nonnull(1, 2, 3)));
+
+static int __kmod_module_new_from_lookup(struct kmod_ctx *ctx, const lookup_func lookup[],
+                                        size_t lookup_count, const char *s,
+                                        struct kmod_list **list)
+{
+       unsigned int i;
+
+       for (i = 0; i < lookup_count; i++) {
+               int err;
+
+               err = lookup[i](ctx, s, list);
+               if (err < 0 && err != -ENOSYS)
+                       return err;
+               else if (*list != NULL)
+                       return 0;
+       }
+
+       return 0;
+}
 
 /**
  * kmod_module_new_from_lookup:
@@ -493,7 +534,7 @@ KMOD_EXPORT struct kmod_module *kmod_module_ref(struct kmod_module *mod)
  *
  * The search order is: 1. aliases in configuration file; 2. module names in
  * modules.dep index; 3. symbol aliases in modules.symbols index; 4. aliases
- * in modules.alias index.
+ * from install commands; 5. builtin indexes from kernel.
  *
  * The initial refcount is 1, and needs to be decremented to release the
  * resources of the kmod_module. The returned @list must be released by
@@ -510,8 +551,17 @@ KMOD_EXPORT int kmod_module_new_from_lookup(struct kmod_ctx *ctx,
                                                const char *given_alias,
                                                struct kmod_list **list)
 {
-       int err;
+       const lookup_func lookup[] = {
+               kmod_lookup_alias_from_config,
+               kmod_lookup_alias_from_moddep_file,
+               kmod_lookup_alias_from_symbols_file,
+               kmod_lookup_alias_from_commands,
+               kmod_lookup_alias_from_aliases_file,
+               kmod_lookup_alias_from_builtin_file,
+               kmod_lookup_alias_from_kernel_builtin_file,
+       };
        char alias[PATH_MAX];
+       int err;
 
        if (ctx == NULL || given_alias == NULL)
                return -ENOENT;
@@ -528,40 +578,75 @@ KMOD_EXPORT int kmod_module_new_from_lookup(struct kmod_ctx *ctx,
 
        DBG(ctx, "input alias=%s, normalized=%s\n", given_alias, alias);
 
-       /* Aliases from config file override all the others */
-       err = kmod_lookup_alias_from_config(ctx, alias, list);
-       CHECK_ERR_AND_FINISH(err, fail, list, finish);
+       err = __kmod_module_new_from_lookup(ctx, lookup, ARRAY_SIZE(lookup),
+                                           alias, list);
 
-       DBG(ctx, "lookup modules.dep %s\n", alias);
-       err = kmod_lookup_alias_from_moddep_file(ctx, alias, list);
-       CHECK_ERR_AND_FINISH(err, fail, list, finish);
+       DBG(ctx, "lookup=%s found=%d\n", alias, err >= 0 && *list);
 
-       DBG(ctx, "lookup modules.symbols %s\n", alias);
-       err = kmod_lookup_alias_from_symbols_file(ctx, alias, list);
-       CHECK_ERR_AND_FINISH(err, fail, list, finish);
+       if (err < 0) {
+               kmod_module_unref_list(*list);
+               *list = NULL;
+       }
 
-       DBG(ctx, "lookup install and remove commands %s\n", alias);
-       err = kmod_lookup_alias_from_commands(ctx, alias, list);
-       CHECK_ERR_AND_FINISH(err, fail, list, finish);
+       return err;
+}
 
-       DBG(ctx, "lookup modules.aliases %s\n", alias);
-       err = kmod_lookup_alias_from_aliases_file(ctx, alias, list);
-       CHECK_ERR_AND_FINISH(err, fail, list, finish);
+/**
+ * kmod_module_new_from_name_lookup:
+ * @ctx: kmod library context
+ * @modname: module name to look for
+ * @mod: returned module on success
+ *
+ * Lookup by module name, without considering possible aliases. This is similar
+ * to kmod_module_new_from_lookup(), but don't consider as source indexes and
+ * configurations that work with aliases. When succesful, this always resolves
+ * to one and only one module.
+ *
+ * The search order is: 1. module names in modules.dep index;
+ * 2. builtin indexes from kernel.
+ *
+ * The initial refcount is 1, and needs to be decremented to release the
+ * resources of the kmod_module. Since libkmod keeps track of all
+ * kmod_modules created, they are all released upon @ctx destruction too. Do
+ * not unref @ctx before all the desired operations with the returned list are
+ * completed.
+ *
+ * Returns: 0 on success or < 0 otherwise. It fails if any of the lookup
+ * methods failed, which is basically due to memory allocation failure. If
+ * module is not found, it still returns 0, but @mod is left untouched.
+ */
+KMOD_EXPORT int kmod_module_new_from_name_lookup(struct kmod_ctx *ctx,
+                                                const char *modname,
+                                                struct kmod_module **mod)
+{
+       const lookup_func lookup[] = {
+               kmod_lookup_alias_from_moddep_file,
+               kmod_lookup_alias_from_builtin_file,
+               kmod_lookup_alias_from_kernel_builtin_file,
+       };
+       char name_norm[PATH_MAX];
+       struct kmod_list *list = NULL;
+       int err;
 
-       DBG(ctx, "lookup modules.builtin %s\n", alias);
-       err = kmod_lookup_alias_from_builtin_file(ctx, alias, list);
-       CHECK_ERR_AND_FINISH(err, fail, list, finish);
+       if (ctx == NULL || modname == NULL || mod == NULL)
+               return -ENOENT;
+
+       modname_normalize(modname, name_norm, NULL);
+
+       DBG(ctx, "input modname=%s, normalized=%s\n", modname, name_norm);
+
+       err = __kmod_module_new_from_lookup(ctx, lookup, ARRAY_SIZE(lookup),
+                                           name_norm, &list);
+
+       DBG(ctx, "lookup=%s found=%d\n", name_norm, err >= 0 && list);
+
+       if (err >= 0 && list != NULL)
+               *mod = kmod_module_get_module(list);
+
+       kmod_module_unref_list(list);
 
-finish:
-       DBG(ctx, "lookup %s=%d, list=%p\n", alias, err, *list);
-       return err;
-fail:
-       DBG(ctx, "Failed to lookup %s\n", alias);
-       kmod_module_unref_list(*list);
-       *list = NULL;
        return err;
 }
-#undef CHECK_ERR_AND_FINISH
 
 /**
  * kmod_module_unref_list:
@@ -738,15 +823,11 @@ extern long delete_module(const char *name, unsigned int flags);
 /**
  * kmod_module_remove_module:
  * @mod: kmod module
- * @flags: flags to pass to Linux kernel when removing the module, valid flags are
+ * @flags: flags to pass to Linux kernel when removing the module. The only valid flag is
  * KMOD_REMOVE_FORCE: force remove module regardless if it's still in
  * use by a kernel subsystem or other process;
- * KMOD_REMOVE_NOWAIT: return immediately. It will fail if the module
- * is in using and KMOD_REMOVE_FORCE is not specified.
- * If this module is in use by any kernel subsystem or process, not using
- * this flag will cause the call to block indefinitely, until the module
- * is not in use anymore. Always use this flag, it's deprecated not using
- * it and the default behavior might change in future to always set it.
+ * KMOD_REMOVE_NOWAIT is always enforced, causing us to pass O_NONBLOCK to
+ * delete_module(2).
  *
  * Remove a module from Linux kernel.
  *
@@ -760,8 +841,9 @@ KMOD_EXPORT int kmod_module_remove_module(struct kmod_module *mod,
        if (mod == NULL)
                return -ENOENT;
 
-       /* Filter out other flags */
-       flags &= (KMOD_REMOVE_FORCE | KMOD_REMOVE_NOWAIT);
+       /* Filter out other flags and force ONONBLOCK */
+       flags &= KMOD_REMOVE_FORCE;
+       flags |= KMOD_REMOVE_NOWAIT;
 
        err = delete_module(mod->name, flags);
        if (err != 0) {
@@ -796,8 +878,7 @@ KMOD_EXPORT int kmod_module_insert_module(struct kmod_module *mod,
        int err;
        const void *mem;
        off_t size;
-       struct kmod_file *file;
-       struct kmod_elf *elf = NULL;
+       struct kmod_elf *elf;
        const char *path;
        const char *args = options ? options : "";
 
@@ -807,16 +888,18 @@ KMOD_EXPORT int kmod_module_insert_module(struct kmod_module *mod,
        path = kmod_module_get_path(mod);
        if (path == NULL) {
                ERR(mod->ctx, "could not find module by name='%s'\n", mod->name);
-               return -ENOSYS;
+               return -ENOENT;
        }
 
-       file = kmod_file_open(mod->ctx, path);
-       if (file == NULL) {
-               err = -errno;
-               return err;
+       if (!mod->file) {
+               mod->file = kmod_file_open(mod->ctx, path);
+               if (mod->file == NULL) {
+                       err = -errno;
+                       return err;
+               }
        }
 
-       if (kmod_file_get_direct(file)) {
+       if (kmod_file_get_direct(mod->file)) {
                unsigned int kernel_flags = 0;
 
                if (flags & KMOD_INSERT_FORCE_VERMAGIC)
@@ -824,19 +907,16 @@ KMOD_EXPORT int kmod_module_insert_module(struct kmod_module *mod,
                if (flags & KMOD_INSERT_FORCE_MODVERSION)
                        kernel_flags |= MODULE_INIT_IGNORE_MODVERSIONS;
 
-               err = finit_module(kmod_file_get_fd(file), args, kernel_flags);
+               err = finit_module(kmod_file_get_fd(mod->file), args, kernel_flags);
                if (err == 0 || errno != ENOSYS)
                        goto init_finished;
        }
 
-       size = kmod_file_get_size(file);
-       mem = kmod_file_get_contents(file);
-
        if (flags & (KMOD_INSERT_FORCE_VERMAGIC | KMOD_INSERT_FORCE_MODVERSION)) {
-               elf = kmod_elf_new(mem, size);
+               elf = kmod_file_get_elf(mod->file);
                if (elf == NULL) {
                        err = -errno;
-                       goto elf_failed;
+                       return err;
                }
 
                if (flags & KMOD_INSERT_FORCE_MODVERSION) {
@@ -852,7 +932,10 @@ KMOD_EXPORT int kmod_module_insert_module(struct kmod_module *mod,
                }
 
                mem = kmod_elf_get_memory(elf);
+       } else {
+               mem = kmod_file_get_contents(mod->file);
        }
+       size = kmod_file_get_size(mod->file);
 
        err = init_module(mem, size, args);
 init_finished:
@@ -860,12 +943,6 @@ init_finished:
                err = -errno;
                INFO(mod->ctx, "Failed to insert module '%s': %m\n", path);
        }
-
-       if (elf != NULL)
-               kmod_elf_unref(elf);
-elf_failed:
-       kmod_file_unref(file);
-
        return err;
 }
 
@@ -923,7 +1000,8 @@ KMOD_EXPORT int kmod_module_apply_filter(const struct kmod_ctx *ctx,
                                module_is_blacklisted(mod))
                        continue;
 
-               if ((filter_type & KMOD_FILTER_BUILTIN) && mod->builtin)
+               if ((filter_type & KMOD_FILTER_BUILTIN)
+                   && kmod_module_is_builtin(mod))
                        continue;
 
                node = kmod_list_append(*output, mod);
@@ -954,14 +1032,19 @@ static int command_do(struct kmod_module *mod, const char *type,
        err = system(cmd);
        unsetenv("MODPROBE_MODULE");
 
-       if (err == -1 || WEXITSTATUS(err)) {
-               ERR(mod->ctx, "Error running %s command for %s\n",
-                                                               type, modname);
-               if (err != -1)
-                       err = -WEXITSTATUS(err);
+       if (err == -1) {
+               ERR(mod->ctx, "Could not run %s command '%s' for module %s: %m\n",
+                   type, cmd, modname);
+               return -EINVAL;
        }
 
-       return err;
+       if (WEXITSTATUS(err)) {
+               ERR(mod->ctx, "Error running %s command '%s' for module %s: retcode %d\n",
+                   type, cmd, modname, WEXITSTATUS(err));
+               return -EINVAL;
+       }
+
+       return 0;
 }
 
 struct probe_insert_cb {
@@ -974,7 +1057,8 @@ static int module_do_install_commands(struct kmod_module *mod,
                                        struct probe_insert_cb *cb)
 {
        const char *command = kmod_module_get_install_commands(mod);
-       char *p, *cmd;
+       char *p;
+       _cleanup_free_ char *cmd;
        int err;
        size_t cmdlen, options_len, varlen;
 
@@ -997,10 +1081,9 @@ static int module_do_install_commands(struct kmod_module *mod,
                size_t slen = cmdlen - varlen + options_len;
                char *suffix = p + varlen;
                char *s = malloc(slen + 1);
-               if (s == NULL) {
-                       free(cmd);
+               if (!s)
                        return -ENOMEM;
-               }
+
                memcpy(s, cmd, p - cmd);
                memcpy(s + prefixlen, options, options_len);
                memcpy(s + prefixlen + options_len, suffix, suffixlen);
@@ -1016,8 +1099,6 @@ static int module_do_install_commands(struct kmod_module *mod,
        else
                err = command_do(mod, "install", cmd);
 
-       free(cmd);
-
        return err;
 }
 
@@ -1048,6 +1129,7 @@ static char *module_options_concat(const char *opt, const char *xopt)
 }
 
 static int __kmod_module_get_probe_list(struct kmod_module *mod,
+                                               bool required,
                                                bool ignorecmd,
                                                struct kmod_list **list);
 
@@ -1067,7 +1149,7 @@ static int __kmod_module_fill_softdep(struct kmod_module *mod,
 
        kmod_list_foreach(l, pre) {
                struct kmod_module *m = l->data;
-               err = __kmod_module_get_probe_list(m, false, list);
+               err = __kmod_module_get_probe_list(m, false, false, list);
                if (err < 0)
                        goto fail;
        }
@@ -1083,7 +1165,7 @@ static int __kmod_module_fill_softdep(struct kmod_module *mod,
 
        kmod_list_foreach(l, post) {
                struct kmod_module *m = l->data;
-               err = __kmod_module_get_probe_list(m, false, list);
+               err = __kmod_module_get_probe_list(m, false, false, list);
                if (err < 0)
                        goto fail;
        }
@@ -1097,6 +1179,7 @@ fail:
 
 /* re-entrant */
 static int __kmod_module_get_probe_list(struct kmod_module *mod,
+                                               bool required,
                                                bool ignorecmd,
                                                struct kmod_list **list)
 {
@@ -1111,6 +1194,19 @@ static int __kmod_module_get_probe_list(struct kmod_module *mod,
        mod->visited = true;
 
        dep = kmod_module_get_dependencies(mod);
+       if (required) {
+               /*
+                * Called from kmod_module_probe_insert_module(); set the
+                * ->required flag on mod and all its dependencies before
+                * they are possibly visited through some softdeps.
+                */
+               mod->required = true;
+               kmod_list_foreach(l, dep) {
+                       struct kmod_module *m = l->data;
+                       m->required = true;
+               }
+       }
+
        kmod_list_foreach(l, dep) {
                struct kmod_module *m = l->data;
                err = __kmod_module_fill_softdep(m, list);
@@ -1148,8 +1244,9 @@ static int kmod_module_get_probe_list(struct kmod_module *mod,
         * Make sure we don't get screwed by previous calls to this function
         */
        kmod_set_modules_visited(mod->ctx, false);
+       kmod_set_modules_required(mod->ctx, false);
 
-       err = __kmod_module_get_probe_list(mod, ignorecmd, list);
+       err = __kmod_module_get_probe_list(mod, true, ignorecmd, list);
        if (err < 0) {
                kmod_module_unref_list(*list);
                *list = NULL;
@@ -1310,8 +1407,12 @@ finish_module:
                                (flags & KMOD_PROBE_FAIL_ON_LOADED))
                        break;
 
-               if (err == -EEXIST)
+               /*
+                * Ignore errors from softdeps
+                */
+               if (err == -EEXIST || !m->required)
                        err = 0;
+
                else if (err < 0)
                        break;
        }
@@ -1624,13 +1725,14 @@ KMOD_EXPORT int kmod_module_new_from_loaded(struct kmod_ctx *ctx,
                struct kmod_module *m;
                struct kmod_list *node;
                int err;
+               size_t len = strlen(line);
                char *saveptr, *name = strtok_r(line, " \t", &saveptr);
 
                err = kmod_module_new_from_name(ctx, name, &m);
                if (err < 0) {
                        ERR(ctx, "could not get module from name '%s': %s\n",
                                name, strerror(-err));
-                       continue;
+                       goto eat_line;
                }
 
                node = kmod_list_append(l, m);
@@ -1640,6 +1742,9 @@ KMOD_EXPORT int kmod_module_new_from_loaded(struct kmod_ctx *ctx,
                        ERR(ctx, "out of memory\n");
                        kmod_module_unref(m);
                }
+eat_line:
+               while (line[len - 1] != '\n' && fgets(line, sizeof(line), fp))
+                       len = strlen(line);
        }
 
        fclose(fp);
@@ -1694,7 +1799,8 @@ KMOD_EXPORT int kmod_module_get_initstate(const struct kmod_module *mod)
        if (mod == NULL)
                return -ENOENT;
 
-       if (mod->builtin)
+       /* remove const: this can only change internal state */
+       if (kmod_module_is_builtin((struct kmod_module *)mod))
                return KMOD_MODULE_BUILTIN;
 
        pathlen = snprintf(path, sizeof(path),
@@ -1710,7 +1816,7 @@ KMOD_EXPORT int kmod_module_get_initstate(const struct kmod_module *mod)
                        struct stat st;
                        path[pathlen - (sizeof("/initstate") - 1)] = '\0';
                        if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
-                               return KMOD_MODULE_BUILTIN;
+                               return KMOD_MODULE_COMING;
                }
 
                DBG(mod->ctx, "could not open '%s': %s\n",
@@ -1764,7 +1870,7 @@ KMOD_EXPORT long kmod_module_get_size(const struct kmod_module *mod)
         * loaded.
         */
        snprintf(line, sizeof(line), "/sys/module/%s", mod->name);
-       dfd = open(line, O_RDONLY);
+       dfd = open(line, O_RDONLY|O_CLOEXEC);
        if (dfd < 0)
                return -errno;
 
@@ -1783,16 +1889,18 @@ KMOD_EXPORT long kmod_module_get_size(const struct kmod_module *mod)
                int err = -errno;
                ERR(mod->ctx,
                    "could not open /proc/modules: %s\n", strerror(errno));
+               close(dfd);
                return err;
        }
 
        while (fgets(line, sizeof(line), fp)) {
+               size_t len = strlen(line);
                char *saveptr, *endptr, *tok = strtok_r(line, " \t", &saveptr);
                long value;
 
                lineno++;
                if (tok == NULL || !streq(tok, mod->name))
-                       continue;
+                       goto eat_line;
 
                tok = strtok_r(NULL, " \t", &saveptr);
                if (tok == NULL) {
@@ -1810,6 +1918,9 @@ KMOD_EXPORT long kmod_module_get_size(const struct kmod_module *mod)
 
                size = value;
                break;
+eat_line:
+               while (line[len - 1] != '\n' && fgets(line, sizeof(line), fp))
+                       len = strlen(line);
        }
        fclose(fp);
 
@@ -1825,7 +1936,7 @@ done:
  * Get the ref count of this @mod, as returned by Linux Kernel, by reading
  * /sys filesystem.
  *
- * Returns: 0 on success or < 0 on failure.
+ * Returns: the reference count on success or < 0 on failure.
  */
 KMOD_EXPORT int kmod_module_get_refcnt(const struct kmod_module *mod)
 {
@@ -1869,6 +1980,7 @@ KMOD_EXPORT struct kmod_list *kmod_module_get_holders(const struct kmod_module *
 {
        char dname[PATH_MAX];
        struct kmod_list *list = NULL;
+       struct dirent *dent;
        DIR *d;
 
        if (mod == NULL || mod->ctx == NULL)
@@ -1883,32 +1995,22 @@ KMOD_EXPORT struct kmod_list *kmod_module_get_holders(const struct kmod_module *
                return NULL;
        }
 
-       for (;;) {
-               struct dirent de, *entp;
+       for (dent = readdir(d); dent != NULL; dent = readdir(d)) {
                struct kmod_module *holder;
                struct kmod_list *l;
                int err;
 
-               err = readdir_r(d, &de, &entp);
-               if (err != 0) {
-                       ERR(mod->ctx, "could not iterate for module '%s': %s\n",
-                                               mod->name, strerror(-err));
-                       goto fail;
-               }
-
-               if (entp == NULL)
-                       break;
-
-               if (de.d_name[0] == '.') {
-                       if (de.d_name[1] == '\0' ||
-                           (de.d_name[1] == '.' && de.d_name[2] == '\0'))
+               if (dent->d_name[0] == '.') {
+                       if (dent->d_name[1] == '\0' ||
+                           (dent->d_name[1] == '.' && dent->d_name[2] == '\0'))
                                continue;
                }
 
-               err = kmod_module_new_from_name(mod->ctx, de.d_name, &holder);
+               err = kmod_module_new_from_name(mod->ctx, dent->d_name,
+                                               &holder);
                if (err < 0) {
                        ERR(mod->ctx, "could not create module for '%s': %s\n",
-                               de.d_name, strerror(-err));
+                               dent->d_name, strerror(-err));
                        goto fail;
                }
 
@@ -1958,6 +2060,7 @@ KMOD_EXPORT struct kmod_list *kmod_module_get_sections(const struct kmod_module
 {
        char dname[PATH_MAX];
        struct kmod_list *list = NULL;
+       struct dirent *dent;
        DIR *d;
        int dfd;
 
@@ -1975,31 +2078,23 @@ KMOD_EXPORT struct kmod_list *kmod_module_get_sections(const struct kmod_module
 
        dfd = dirfd(d);
 
-       for (;;) {
-               struct dirent de, *entp;
+       for (dent = readdir(d); dent; dent = readdir(d)) {
                struct kmod_module_section *section;
                struct kmod_list *l;
                unsigned long address;
                size_t namesz;
                int fd, err;
 
-               err = readdir_r(d, &de, &entp);
-               if (err != 0) {
-                       ERR(mod->ctx, "could not iterate for module '%s': %s\n",
-                                               mod->name, strerror(-err));
-                       goto fail;
-               }
-
-               if (de.d_name[0] == '.') {
-                       if (de.d_name[1] == '\0' ||
-                           (de.d_name[1] == '.' && de.d_name[2] == '\0'))
+               if (dent->d_name[0] == '.') {
+                       if (dent->d_name[1] == '\0' ||
+                           (dent->d_name[1] == '.' && dent->d_name[2] == '\0'))
                                continue;
                }
 
-               fd = openat(dfd, de.d_name, O_RDONLY|O_CLOEXEC);
+               fd = openat(dfd, dent->d_name, O_RDONLY|O_CLOEXEC);
                if (fd < 0) {
                        ERR(mod->ctx, "could not open '%s/%s': %m\n",
-                                                       dname, de.d_name);
+                                                       dname, dent->d_name);
                        goto fail;
                }
 
@@ -2007,11 +2102,11 @@ KMOD_EXPORT struct kmod_list *kmod_module_get_sections(const struct kmod_module
                close(fd);
                if (err < 0) {
                        ERR(mod->ctx, "could not read long from '%s/%s': %m\n",
-                                                       dname, de.d_name);
+                                                       dname, dent->d_name);
                        goto fail;
                }
 
-               namesz = strlen(de.d_name) + 1;
+               namesz = strlen(dent->d_name) + 1;
                section = malloc(sizeof(*section) + namesz);
 
                if (section == NULL) {
@@ -2020,7 +2115,7 @@ KMOD_EXPORT struct kmod_list *kmod_module_get_sections(const struct kmod_module
                }
 
                section->address = address;
-               memcpy(section->name, de.d_name, namesz);
+               memcpy(section->name, dent->d_name, namesz);
 
                l = kmod_list_append(list, section);
                if (l != NULL) {
@@ -2161,6 +2256,63 @@ static struct kmod_list *kmod_module_info_append(struct kmod_list **list, const
        return n;
 }
 
+static char *kmod_module_hex_to_str(const char *hex, size_t len)
+{
+       char *str;
+       int i;
+       int j;
+       const size_t line_limit = 20;
+       size_t str_len;
+
+       str_len = len * 3; /* XX: or XX\0 */
+       str_len += ((str_len + line_limit - 1) / line_limit - 1) * 3; /* \n\t\t */
+
+       str = malloc(str_len);
+       if (str == NULL)
+               return NULL;
+
+       for (i = 0, j = 0; i < (int)len; i++) {
+               j += sprintf(str + j, "%02X", (unsigned char)hex[i]);
+               if (i < (int)len - 1) {
+                       str[j++] = ':';
+
+                       if ((i + 1) % line_limit == 0)
+                               j += sprintf(str + j, "\n\t\t");
+               }
+       }
+       return str;
+}
+
+static struct kmod_list *kmod_module_info_append_hex(struct kmod_list **list,
+                                                    const char *key,
+                                                    size_t keylen,
+                                                    const char *value,
+                                                    size_t valuelen)
+{
+       char *hex;
+       struct kmod_list *n;
+
+       if (valuelen > 0) {
+               /* Display as 01:12:DE:AD:BE:EF:... */
+               hex = kmod_module_hex_to_str(value, valuelen);
+               if (hex == NULL)
+                       goto list_error;
+               n = kmod_module_info_append(list, key, keylen, hex, strlen(hex));
+               free(hex);
+               if (n == NULL)
+                       goto list_error;
+       } else {
+               n = kmod_module_info_append(list, key, keylen, NULL, 0);
+               if (n == NULL)
+                       goto list_error;
+       }
+
+       return n;
+
+list_error:
+       return NULL;
+}
+
 /**
  * kmod_module_get_info:
  * @mod: kmod module
@@ -2177,27 +2329,36 @@ static struct kmod_list *kmod_module_info_append(struct kmod_list **list, const
  *
  * After use, free the @list by calling kmod_module_info_free_list().
  *
- * Returns: 0 on success or < 0 otherwise.
+ * Returns: number of entries in @list on success or < 0 otherwise.
  */
 KMOD_EXPORT int kmod_module_get_info(const struct kmod_module *mod, struct kmod_list **list)
 {
        struct kmod_elf *elf;
        char **strings;
        int i, count, ret = -ENOMEM;
-       struct kmod_signature_info sig_info;
+       struct kmod_signature_info sig_info = {};
 
        if (mod == NULL || list == NULL)
                return -ENOENT;
 
        assert(*list == NULL);
 
-       elf = kmod_module_get_elf(mod);
-       if (elf == NULL)
-               return -errno;
+       /* remove const: this can only change internal state */
+       if (kmod_module_is_builtin((struct kmod_module *)mod)) {
+               count = kmod_builtin_get_modinfo(mod->ctx,
+                                               kmod_module_get_name(mod),
+                                               &strings);
+               if (count < 0)
+                       return count;
+       } else {
+               elf = kmod_module_get_elf(mod);
+               if (elf == NULL)
+                       return -errno;
 
-       count = kmod_elf_get_strings(elf, ".modinfo", &strings);
-       if (count < 0)
-               return count;
+               count = kmod_elf_get_strings(elf, ".modinfo", &strings);
+               if (count < 0)
+                       return count;
+       }
 
        for (i = 0; i < count; i++) {
                struct kmod_list *n;
@@ -2221,9 +2382,14 @@ KMOD_EXPORT int kmod_module_get_info(const struct kmod_module *mod, struct kmod_
                        goto list_error;
        }
 
-       if (kmod_module_signature_info(mod->file, &sig_info)) {
+       if (mod->file && kmod_module_signature_info(mod->file, &sig_info)) {
                struct kmod_list *n;
-               char *key_hex;
+
+               n = kmod_module_info_append(list, "sig_id", strlen("sig_id"),
+                               sig_info.id_type, strlen(sig_info.id_type));
+               if (n == NULL)
+                       goto list_error;
+               count++;
 
                n = kmod_module_info_append(list, "signer", strlen("signer"),
                                sig_info.signer, sig_info.signer_len);
@@ -2231,19 +2397,10 @@ KMOD_EXPORT int kmod_module_get_info(const struct kmod_module *mod, struct kmod_
                        goto list_error;
                count++;
 
-               /* Display the key id as 01:12:DE:AD:BE:EF:... */
-               key_hex = malloc(sig_info.key_id_len * 3);
-               if (key_hex == NULL)
-                       goto list_error;
-               for (i = 0; i < (int)sig_info.key_id_len; i++) {
-                       sprintf(key_hex + i * 3, "%02X",
-                                       (unsigned char)sig_info.key_id[i]);
-                       if (i < (int)sig_info.key_id_len - 1)
-                               key_hex[i * 3 + 2] = ':';
-               }
-               n = kmod_module_info_append(list, "sig_key", strlen("sig_key"),
-                               key_hex, sig_info.key_id_len * 3 - 1);
-               free(key_hex);
+
+               n = kmod_module_info_append_hex(list, "sig_key", strlen("sig_key"),
+                                               sig_info.key_id,
+                                               sig_info.key_id_len);
                if (n == NULL)
                        goto list_error;
                count++;
@@ -2256,13 +2413,25 @@ KMOD_EXPORT int kmod_module_get_info(const struct kmod_module *mod, struct kmod_
                count++;
 
                /*
-                * Omit sig_info.id_type and sig_info.algo for now, as these
+                * Omit sig_info.algo for now, as these
                 * are currently constant.
                 */
+               n = kmod_module_info_append_hex(list, "signature",
+                                               strlen("signature"),
+                                               sig_info.sig,
+                                               sig_info.sig_len);
+
+               if (n == NULL)
+                       goto list_error;
+               count++;
+
        }
        ret = count;
 
 list_error:
+       /* aux structures freed in normal case also */
+       kmod_module_signature_info_free(&sig_info);
+
        if (ret < 0) {
                kmod_module_info_free_list(*list);
                *list = NULL;
@@ -2425,7 +2594,7 @@ KMOD_EXPORT const char *kmod_module_version_get_symbol(const struct kmod_list *e
 {
        struct kmod_module_version *version;
 
-       if (entry == NULL)
+       if (entry == NULL || entry->data == NULL)
                return NULL;
 
        version = entry->data;
@@ -2438,14 +2607,13 @@ KMOD_EXPORT const char *kmod_module_version_get_symbol(const struct kmod_list *e
  *
  * Get the crc of a kmod module version.
  *
- * Returns: the crc of this kmod module version on success or NULL on
- * failure. The string is owned by the version, do not free it.
+ * Returns: the crc of this kmod module version if available, otherwise default to 0.
  */
 KMOD_EXPORT uint64_t kmod_module_version_get_crc(const struct kmod_list *entry)
 {
        struct kmod_module_version *version;
 
-       if (entry == NULL)
+       if (entry == NULL || entry->data == NULL)
                return 0;
 
        version = entry->data;
@@ -2566,7 +2734,7 @@ KMOD_EXPORT const char *kmod_module_symbol_get_symbol(const struct kmod_list *en
 {
        struct kmod_module_symbol *symbol;
 
-       if (entry == NULL)
+       if (entry == NULL || entry->data == NULL)
                return NULL;
 
        symbol = entry->data;
@@ -2579,14 +2747,13 @@ KMOD_EXPORT const char *kmod_module_symbol_get_symbol(const struct kmod_list *en
  *
  * Get the crc of a kmod module symbol.
  *
- * Returns: the crc of this kmod module symbol on success or NULL on
- * failure. The string is owned by the symbol, do not free it.
+ * Returns: the crc of this kmod module symbol if available, otherwise default to 0.
  */
 KMOD_EXPORT uint64_t kmod_module_symbol_get_crc(const struct kmod_list *entry)
 {
        struct kmod_module_symbol *symbol;
 
-       if (entry == NULL)
+       if (entry == NULL || entry->data == NULL)
                return 0;
 
        symbol = entry->data;
@@ -2712,7 +2879,7 @@ KMOD_EXPORT const char *kmod_module_dependency_symbol_get_symbol(const struct km
 {
        struct kmod_module_dependency_symbol *dependency_symbol;
 
-       if (entry == NULL)
+       if (entry == NULL || entry->data == NULL)
                return NULL;
 
        dependency_symbol = entry->data;
@@ -2725,14 +2892,13 @@ KMOD_EXPORT const char *kmod_module_dependency_symbol_get_symbol(const struct km
  *
  * Get the crc of a kmod module dependency_symbol.
  *
- * Returns: the crc of this kmod module dependency_symbol on success or NULL on
- * failure. The string is owned by the dependency_symbol, do not free it.
+ * Returns: the crc of this kmod module dependency_symbol if available, otherwise default to 0.
  */
 KMOD_EXPORT uint64_t kmod_module_dependency_symbol_get_crc(const struct kmod_list *entry)
 {
        struct kmod_module_dependency_symbol *dependency_symbol;
 
-       if (entry == NULL)
+       if (entry == NULL || entry->data == NULL)
                return 0;
 
        dependency_symbol = entry->data;
@@ -2752,7 +2918,7 @@ KMOD_EXPORT int kmod_module_dependency_symbol_get_bind(const struct kmod_list *e
 {
        struct kmod_module_dependency_symbol *dependency_symbol;
 
-       if (entry == NULL)
+       if (entry == NULL || entry->data == NULL)
                return 0;
 
        dependency_symbol = entry->data;
@@ -2772,3 +2938,46 @@ KMOD_EXPORT void kmod_module_dependency_symbols_free_list(struct kmod_list *list
                list = kmod_list_remove(list);
        }
 }
+
+/**
+ * kmod_module_get_builtin:
+ * @ctx: kmod library context
+ * @list: where to save the builtin module list
+ *
+ * Returns: 0 on success or < 0 otherwise.
+ */
+int kmod_module_get_builtin(struct kmod_ctx *ctx, struct kmod_list **list)
+{
+       struct kmod_builtin_iter *iter;
+       int err = 0;
+
+       iter = kmod_builtin_iter_new(ctx);
+       if (!iter)
+               return -errno;
+
+       while (kmod_builtin_iter_next(iter)) {
+               struct kmod_module *mod = NULL;
+               char modname[PATH_MAX];
+
+               if (!kmod_builtin_iter_get_modname(iter, modname)) {
+                       err = -errno;
+                       goto fail;
+               }
+
+               err = kmod_module_new_from_name(ctx, modname, &mod);
+               if (err < 0)
+                       goto fail;
+
+               kmod_module_set_builtin(mod, true);
+
+               *list = kmod_list_append(*list, mod);
+       }
+
+       kmod_builtin_iter_free(iter);
+       return err;
+fail:
+       kmod_builtin_iter_free(iter);
+       kmod_module_unref_list(*list);
+       *list = NULL;
+       return err;
+}