kmod_config: optimize config files handling

 1) Allocate less by not sorting the result with qsort. Instead,
    insert the nodes in the correct order;
 2) Do not maintain the whole path in memory, but rely on openat()
diff --git a/libkmod/libkmod-config.c b/libkmod/libkmod-config.c
index 75fdbf1..2859dd4 100644
--- a/libkmod/libkmod-config.c
+++ b/libkmod/libkmod-config.c
@@ -124,18 +124,26 @@
 	config->blacklists = kmod_list_remove(l);
 }
 
-static int kmod_config_parse(struct kmod_config *config, const char *filename)
+/*
+ * Take an fd and own it. It will be closed on return. filename is used only
+ * for debug messages
+ */
+static int kmod_config_parse(struct kmod_config *config, int fd,
+							const char *filename)
 {
 	struct kmod_ctx *ctx = config->ctx;
 	char *line;
 	FILE *fp;
 	unsigned int linenum;
+	int err;
 
-	DBG(ctx, "%s\n", filename);
-
-	fp = fopen(filename, "r");
-	if (fp == NULL)
-		return errno;
+	fp = fdopen(fd, "r");
+	if (fp == NULL) {
+		err = -errno;
+		ERR(config->ctx, "fd %d: %m", fd);
+		close(fd);
+		return err;
+	}
 
 	while ((line = getline_wrapped(fp, &linenum)) != NULL) {
 		char *cmd, *saveptr;
@@ -198,8 +206,8 @@
 	free(config);
 }
 
-static bool conf_files_filter(struct kmod_ctx *ctx, const char *path,
-							const char *fn)
+static bool conf_files_filter_out(struct kmod_ctx *ctx, const char *path,
+								const char *fn)
 {
 	size_t len = strlen(fn);
 
@@ -217,78 +225,84 @@
 	return 0;
 }
 
-static int conf_files_list(struct kmod_ctx *ctx, struct kmod_list **list,
-						const char *path, size_t *n)
+static DIR *conf_files_list(struct kmod_ctx *ctx, struct kmod_list **list,
+							const char *path)
 {
 	struct stat st;
 	DIR *d;
 	int err;
 
 	if (stat(path, &st) < 0)
-		return -ENOENT;
+		return NULL;
 
 	if (!S_ISDIR(st.st_mode)) {
-		*list = kmod_list_append(*list, (void *)path);
-		*n += 1;
-		return 0;
+		*list = kmod_list_append(*list, path);
+		return NULL;
 	}
 
 	d = opendir(path);
 	if (d == NULL) {
 		err = errno;
 		ERR(ctx, "%m\n");
-		return -errno;
+		return NULL;
 	}
 
 	for (;;) {
 		struct dirent ent, *entp;
-		char *p;
+		struct kmod_list *l, *tmp;
+		const char *dname;
 
 		err = readdir_r(d, &ent, &entp);
 		if (err != 0) {
-			err = -err;
-			goto finish;
+			ERR(ctx, "reading entry %s\n", strerror(-err));
+			goto fail_read;
 		}
 
 		if (entp == NULL)
 			break;
 
-		if (conf_files_filter(ctx, path, entp->d_name) == 1)
+		if (conf_files_filter_out(ctx, path, entp->d_name) == 1)
 			continue;
 
-		if (asprintf(&p, "%s/%s", path, entp->d_name) < 0) {
-			err = -ENOMEM;
-			goto finish;
+		/* insert sorted */
+		kmod_list_foreach(l, *list) {
+			if (strcmp(entp->d_name, l->data) < 0)
+				break;
 		}
 
-		DBG(ctx, "%s\n", p);
+		dname = strdup(entp->d_name);
+		if (dname == NULL)
+			goto fail_oom;
 
-		*list = kmod_list_append(*list, p);
-		*n += 1;
+		if (l == NULL)
+			tmp = kmod_list_append(*list, dname);
+		else if (l == *list)
+			tmp = kmod_list_prepend(*list, dname);
+		else
+			tmp = kmod_list_insert_before(l, dname);
+
+		if (tmp == NULL)
+			goto fail_oom;
+
+		if (l == NULL || l == *list)
+			*list = tmp;
 	}
 
-finish:
+	return d;
+
+fail_oom:
+	ERR(ctx, "out of memory while scanning '%s'\n", path);
+fail_read:
+	for (; *list != NULL; *list = kmod_list_remove(*list))
+		free((*list)->data);
 	closedir(d);
-	return err;
-}
-
-static int base_cmp(const void *a, const void *b)
-{
-        const char *s1, *s2;
-
-        s1 = *(char * const *)a;
-        s2 = *(char * const *)b;
-
-	return strcmp(basename(s1), basename(s2));
+	return NULL;
 }
 
 int kmod_config_new(struct kmod_ctx *ctx, struct kmod_config **p_config)
 {
 	struct kmod_config *config;
-	size_t i, n = 0;
-	const char **files;
-	int err = 0;
-	struct kmod_list *list = NULL, *l;
+	size_t i;
 
 	*p_config = config = calloc(1, sizeof(struct kmod_config));
 	if (config == NULL)
@@ -296,33 +310,41 @@
 
 	config->ctx = ctx;
 
-	for (i = 0; i < ARRAY_SIZE(config_files); i++)
-		conf_files_list(ctx, &list, config_files[i], &n);
+	for (i = 0; i < ARRAY_SIZE(config_files); i++) {
+		struct kmod_list *list = NULL;
+		DIR *d;
+		int fd;
 
-	files = malloc(sizeof(char *) * n);
-	if (files == NULL) {
-		err = -ENOMEM;
-		goto finish;
+		d = conf_files_list(ctx, &list, config_files[i]);
+
+		/* there's no entry */
+		if (list == NULL)
+			continue;
+
+		/* there's only one entry, and it's a file */
+		if (d == NULL) {
+			DBG(ctx, "parsing file '%s'\n", config_files[i]);
+			list = kmod_list_remove(list);
+			fd = open(config_files[i], O_RDONLY);
+			if (fd >= 0)
+				kmod_config_parse(config, fd, config_files[i]);
+
+			continue;
+		}
+
+		/* treat all the entries in that dir */
+		for (; list != NULL; list = kmod_list_remove(list)) {
+			DBG(ctx, "parsing file '%s/%s'\n", config_files[i],
+							(char *) list->data);
+			fd = openat(dirfd(d), list->data, O_RDONLY);
+			if (fd >= 0)
+				kmod_config_parse(config, fd, list->data);
+
+			free(list->data);
+		}
+
+		closedir(d);
 	}
 
-	i = 0;
-	kmod_list_foreach(l, list) {
-		files[i] = l->data;
-		i++;
-	}
-
-	qsort(files, n, sizeof(char *), base_cmp);
-
-	for (i = 0; i < n; i++)
-		kmod_config_parse(config, files[i]);
-
-finish:
-	free(files);
-
-	while (list) {
-		free(list->data);
-		list = kmod_list_remove(list);
-	}
-
-	return err;
+	return 0;
 }