Add libkmod-loaded to handle live modules information

All the functions needed by a lsmod binary are in place.
test/test-loaded.c implements it with the same output of lsmod.
diff --git a/Makefile.am b/Makefile.am
index 57a1da3..a6b81db 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -31,7 +31,8 @@
 	libkmod/libkmod-private.h \
 	libkmod/libkmod-util.h \
 	libkmod/libkmod.c \
-	libkmod/libkmod-list.c
+	libkmod/libkmod-list.c \
+	libkmod/libkmod-loaded.c
 
 EXTRA_DIST += libkmod/libkmod.sym
 
@@ -42,8 +43,11 @@
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = libkmod/libkmod.pc
 
-TESTS = test/test-init
+TESTS = test/test-init test/test-loaded
 
-check_PROGRAMS = test/test-init
+check_PROGRAMS = test/test-init test/test-loaded
 test_test_init_SOURCES = test/test-init.c
 test_test_init_LDADD = libkmod/libkmod.la
+
+test_test_loaded_SOURCES = test/test-loaded.c
+test_test_loaded_LDADD = libkmod/libkmod.la
diff --git a/libkmod/libkmod-loaded.c b/libkmod/libkmod-loaded.c
new file mode 100644
index 0000000..b0bed74
--- /dev/null
+++ b/libkmod/libkmod-loaded.c
@@ -0,0 +1,228 @@
+/*
+ * libkmod - interface to kernel module operations
+ *
+ * Copyright (C) 2011  ProFUSION embedded systems
+ * Copyright (C) 2011  Lucas De Marchi <lucas.de.marchi@gmail.com>
+ *
+ * This library 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 version 2.1.
+ *
+ * This library 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#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 <inttypes.h>
+
+#include "libkmod.h"
+#include "libkmod-private.h"
+
+/**
+ * SECTION:libkmod-loaded
+ * @short_description: currently loaded modules
+ *
+ * Information about currently loaded modules, as reported by Linux kernel
+ */
+
+/**
+ * kmod_loaded:
+ *
+ * Opaque object representing a loaded module.
+ */
+struct kmod_loaded {
+	struct kmod_ctx *ctx;
+	int refcount;
+	struct kmod_list *modules;
+	bool parsed;
+};
+
+struct kmod_loaded_module {
+	char *name;
+	long size;
+	int use_count;
+	char *deps;
+	uintptr_t addr;
+};
+
+KMOD_EXPORT int kmod_loaded_new(struct kmod_ctx *ctx, struct kmod_loaded **mod)
+{
+	struct kmod_loaded *m;
+
+	m = calloc(1, sizeof(*m));
+	if (m == NULL)
+		return -ENOMEM;
+
+	m->refcount = 1;
+	m->ctx = ctx;
+	*mod = m;
+	return 0;
+}
+
+KMOD_EXPORT struct kmod_loaded *kmod_loaded_ref(struct kmod_loaded *mod)
+{
+	if (mod == NULL)
+		return NULL;
+	mod->refcount++;
+	return mod;
+}
+
+static void loaded_modules_free_module(struct kmod_loaded_module *m)
+{
+	free(m->name);
+	free(m->deps);
+	free(m);
+}
+
+static void loaded_modules_free(struct kmod_loaded *mod)
+{
+	while (mod->modules != NULL) {
+		loaded_modules_free_module(mod->modules->data);
+		mod->modules = kmod_list_remove(mod->modules);
+	}
+}
+
+KMOD_EXPORT struct kmod_loaded *kmod_loaded_unref(struct kmod_loaded *mod)
+{
+	if (mod == NULL)
+		return NULL;
+	mod->refcount--;
+	dbg(mod->ctx, "kmod_loaded %p released\n", mod);
+	loaded_modules_free(mod);
+	free(mod);
+	return NULL;
+}
+
+static int loaded_modules_parse(struct kmod_loaded *mod,
+						struct kmod_list **list)
+{
+	struct kmod_list *l = NULL;
+	FILE *fp;
+	char line[4096];
+
+	fp = fopen("/proc/modules", "r");
+	if (fp == NULL)
+		return -errno;
+
+	while (fgets(line, sizeof(line), fp)) {
+		char *tok;
+		struct kmod_loaded_module *m;
+
+		m = calloc(1, sizeof(*m));
+		if (m == NULL)
+			goto err;
+
+		tok = strtok(line, " \t");
+		m->name = strdup(tok);
+
+		tok = strtok(NULL, " \t\n");
+		m->size = atoi(tok);
+
+		/* Null if no module unloading is supported */
+		tok = strtok(NULL, " \t\n");
+		if (tok == NULL)
+			goto done;
+
+		m->use_count = atoi(tok);
+		tok = strtok(NULL, "\n");
+		if (tok == NULL)
+			goto done;
+
+		/* Strip trailing comma */
+		if (strchr(tok, ',')) {
+			char *end;
+			tok = strtok(tok, " \t");
+			end = &tok[strlen(tok) - 1];
+			if (*end == ',')
+				*end = '\0';
+			m->deps = strdup(tok);
+			tok = &end[2];
+		} else if (tok[0] == '-' && tok[1] == '\0')
+			goto done;
+		else if (tok[0] == '-' && isspace(tok[1]))
+			tok = &tok[3];
+
+		tok = strtok(tok, " \t\n");
+		if (tok == NULL)
+			goto done;
+
+		tok = strtok(NULL, " \t\n");
+		if (tok == NULL)
+			goto done;
+
+		sscanf(tok, "%" SCNxPTR, &m->addr);
+
+done:
+		l = kmod_list_append(l, m);
+	}
+
+	fclose(fp);
+	mod->parsed = 1;
+	*list = l;
+
+	return 0;
+
+err:
+	fclose(fp);
+	mod->modules = l;
+	loaded_modules_free(mod);
+	mod->modules = NULL;
+	return -ENOMEM;
+}
+
+KMOD_EXPORT int kmod_loaded_get_list(struct kmod_loaded *mod,
+						struct kmod_list **list)
+{
+	if (mod == NULL)
+		return -ENOENT;
+
+	if (!mod->parsed) {
+		int err = loaded_modules_parse(mod, &mod->modules);
+		if (err < 0)
+			return err;
+	}
+
+	*list = mod->modules;
+
+	return 0;
+}
+
+KMOD_EXPORT int kmod_loaded_get_module_info(struct kmod_list *entry,
+						const char **name,
+						long *size, int *use_count,
+						const char **deps,
+						uintptr_t *addr)
+{
+	struct kmod_loaded_module *m;
+
+	if (entry == NULL)
+		return -ENOENT;
+
+	m = entry->data;
+
+	if (name)
+		*name = m->name;
+	if (size)
+		*size = m->size;
+	if (use_count)
+		*use_count = m->use_count;
+	if (addr)
+		*addr = m->addr;
+	if (deps)
+		*deps = m->deps;
+
+	return 0;
+}
diff --git a/libkmod/libkmod.h b/libkmod/libkmod.h
index 2f3006d..93c5da1 100644
--- a/libkmod/libkmod.h
+++ b/libkmod/libkmod.h
@@ -22,6 +22,7 @@
 #define _LIBABC_H_
 
 #include <stdarg.h>
+#include <inttypes.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -64,4 +65,13 @@
 } /* extern "C" */
 #endif
 
+struct kmod_loaded;
+int kmod_loaded_new(struct kmod_ctx *ctx, struct kmod_loaded **mod);
+struct kmod_loaded *kmod_loaded_ref(struct kmod_loaded *mod);
+struct kmod_loaded *kmod_loaded_unref(struct kmod_loaded *mod);
+int kmod_loaded_get_list(struct kmod_loaded *mod, struct kmod_list **list);
+int kmod_loaded_get_module_info(struct kmod_list *entry, const char **name,
+				long *size, int *use_count, const char **deps,
+				uintptr_t *addr);
+
 #endif
diff --git a/libkmod/libkmod.sym b/libkmod/libkmod.sym
index 3a06d74..0482ada 100644
--- a/libkmod/libkmod.sym
+++ b/libkmod/libkmod.sym
@@ -10,6 +10,11 @@
 	kmod_set_userdata;
 	kmod_unref;
 	kmod_list_next;
+	kmod_loaded_new;
+	kmod_loaded_ref;
+	kmod_loaded_unref;
+	kmod_loaded_get_list;
+	kmod_loaded_get_module_info;
 local:
         *;
 };
diff --git a/test/.gitignore b/test/.gitignore
index 9f66dbd..20b56b7 100644
--- a/test/.gitignore
+++ b/test/.gitignore
@@ -1,2 +1,3 @@
 .dirstamp
 test-init
+test-loaded
diff --git a/test/test-loaded.c b/test/test-loaded.c
new file mode 100644
index 0000000..c81c288
--- /dev/null
+++ b/test/test-loaded.c
@@ -0,0 +1,51 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <errno.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <string.h>
+#include <libkmod.h>
+
+
+int main(int argc, char *argv[])
+{
+	struct kmod_ctx *ctx;
+	struct kmod_loaded *mod;
+	struct kmod_list *list, *itr;
+	int err;
+
+	ctx = kmod_new();
+	if (ctx == NULL)
+		exit(EXIT_FAILURE);
+
+	printf("libkmod version %s\n", VERSION);
+
+	err = kmod_loaded_new(ctx, &mod);
+	if (err < 0)
+		exit(EXIT_FAILURE);
+
+	err = kmod_loaded_get_list(mod, &list);
+	if (err < 0) {
+		fprintf(stderr, "%s\n", strerror(-err));
+		exit(EXIT_FAILURE);
+	}
+
+	printf("Module                  Size  Used by\n");
+
+	kmod_list_foreach(itr, list) {
+		const char *name, *deps;
+		long size;
+		int use_count;
+		kmod_loaded_get_module_info(itr, &name, &size, &use_count,
+								&deps, NULL);
+		printf("%-19s %8ld  %d %s\n", name, size,
+					use_count, deps ? deps : "");
+	}
+
+	kmod_loaded_unref(mod);
+
+	kmod_unref(ctx);
+
+	return EXIT_SUCCESS;
+}