elf: add get_symbols()

Similar to module-init-tools load_symbols(), it will try .symtab and
.strtab for symbols starting with __crc_, if they are found their crc
is read from ELF's Elf_Sym::st_value.

If not found, then it will fallback to __ksymtab_strings.
diff --git a/libkmod/libkmod-elf.c b/libkmod/libkmod-elf.c
index eeb17c6..b4d6882 100644
--- a/libkmod/libkmod-elf.c
+++ b/libkmod/libkmod-elf.c
@@ -627,3 +627,195 @@
 	ELFDBG(elf, "no vermagic found in .modinfo\n");
 	return -ENOENT;
 }
+
+
+static int kmod_elf_get_symbols_symtab(const struct kmod_elf *elf, struct kmod_modversion **array)
+{
+	uint64_t i, last, size;
+	const void *buf;
+	const char *strings;
+	char *itr;
+	struct kmod_modversion *a;
+	int count, err;
+
+	*array = NULL;
+
+	err = kmod_elf_get_section(elf, "__ksymtab_strings", &buf, &size);
+	if (err < 0)
+		return err;
+	strings = buf;
+	if (strings == NULL || size == 0)
+		return 0;
+
+	/* skip zero padding */
+	while (strings[0] == '\0' && size > 1) {
+		strings++;
+		size--;
+	}
+	if (size <= 1)
+		return 0;
+
+	last = 0;
+	for (i = 0, count = 0; i < size; i++) {
+		if (strings[i] == '\0') {
+			if (last == i) {
+				last = i + 1;
+				continue;
+			}
+			count++;
+			last = i + 1;
+		}
+	}
+	if (strings[i - 1] != '\0')
+		count++;
+
+	*array = a = malloc(size + 1 + sizeof(struct kmod_modversion) * count);
+	if (*array == NULL)
+		return -errno;
+
+	itr = (char *)(a + count);
+	last = 0;
+	for (i = 0, count = 0; i < size; i++) {
+		if (strings[i] == '\0') {
+			size_t slen = i - last;
+			if (last == i) {
+				last = i + 1;
+				continue;
+			}
+			a[count].crc = 0;
+			a[count].symbol = itr;
+			memcpy(itr, strings + last, slen);
+			itr[slen] = '\0';
+			itr += slen + 1;
+			count++;
+			last = i + 1;
+		}
+	}
+	if (strings[i - 1] != '\0') {
+		size_t slen = i - last;
+		a[count].crc = 0;
+		a[count].symbol = itr;
+		memcpy(itr, strings + last, slen);
+		itr[slen] = '\0';
+		itr += slen + 1;
+		count++;
+	}
+
+	return count;
+}
+
+/* array will be allocated with strings in a single malloc, just free *array */
+int kmod_elf_get_symbols(const struct kmod_elf *elf, struct kmod_modversion **array)
+{
+	static const char crc_str[] = "__crc_";
+	static const size_t crc_strlen = sizeof(crc_str) - 1;
+	uint64_t strtablen, symtablen, str_off, sym_off;
+	const void *strtab, *symtab;
+	struct kmod_modversion *a;
+	char *itr;
+	size_t slen, symlen;
+	int i, count, symcount, err;
+
+	err = kmod_elf_get_section(elf, ".strtab", &strtab, &strtablen);
+	if (err < 0) {
+		ELFDBG(elf, "no .strtab found.\n");
+		goto fallback;
+	}
+
+	err = kmod_elf_get_section(elf, ".symtab", &symtab, &symtablen);
+	if (err < 0) {
+		ELFDBG(elf, "no .symtab found.\n");
+		goto fallback;
+	}
+
+	if (elf->class & KMOD_ELF_32)
+		symlen = sizeof(Elf32_Sym);
+	else
+		symlen = sizeof(Elf64_Sym);
+
+	if (symtablen % symlen != 0) {
+		ELFDBG(elf, "unexpected .symtab of length %"PRIu64", not multiple of %"PRIu64" as expected.\n", symtablen, symlen);
+		goto fallback;
+	}
+
+	symcount = symtablen / symlen;
+	count = 0;
+	slen = 0;
+	str_off = (const uint8_t *)strtab - elf->memory;
+	sym_off = (const uint8_t *)symtab - elf->memory + symlen;
+	for (i = 1; i < symcount; i++, sym_off += symlen) {
+		const char *name;
+		uint32_t name_off;
+
+#define READV(field)							\
+		elf_get_uint(elf, sym_off + offsetof(typeof(*s), field),\
+			     sizeof(s->field))
+		if (elf->class & KMOD_ELF_32) {
+			Elf32_Sym *s;
+			name_off = READV(st_name);
+		} else {
+			Elf64_Sym *s;
+			name_off = READV(st_name);
+		}
+#undef READV
+		if (name_off >= strtablen) {
+			ELFDBG(elf, ".strtab is %"PRIu64" bytes, but .symtab entry %d wants to access offset %"PRIu32".\n", strtablen, i, name_off);
+			goto fallback;
+		}
+
+		name = elf_get_mem(elf, str_off + name_off);
+
+		if (strncmp(name, crc_str, crc_strlen) != 0)
+			continue;
+		slen += strlen(name + crc_strlen) + 1;
+		count++;
+	}
+
+	if (count == 0)
+		goto fallback;
+
+	*array = a = malloc(sizeof(struct kmod_modversion) * count + slen);
+	if (*array == NULL)
+		return -errno;
+
+	itr = (char *)(a + count);
+	count = 0;
+	str_off = (const uint8_t *)strtab - elf->memory;
+	sym_off = (const uint8_t *)symtab - elf->memory + symlen;
+	for (i = 1; i < symcount; i++, sym_off += symlen) {
+		const char *name;
+		uint32_t name_off;
+		uint64_t crc;
+
+#define READV(field)							\
+		elf_get_uint(elf, sym_off + offsetof(typeof(*s), field),\
+			     sizeof(s->field))
+		if (elf->class & KMOD_ELF_32) {
+			Elf32_Sym *s;
+			name_off = READV(st_name);
+			crc = READV(st_value);
+		} else {
+			Elf64_Sym *s;
+			name_off = READV(st_name);
+			crc = READV(st_value);
+		}
+#undef READV
+		name = elf_get_mem(elf, str_off + name_off);
+		if (strncmp(name, crc_str, crc_strlen) != 0)
+			continue;
+		name += crc_strlen;
+
+		a[count].crc = crc;
+		a[count].symbol = itr;
+		slen = strlen(name);
+		memcpy(itr, name, slen);
+		itr[slen] = '\0';
+		itr += slen + 1;
+		count++;
+	}
+	return count;
+
+fallback:
+	ELFDBG(elf, "Falling back to __ksymtab_strings!\n");
+	return kmod_elf_get_symbols_symtab(elf, array);
+}
diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c
index 58787f6..089c61e 100644
--- a/libkmod/libkmod-module.c
+++ b/libkmod/libkmod-module.c
@@ -1896,3 +1896,168 @@
 		list = kmod_list_remove(list);
 	}
 }
+
+struct kmod_module_symbol {
+	uint64_t crc;
+	char symbol[];
+};
+
+static struct kmod_module_symbol *kmod_module_symbols_new(uint64_t crc, const char *symbol)
+{
+	struct kmod_module_symbol *mv;
+	size_t symbollen = strlen(symbol) + 1;
+
+	mv = malloc(sizeof(struct kmod_module_symbol) + symbollen);
+	if (mv == NULL)
+		return NULL;
+
+	mv->crc = crc;
+	memcpy(mv->symbol, symbol, symbollen);
+	return mv;
+}
+
+static void kmod_module_symbol_free(struct kmod_module_symbol *symbol)
+{
+	free(symbol);
+}
+
+/**
+ * kmod_module_get_symbols:
+ * @mod: kmod module
+ * @list: where to return list of module symbols. Use
+ *        kmod_module_symbols_get_symbol() and
+ *        kmod_module_symbols_get_crc(). Release this list with
+ *        kmod_module_symbols_unref_list()
+ *
+ * Get a list of entries in ELF section ".symtab" or "__ksymtab_strings".
+ *
+ * After use, free the @list by calling kmod_module_symbols_free_list().
+ *
+ * Returns: 0 on success or < 0 otherwise.
+ */
+KMOD_EXPORT int kmod_module_get_symbols(const struct kmod_module *mod, struct kmod_list **list)
+{
+	struct kmod_file *file;
+	struct kmod_elf *elf;
+	const char *path;
+	const void *mem;
+	struct kmod_modversion *symbols;
+	size_t size;
+	int i, count, ret = 0;
+
+	if (mod == NULL || list == NULL)
+		return -ENOENT;
+
+	assert(*list == NULL);
+
+	path = kmod_module_get_path(mod);
+	if (path == NULL)
+		return -ENOENT;
+
+	file = kmod_file_open(path);
+	if (file == NULL)
+		return -errno;
+
+	size = kmod_file_get_size(file);
+	mem = kmod_file_get_contents(file);
+
+	elf = kmod_elf_new(mem, size);
+	if (elf == NULL) {
+		ret = -errno;
+		goto elf_open_error;
+	}
+
+	count = kmod_elf_get_symbols(elf, &symbols);
+	if (count < 0) {
+		ret = count;
+		goto get_strings_error;
+	}
+
+	for (i = 0; i < count; i++) {
+		struct kmod_module_symbol *mv;
+		struct kmod_list *n;
+
+		mv = kmod_module_symbols_new(symbols[i].crc, symbols[i].symbol);
+		if (mv == NULL) {
+			ret = -errno;
+			kmod_module_symbols_free_list(*list);
+			*list = NULL;
+			goto list_error;
+		}
+
+		n = kmod_list_append(*list, mv);
+		if (n != NULL)
+			*list = n;
+		else {
+			kmod_module_symbol_free(mv);
+			kmod_module_symbols_free_list(*list);
+			*list = NULL;
+			ret = -ENOMEM;
+			goto list_error;
+		}
+	}
+	ret = count;
+
+list_error:
+	free(symbols);
+get_strings_error:
+	kmod_elf_unref(elf);
+elf_open_error:
+	kmod_file_unref(file);
+
+	return ret;
+}
+
+/**
+ * kmod_module_symbols_get_symbol:
+ * @entry: a list entry representing a kmod module symbols
+ *
+ * Get the symbol of a kmod module symbols.
+ *
+ * Returns: the symbol of this kmod module symbols on success or NULL
+ * on failure. The string is owned by the symbols, do not free it.
+ */
+KMOD_EXPORT const char *kmod_module_symbol_get_symbol(const struct kmod_list *entry)
+{
+	struct kmod_module_symbol *symbol;
+
+	if (entry == NULL)
+		return NULL;
+
+	symbol = entry->data;
+	return symbol->symbol;
+}
+
+/**
+ * kmod_module_symbol_get_crc:
+ * @entry: a list entry representing a kmod module symbol
+ *
+ * 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.
+ */
+KMOD_EXPORT uint64_t kmod_module_symbol_get_crc(const struct kmod_list *entry)
+{
+	struct kmod_module_symbol *symbol;
+
+	if (entry == NULL)
+		return 0;
+
+	symbol = entry->data;
+	return symbol->crc;
+}
+
+/**
+ * kmod_module_symbols_free_list:
+ * @list: kmod module symbols list
+ *
+ * Release the resources taken by @list
+ */
+KMOD_EXPORT void kmod_module_symbols_free_list(struct kmod_list *list)
+{
+	while (list) {
+		kmod_module_symbol_free(list->data);
+		list = kmod_list_remove(list);
+	}
+}
diff --git a/libkmod/libkmod-private.h b/libkmod/libkmod-private.h
index 4b0d84f..d32b6d7 100644
--- a/libkmod/libkmod-private.h
+++ b/libkmod/libkmod-private.h
@@ -152,6 +152,7 @@
 const void *kmod_elf_get_memory(const struct kmod_elf *elf) __must_check __attribute__((nonnull(1)));
 int kmod_elf_get_strings(const struct kmod_elf *elf, const char *section, char ***array) __must_check __attribute__((nonnull(1,2,3)));
 int kmod_elf_get_modversions(const struct kmod_elf *elf, struct kmod_modversion **array) __must_check __attribute__((nonnull(1,2)));
+int kmod_elf_get_symbols(const struct kmod_elf *elf, struct kmod_modversion **array) __must_check __attribute__((nonnull(1,2)));
 int kmod_elf_strip_section(struct kmod_elf *elf, const char *section) __must_check __attribute__((nonnull(1,2)));
 int kmod_elf_strip_vermagic(struct kmod_elf *elf) __must_check __attribute__((nonnull(1)));
 
diff --git a/libkmod/libkmod.h b/libkmod/libkmod.h
index 38b81cf..cc3de08 100644
--- a/libkmod/libkmod.h
+++ b/libkmod/libkmod.h
@@ -150,6 +150,11 @@
 uint64_t kmod_module_version_get_crc(const struct kmod_list *entry);
 void kmod_module_versions_free_list(struct kmod_list *list);
 
+int kmod_module_get_symbols(const struct kmod_module *mod, struct kmod_list **list);
+const char *kmod_module_symbol_get_symbol(const struct kmod_list *entry);
+uint64_t kmod_module_symbol_get_crc(const struct kmod_list *entry);
+void kmod_module_symbols_free_list(struct kmod_list *list);
+
 #ifdef __cplusplus
 } /* extern "C" */
 #endif
diff --git a/libkmod/libkmod.sym b/libkmod/libkmod.sym
index c61ad7e..bf93729 100644
--- a/libkmod/libkmod.sym
+++ b/libkmod/libkmod.sym
@@ -62,4 +62,11 @@
 	kmod_module_version_get_symbol;
 	kmod_module_version_get_crc;
 	kmod_module_versions_free_list;
-} LIBKMOD_1;
+
+	kmod_module_get_symbols;
+	kmod_module_symbol_get_symbol;
+	kmod_module_symbol_get_crc;
+	kmod_module_symbols_free_list;
+local:
+        *;
+};
diff --git a/test/test-lookup.c b/test/test-lookup.c
index 620237e..70cdb5c 100644
--- a/test/test-lookup.c
+++ b/test/test-lookup.c
@@ -160,6 +160,20 @@
 			kmod_module_versions_free_list(pre);
 		}
 
+		pre = NULL;
+		err = kmod_module_get_symbols(mod, &pre);
+		if (err > 0) {
+			puts("\t\tsymbols:");
+			kmod_list_foreach(d, pre) {
+				const char *symbol;
+				uint64_t crc;
+				symbol = kmod_module_symbol_get_symbol(d);
+				crc = kmod_module_symbol_get_crc(d);
+				printf("\t\t\t%s: %#"PRIx64"\n", symbol, crc);
+			}
+			kmod_module_symbols_free_list(pre);
+		}
+
 		kmod_module_unref(mod);
 	}