kmod_module: extended information gathering.

provide means to get:
 * refcount
 * initstate
 * holders
 * sections

this can be used to individually query properties from modules,
similar to /proc/modules (kmod_loaded / kmod_loaded_module).
diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c
index 8bc644f..10c8b16 100644
--- a/libkmod/libkmod-module.c
+++ b/libkmod/libkmod-module.c
@@ -27,6 +27,8 @@
 #include <string.h>
 #include <ctype.h>
 #include <inttypes.h>
+#include <limits.h>
+#include <dirent.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/mman.h>
@@ -359,3 +361,239 @@
 
 	return err;
 }
+
+KMOD_EXPORT const char *kmod_module_initstate_str(enum kmod_module_initstate state)
+{
+    switch (state) {
+    case KMOD_MODULE_BUILTIN:
+	return "builtin";
+    case KMOD_MODULE_LIVE:
+	return "live";
+    case KMOD_MODULE_COMING:
+	return "coming";
+    case KMOD_MODULE_GOING:
+	return "going";
+    default:
+	return NULL;
+    }
+}
+
+KMOD_EXPORT int kmod_module_get_initstate(const struct kmod_module *mod)
+{
+	char path[PATH_MAX], buf[32];
+	int fd, err, pathlen;
+
+	pathlen = snprintf(path, sizeof(path),
+				"/sys/module/%s/initstate", mod->name);
+	fd = open(path, O_RDONLY);
+	if (fd < 0) {
+		err = -errno;
+
+		if (pathlen > (int)sizeof("/initstate") - 1) {
+			struct stat st;
+			path[pathlen - (sizeof("/initstate") - 1)] = '\0';
+			if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
+				return KMOD_MODULE_BUILTIN;
+		}
+
+		ERR(mod->ctx, "could not open '%s': %s\n",
+			path, strerror(-err));
+		return err;
+	}
+
+	err = read_str_safe(fd, buf, sizeof(buf));
+	close(fd);
+	if (err < 0) {
+		ERR(mod->ctx, "could not read from '%s': %s\n",
+			path, strerror(-err));
+		return err;
+	}
+
+	if (strcmp(buf, "live\n") == 0)
+		return KMOD_MODULE_LIVE;
+	else if (strcmp(buf, "coming\n") == 0)
+		return KMOD_MODULE_COMING;
+	else if (strcmp(buf, "going\n") == 0)
+		return KMOD_MODULE_GOING;
+
+	ERR(mod->ctx, "unknown %s: '%s'\n", path, buf);
+	return -EINVAL;
+}
+
+KMOD_EXPORT int kmod_module_get_refcnt(const struct kmod_module *mod)
+{
+	char path[PATH_MAX];
+	long refcnt;
+	int fd, err;
+
+	snprintf(path, sizeof(path), "/sys/module/%s/refcnt", mod->name);
+	fd = open(path, O_RDONLY);
+	if (fd < 0) {
+		err = -errno;
+		ERR(mod->ctx, "could not open '%s': %s\n",
+			path, strerror(errno));
+		return err;
+	}
+
+	err = read_str_long(fd, &refcnt, 10);
+	close(fd);
+	if (err < 0) {
+		ERR(mod->ctx, "could not read integer from '%s': '%s'\n",
+			path, strerror(-err));
+		return err;
+	}
+
+	return (int)refcnt;
+}
+
+KMOD_EXPORT struct kmod_list *kmod_module_get_holders(const struct kmod_module *mod)
+{
+	char dname[PATH_MAX];
+	struct kmod_list *list = NULL;
+	DIR *d;
+	struct dirent *de;
+
+	if (mod == NULL)
+		return NULL;
+	snprintf(dname, sizeof(dname), "/sys/module/%s/holders", mod->name);
+
+	d = opendir(dname);
+	if (d == NULL) {
+		ERR(mod->ctx, "could not open '%s': %s\n",
+			dname, strerror(errno));
+		return NULL;
+	}
+
+	while ((de = readdir(d)) != NULL) {
+		struct kmod_list *node;
+		struct kmod_module *holder;
+		int err;
+
+		if (de->d_name[0] == '.') {
+			if (de->d_name[1] == '\0' ||
+			    (de->d_name[1] == '.' && de->d_name[2] == '\0'))
+				continue;
+		}
+
+		err = kmod_module_new_from_name(mod->ctx, de->d_name, &holder);
+		if (err < 0) {
+			ERR(mod->ctx, "could not create module for '%s': %s\n",
+				de->d_name, strerror(-err));
+			continue;
+		}
+
+		node = kmod_list_append(list, holder);
+		if (node)
+			list = node;
+		else {
+			ERR(mod->ctx, "out of memory\n");
+			kmod_module_unref(holder);
+		}
+	}
+
+	closedir(d);
+	return list;
+}
+
+struct kmod_module_section {
+	unsigned long address;
+	char name[];
+};
+
+static void kmod_module_section_free(struct kmod_module_section *section)
+{
+	free(section);
+}
+
+KMOD_EXPORT struct kmod_list *kmod_module_get_sections(const struct kmod_module *mod)
+{
+	char dname[PATH_MAX];
+	struct kmod_list *list = NULL;
+	DIR *d;
+	int dfd;
+	struct dirent *de;
+
+	if (mod == NULL)
+		return NULL;
+	snprintf(dname, sizeof(dname), "/sys/module/%s/sections", mod->name);
+
+	d = opendir(dname);
+	if (d == NULL) {
+		ERR(mod->ctx, "could not open '%s': %s\n",
+			dname, strerror(errno));
+		return NULL;
+	}
+
+	dfd = dirfd(d);
+	while ((de = readdir(d)) != NULL) {
+		struct kmod_module_section *section;
+		struct kmod_list *node;
+		unsigned long address;
+		size_t namesz;
+		int fd, err;
+
+		if (de->d_name[0] == '.') {
+			if (de->d_name[1] == '\0' ||
+			    (de->d_name[1] == '.' && de->d_name[2] == '\0'))
+				continue;
+		}
+
+		fd = openat(dfd, de->d_name, O_RDONLY);
+		if (fd < 0) {
+			ERR(mod->ctx, "could not open '%s/%s': %s\n",
+				dname, de->d_name, strerror(errno));
+			continue;
+		}
+
+		err = read_str_ulong(fd, &address, 16);
+		if (err < 0) {
+			ERR(mod->ctx, "could not read long from '%s/%s': %s\n",
+				dname, de->d_name, strerror(-err));
+			close(fd);
+			continue;
+		}
+
+		namesz = strlen(de->d_name) + 1;
+		section = malloc(sizeof(struct kmod_module_section) + namesz);
+		section->address = address;
+		memcpy(section->name, de->d_name, namesz);
+
+		node = kmod_list_append(list, section);
+		if (node)
+			list = node;
+		else {
+			ERR(mod->ctx, "out of memory\n");
+			free(section);
+		}
+		close(fd);
+	}
+
+	closedir(d);
+	return list;
+}
+
+KMOD_EXPORT const char *kmod_module_section_get_name(const struct kmod_list *entry)
+{
+	struct kmod_module_section *section;
+	if (entry == NULL)
+		return NULL;
+	section = entry->data;
+	return section->name;
+}
+
+KMOD_EXPORT unsigned long kmod_module_section_get_address(const struct kmod_list *entry)
+{
+	struct kmod_module_section *section;
+	if (entry == NULL)
+		return (unsigned long)-1;
+	section = entry->data;
+	return section->address;
+}
+
+KMOD_EXPORT void kmod_module_section_free_list(struct kmod_list *list)
+{
+	while (list) {
+		kmod_module_section_free(list->data);
+		list = kmod_list_remove(list);
+	}
+}
diff --git a/libkmod/libkmod-private.h b/libkmod/libkmod-private.h
index f00f3f9..660a85c 100644
--- a/libkmod/libkmod-private.h
+++ b/libkmod/libkmod-private.h
@@ -82,4 +82,9 @@
 bool startswith(const char *s, const char *prefix) __attribute__((nonnull(1, 2)));
 void *memdup(const void *p, size_t n) __attribute__((nonnull(1)));
 
+ssize_t read_str_safe(int fd, char *buf, size_t buflen) __must_check __attribute__((nonnull(2)));
+int read_str_long(int fd, long *value, int base) __must_check __attribute__((nonnull(2)));
+int read_str_ulong(int fd, unsigned long *value, int base) __must_check __attribute__((nonnull(2)));
+
+
 #endif
diff --git a/libkmod/libkmod-util.c b/libkmod/libkmod-util.c
index b6e3cfc..e506b52 100644
--- a/libkmod/libkmod-util.c
+++ b/libkmod/libkmod-util.c
@@ -143,3 +143,64 @@
 
 	return memcpy(r, p, n);
 }
+
+ssize_t read_str_safe(int fd, char *buf, size_t buflen) {
+	size_t todo = buflen;
+	size_t done;
+	do {
+		ssize_t r = read(fd, buf, todo);
+		if (r == 0)
+			break;
+		else if (r > 0)
+			todo -= r;
+		else {
+			if (errno == EAGAIN || errno == EWOULDBLOCK ||
+				errno == EINTR)
+				continue;
+			else
+				return -errno;
+		}
+	} while (todo > 0);
+	done = buflen - todo;
+	if (done == 0)
+		buf[0] = '\0';
+	else {
+		if (done < buflen)
+			buf[done] = '\0';
+		else if (buf[done - 1] != '\0')
+			return -ENOSPC;
+	}
+	return done;
+}
+
+int read_str_long(int fd, long *value, int base) {
+	char buf[32], *end;
+	long v;
+	int err;
+	*value = 0;
+	err = read_str_safe(fd, buf, sizeof(buf));
+	if (err < 0)
+		return err;
+	errno = 0;
+	v = strtol(buf, &end, base);
+	if (end == buf || !isspace(*end))
+		return -EINVAL;
+	*value = v;
+	return 0;
+}
+
+int read_str_ulong(int fd, unsigned long *value, int base) {
+	char buf[32], *end;
+	long v;
+	int err;
+	*value = 0;
+	err = read_str_safe(fd, buf, sizeof(buf));
+	if (err < 0)
+		return err;
+	errno = 0;
+	v = strtoul(buf, &end, base);
+	if (end == buf || !isspace(*end))
+		return -EINVAL;
+	*value = v;
+	return 0;
+}
diff --git a/libkmod/libkmod.h b/libkmod/libkmod.h
index fca4107..c552f29 100644
--- a/libkmod/libkmod.h
+++ b/libkmod/libkmod.h
@@ -117,6 +117,23 @@
 const char *kmod_module_get_name(const struct kmod_module *mod);
 const char *kmod_module_get_path(const struct kmod_module *mod);
 
+enum kmod_module_initstate {
+	KMOD_MODULE_BUILTIN = 0,
+	KMOD_MODULE_LIVE,
+	KMOD_MODULE_COMING,
+	KMOD_MODULE_GOING
+};
+const char *kmod_module_initstate_str(enum kmod_module_initstate initstate);
+int kmod_module_get_initstate(const struct kmod_module *mod);
+int kmod_module_get_refcnt(const struct kmod_module *mod);
+struct kmod_list *kmod_module_get_holders(const struct kmod_module *mod);
+
+struct kmod_list *kmod_module_get_sections(const struct kmod_module *mod);
+const char *kmod_module_section_get_name(const struct kmod_list *entry);
+unsigned long kmod_module_section_get_address(const struct kmod_list *entry);
+void kmod_module_section_free_list(struct kmod_list *list);
+
+
 #ifdef __cplusplus
 } /* extern "C" */
 #endif
diff --git a/libkmod/libkmod.sym b/libkmod/libkmod.sym
index fcbe8a9..07690dd 100644
--- a/libkmod/libkmod.sym
+++ b/libkmod/libkmod.sym
@@ -32,6 +32,16 @@
 
 	kmod_module_get_name;
 	kmod_module_get_path;
+
+	kmod_module_initstate_str;
+	kmod_module_get_initstate;
+	kmod_module_get_refcnt;
+	kmod_module_get_sections;
+	kmod_module_section_free_list;
+	kmod_module_section_get_name;
+	kmod_module_section_get_address;
+
+	kmod_module_get_holders;
 local:
         *;
 };