From: Gustavo Sverzut Barbieri Date: Sun, 18 Dec 2011 03:25:06 +0000 (-0200) Subject: ELF: initial support for modinfo and strip of modversions and vermagic. X-Git-Tag: v2~40 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=708624a4ebffc515da2f9e2fdbdce79fc9c0aaaf;p=thirdparty%2Fkmod.git ELF: initial support for modinfo and strip of modversions and vermagic. Needs testing, but should work. --- diff --git a/Makefile.am b/Makefile.am index ca27139d..d5e308a8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -37,7 +37,8 @@ libkmod_libkmod_la_SOURCES =\ libkmod/libkmod-index.c \ libkmod/libkmod-index.h \ libkmod/libkmod-module.c \ - libkmod/libkmod-file.c + libkmod/libkmod-file.c \ + libkmod/libkmod-elf.c EXTRA_DIST += libkmod/libkmod.sym EXTRA_DIST += libkmod/COPYING libkmod/README diff --git a/configure.ac b/configure.ac index b3bb2180..6d32fee9 100644 --- a/configure.ac +++ b/configure.ac @@ -22,6 +22,7 @@ AC_PROG_CC_C99 AC_C_TYPEOF AM_PROG_CC_C_O AC_PROG_GCC_TRADITIONAL +AC_C_BIGENDIAN required_private_libs="" diff --git a/libkmod/libkmod-elf.c b/libkmod/libkmod-elf.c new file mode 100644 index 00000000..e1833cf5 --- /dev/null +++ b/libkmod/libkmod-elf.c @@ -0,0 +1,619 @@ +/* + * libkmod - interface to kernel module operations + * + * Copyright (C) 2011 ProFUSION embedded systems + * + * 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; either + * version 2.1 of the License, or (at your option) any later version. + * + * 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 +#include +#include +#include +#include + +#include "libkmod.h" +#include "libkmod-private.h" + +enum kmod_elf_class { + KMOD_ELF_32 = (1 << 1), + KMOD_ELF_64 = (1 << 2), + KMOD_ELF_LSB = (1 << 3), + KMOD_ELF_MSB = (1 << 4) +}; + +#ifdef WORDS_BIGENDIAN +static const enum kmod_elf_class native_endianess = KMOD_ELF_MSB; +#else +static const enum kmod_elf_class native_endianess = KMOD_ELF_LSB; +#endif + +struct kmod_elf { + const uint8_t *memory; + uint8_t *changed; + uint64_t size; + enum kmod_elf_class class; + struct kmod_elf_header { + struct { + uint64_t offset; + uint16_t count; + uint16_t entry_size; + } section; + struct { + uint16_t section; /* index of the strings section */ + uint64_t size; + uint64_t offset; + uint32_t nameoff; /* offset in strings itself */ + } strings; + } header; +}; + +//#define ENABLE_ELFDBG 1 + +#if defined(ENABLE_LOGGING) && defined(ENABLE_ELFDBG) +#define ELFDBG(elf, ...) \ + _elf_dbg(elf, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__); + +static inline void _elf_dbg(const struct kmod_elf *elf, const char *fname, unsigned line, const char *func, const char *fmt, ...) +{ + va_list args; + + fprintf(stderr, "ELFDBG-%d%c: %s:%u %s() ", + (elf->class & KMOD_ELF_32) ? 32 : 64, + (elf->class & KMOD_ELF_MSB) ? 'M' : 'L', + fname, line, func); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} +#else +#define ELFDBG(elf, ...) +#endif + + +static int elf_identify(const void *memory, uint64_t size) +{ + const uint8_t *p = memory; + int class = 0; + + if (size <= EI_NIDENT || memcmp(p, ELFMAG, SELFMAG) != 0) + return -ENOEXEC; + + switch (p[EI_CLASS]) { + case ELFCLASS32: + if (size <= sizeof(Elf32_Ehdr)) + return -EINVAL; + class |= KMOD_ELF_32; + break; + case ELFCLASS64: + if (size <= sizeof(Elf64_Ehdr)) + return -EINVAL; + class |= KMOD_ELF_64; + break; + default: + return -EINVAL; + } + + switch (p[EI_DATA]) { + case ELFDATA2LSB: + class |= KMOD_ELF_LSB; + break; + case ELFDATA2MSB: + class |= KMOD_ELF_MSB; + break; + default: + return -EINVAL; + } + + return class; +} + +static inline uint64_t elf_get_uint(const struct kmod_elf *elf, uint64_t offset, uint16_t size) +{ + const uint8_t *p; + uint64_t ret = 0; + size_t i; + + assert(size <= sizeof(uint64_t)); + assert(offset + size <= elf->size); + if (offset + size > elf->size) { + ELFDBG(elf, "out of bounds: %"PRIu64" + %"PRIu16" = %"PRIu64"> %"PRIu64" (ELF size)\n", + offset, size, offset + size, elf->size); + return (uint64_t)-1; + } + + p = elf->memory + offset; + if (elf->class & KMOD_ELF_MSB) { + for (i = 0; i < size; i++) + ret = (ret << 8) | p[size]; + } else { + for (i = 1; i <= size; i++) + ret = (ret << 8) | p[size - i]; + } + + ELFDBG(elf, "size=%"PRIu16" offset=%"PRIu64" value=%"PRIu64"\n", + size, offset, ret); + + return ret; +} + +static inline int elf_set_uint(struct kmod_elf *elf, uint64_t offset, uint64_t size, uint64_t value) +{ + uint8_t *p; + size_t i; + + ELFDBG(elf, "size=%"PRIu16" offset=%"PRIu64" value=%"PRIu64" write memory=%p\n", + size, offset, value, elf->changed); + + assert(size <= sizeof(uint64_t)); + assert(offset + size <= elf->size); + if (offset + size > elf->size) { + ELFDBG(elf, "out of bounds: %"PRIu64" + %"PRIu16" = %"PRIu64"> %"PRIu64" (ELF size)\n", + offset, size, offset + size, elf->size); + return -1; + } + + if (elf->changed == NULL) { + elf->changed = malloc(elf->size); + if (elf->changed == NULL) + return -errno; + memcpy(elf->changed, elf->memory, elf->size); + elf->memory = elf->changed; + ELFDBG(elf, "copied memory to allow writing.\n"); + } + + p = elf->changed + offset; + if (elf->class & KMOD_ELF_MSB) { + for (i = 1; i <= size; i++) { + p[size - i] = value & 0xff; + value = (value & 0xffffffffffffff00) >> 8; + } + } else { + for (i = 0; i < size; i++) { + p[i] = value & 0xff; + value = (value & 0xffffffffffffff00) >> 8; + } + } + + return 0; +} + +static inline const void *elf_get_mem(const struct kmod_elf *elf, uint64_t offset) +{ + assert(offset < elf->size); + if (offset >= elf->size) { + ELFDBG(elf, "out-of-bounds: %"PRIu64" >= %"PRIu64" (ELF size)\n", + offset, elf->size); + return NULL; + } + return elf->memory + offset; +} + +static inline const void *elf_get_section_header(const struct kmod_elf *elf, uint16_t idx) +{ + assert(idx != SHN_UNDEF); + assert(idx < elf->header.section.count); + if (idx == SHN_UNDEF || idx >= elf->header.section.count) { + ELFDBG(elf, "invalid section number: %"PRIu16", last=%"PRIu16"\n", + idx, elf->header.section.count); + return NULL; + } + return elf_get_mem(elf, elf->header.section.offset + + idx * elf->header.section.entry_size); +} + +static inline int elf_get_section_info(const struct kmod_elf *elf, uint16_t idx, uint64_t *offset, uint64_t *size, uint32_t *nameoff) +{ + const uint8_t *p = elf_get_section_header(elf, idx); + uint64_t min_size, off = p - elf->memory; + + if (p == NULL) { + ELFDBG(elf, "no section at %"PRIu16"\n", idx); + *offset = 0; + *size = 0; + *nameoff = 0; + return -EINVAL; + } + +#define READV(field) \ + elf_get_uint(elf, off + offsetof(typeof(*hdr), field), sizeof(hdr->field)) + + if (elf->class & KMOD_ELF_32) { + const Elf32_Shdr *hdr = (const Elf32_Shdr *)p; + *size = READV(sh_size); + *offset = READV(sh_offset); + *nameoff = READV(sh_name); + } else { + const Elf64_Shdr *hdr = (const Elf64_Shdr *)p; + *size = READV(sh_size); + *offset = READV(sh_offset); + *nameoff = READV(sh_name); + } +#undef READV + + min_size = *offset + *size; + if (min_size >= elf->size) { + ELFDBG(elf, "out-of-bounds: %"PRIu64" >= %"PRIu64" (ELF size)\n", + min_size, elf->size); + return -EINVAL; + } + + ELFDBG(elf, "section=%"PRIu16" is: offset=%"PRIu64" size=%"PRIu64" nameoff=%"PRIu32"\n", + idx, *offset, *size, *nameoff); + + return 0; +} + +static const char *elf_get_strings_section(const struct kmod_elf *elf, uint64_t *size) +{ + *size = elf->header.strings.size; + return elf_get_mem(elf, elf->header.strings.offset); +} + +struct kmod_elf *kmod_elf_new(const void *memory, off_t size) +{ + struct kmod_elf *elf; + size_t hdr_size, shdr_size, min_size; + int class; + + assert(sizeof(uint16_t) == sizeof(Elf32_Half)); + assert(sizeof(uint16_t) == sizeof(Elf64_Half)); + assert(sizeof(uint32_t) == sizeof(Elf32_Word)); + assert(sizeof(uint32_t) == sizeof(Elf64_Word)); + + class = elf_identify(memory, size); + if (class < 0) { + errno = -class; + return NULL; + } + + elf = malloc(sizeof(struct kmod_elf)); + if (elf == NULL) { + return NULL; + } + + elf->memory = memory; + elf->changed = NULL; + elf->size = size; + elf->class = class; + +#define READV(field) \ + elf_get_uint(elf, offsetof(typeof(*hdr), field), sizeof(hdr->field)) + +#define LOAD_HEADER \ + elf->header.section.offset = READV(e_shoff); \ + elf->header.section.count = READV(e_shnum); \ + elf->header.section.entry_size = READV(e_shentsize); \ + elf->header.strings.section = READV(e_shstrndx) + if (elf->class & KMOD_ELF_32) { + const Elf32_Ehdr *hdr = elf_get_mem(elf, 0); + LOAD_HEADER; + hdr_size = sizeof(Elf32_Ehdr); + shdr_size = sizeof(Elf32_Shdr); + } else { + const Elf64_Ehdr *hdr = elf_get_mem(elf, 0); + LOAD_HEADER; + hdr_size = sizeof(Elf64_Ehdr); + shdr_size = sizeof(Elf64_Shdr); + } +#undef LOAD_HEADER +#undef READV + + ELFDBG(elf, "section: offset=%"PRIu64" count=%"PRIu16" entry_size=%"PRIu16" strings index=%"PRIu16"\n", + elf->header.section.offset, + elf->header.section.count, + elf->header.section.entry_size, + elf->header.strings.section); + + if (elf->header.section.entry_size != shdr_size) { + ELFDBG(elf, "unexpected section entry size: %"PRIu16", expected %"PRIu16"\n", + elf->header.section.entry_size, shdr_size); + goto invalid; + } + min_size = hdr_size * shdr_size * elf->header.section.count; + if (min_size >= elf->size) { + ELFDBG(elf, "file is too short to hold sections\n"); + goto invalid; + } + + if (elf_get_section_info(elf, elf->header.strings.section, + &elf->header.strings.offset, + &elf->header.strings.size, + &elf->header.strings.nameoff) < 0) { + ELFDBG(elf, "could not get strings section\n"); + goto invalid; + } else { + uint64_t slen; + const char *s = elf_get_strings_section(elf, &slen); + if (slen == 0 || s[slen - 1] != '\0') { + ELFDBG(elf, "strings section does not ends with \\0\n"); + goto invalid; + } + } + + return elf; + +invalid: + free(elf); + errno = EINVAL; + return NULL; +} + +void kmod_elf_unref(struct kmod_elf *elf) +{ + free(elf->changed); + free(elf); +} + +const void *kmod_elf_get_memory(const struct kmod_elf *elf) +{ + return elf->memory; +} + +static int kmod_elf_get_section(const struct kmod_elf *elf, const char *section, const void **buf, size_t *buf_size) +{ + uint64_t nameslen; + const char *names = elf_get_strings_section(elf, &nameslen); + uint16_t i; + + *buf = NULL; + *buf_size = 0; + + for (i = 1; i < elf->header.section.count; i++) { + uint64_t off, size; + uint32_t nameoff; + const char *n; + int err = elf_get_section_info(elf, i, &off, &size, &nameoff); + if (err < 0) + continue; + if (nameoff >= nameslen) + continue; + n = names + nameoff; + if (!streq(section, n)) + continue; + + *buf = elf_get_mem(elf, off); + *buf_size = size; + return 0; + } + + return -ENOENT; +} + +/* array will be allocated with strings in a single malloc, just free *array */ +int kmod_elf_get_strings(const struct kmod_elf *elf, const char *section, char ***array) +{ + uint64_t i, last, size; + const void *buf; + const char *strings; + char *itr, **a; + int count, err; + + *array = NULL; + + err = kmod_elf_get_section(elf, section, &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; + + for (i = 0, count = 0; i < size; i++) { + if (strings[i] == '\0') + count++; + } + if (strings[i - 1] != '\0') + count++; + + *array = a = malloc(size + 1 + sizeof(char *) * 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; + a[count] = 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] = 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_modversions(const struct kmod_elf *elf, struct kmod_modversion **array) +{ + uint64_t size, secsize, slen, off; + struct kmod_modversion *a; + const void *buf; + char *itr; + int i, count, err; + struct kmod_modversion32 { + uint32_t crc; + char name[64 - sizeof(uint32_t)]; + }; + struct kmod_modversion64 { + uint64_t crc; + char name[64 - sizeof(uint64_t)]; + }; + + *array = NULL; + + err = kmod_elf_get_section(elf, "__versions", &buf, &size); + if (err < 0) + return err; + if (buf == NULL || size == 0) + return 0; + + if (elf->class & KMOD_ELF_32) + secsize = sizeof(struct kmod_modversion32); + else + secsize = sizeof(struct kmod_modversion64); + + if (size % secsize != 0) + return -EINVAL; + count = size / secsize; + + off = (const uint8_t *)buf - elf->memory; + slen = 0; + for (i = 0; i < count; i++, off += secsize) { + const char *symbol; + if (elf->class & KMOD_ELF_32) { + struct kmod_modversion32 *mv; + symbol = elf_get_mem(elf, off + sizeof(mv->crc)); + } else { + struct kmod_modversion64 *mv; + symbol = elf_get_mem(elf, off + sizeof(mv->crc)); + } + slen += strlen(symbol) + 1; + } + + *array = a = malloc(sizeof(struct kmod_modversion) * count + slen); + if (*array == NULL) + return -errno; + + itr = (char *)(a + count); + off = (const uint8_t *)buf - elf->memory; + for (i = 0; i < count; i++, off += secsize) { + uint64_t crc; + const char *symbol; + size_t symbollen; + if (elf->class & KMOD_ELF_32) { + struct kmod_modversion32 *mv; + crc = elf_get_uint(elf, off, sizeof(mv->crc)); + symbol = elf_get_mem(elf, off + sizeof(mv->crc)); + } else { + struct kmod_modversion64 *mv; + crc = elf_get_uint(elf, off, sizeof(mv->crc)); + symbol = elf_get_mem(elf, off + sizeof(mv->crc)); + } + + a[i].crc = crc; + a[i].symbol = itr; + symbollen = strlen(symbol) + 1; + memcpy(itr, symbol, symbollen); + itr += symbollen; + } + + return count; +} + +int kmod_elf_strip_section(struct kmod_elf *elf, const char *section) +{ + uint64_t size, off; + const void *buf; + int err = kmod_elf_get_section(elf, section, &buf, &size); + if (err < 0) + return err; + + off = (const uint8_t *)buf - elf->memory; + +#define WRITEV(field, value) \ + elf_set_uint(elf, off + offsetof(typeof(*hdr), field), sizeof(hdr->field), value) + if (elf->class & KMOD_ELF_32) { + const Elf32_Shdr *hdr = buf; + uint32_t val = ~(uint32_t)SHF_ALLOC; + return WRITEV(sh_flags, val); + } else { + const Elf64_Shdr *hdr = buf; + uint64_t val = ~(uint64_t)SHF_ALLOC; + return WRITEV(sh_flags, val); + } +#undef WRITEV +} + +int kmod_elf_strip_vermagic(struct kmod_elf *elf) +{ + uint64_t i, size; + const void *buf; + const char *strings; + int err; + + err = kmod_elf_get_section(elf, ".modinfo", &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; + + for (i = 0; i < size; i++) { + const char *s; + size_t off, len; + + if (strings[i] == '\0') + continue; + if (i + 1 >= size) + continue; + + s = strings + i; + len = sizeof("vermagic=") - 1; + if (i + len >= size) + continue; + if (strncmp(s, "vermagic=", len) != 0) { + i += strlen(s); + continue; + } + s += len; + off = (const uint8_t *)s - elf->memory; + + if (elf->changed == NULL) { + elf->changed = malloc(elf->size); + if (elf->changed == NULL) + return -errno; + memcpy(elf->changed, elf->memory, elf->size); + elf->memory = elf->changed; + ELFDBG(elf, "copied memory to allow writing.\n"); + } + + len = strlen(s); + ELFDBG(elf, "clear .modinfo vermagic \"%s\" (%zd bytes)\n", + s, len); + memset(elf->changed + off, '\0', len); + return 0; + } + + ELFDBG(elf, "no vermagic found in .modinfo\n"); + return -ENOENT; +} diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c index 0330ffac..a5862013 100644 --- a/libkmod/libkmod-module.c +++ b/libkmod/libkmod-module.c @@ -682,7 +682,7 @@ KMOD_EXPORT int kmod_module_remove_module(struct kmod_module *mod, return 0; } -extern long init_module(void *mem, unsigned long len, const char *args); +extern long init_module(const void *mem, unsigned long len, const char *args); /** * kmod_module_insert_module: @@ -701,23 +701,23 @@ KMOD_EXPORT int kmod_module_insert_module(struct kmod_module *mod, const char *options) { int err; - void *mem; + const void *mem; off_t size; struct kmod_file *file; + struct kmod_elf *elf = NULL; + const char *path; const char *args = options ? options : ""; if (mod == NULL) return -ENOENT; - if (mod->path == NULL) { + path = kmod_module_get_path(mod); + if (path == NULL) { ERR(mod->ctx, "Not supported to load a module by name yet\n"); return -ENOSYS; } - if (flags != 0) - INFO(mod->ctx, "Flags are not implemented yet\n"); - - file = kmod_file_open(mod->path); + file = kmod_file_open(path); if (file == NULL) { err = -errno; return err; @@ -726,10 +726,35 @@ KMOD_EXPORT int kmod_module_insert_module(struct kmod_module *mod, 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); + if (elf == NULL) { + err = -errno; + goto elf_failed; + } + + if (flags & KMOD_INSERT_FORCE_MODVERSION) { + err = kmod_elf_strip_section(elf, "__versions"); + if (err < 0) + INFO(mod->ctx, "Failed to strip modversion: %s\n", strerror(-err)); + } + + if (flags & KMOD_INSERT_FORCE_VERMAGIC) { + err = kmod_elf_strip_vermagic(elf); + if (err < 0) + INFO(mod->ctx, "Failed to strip vermagic: %s\n", strerror(-err)); + } + + mem = kmod_elf_get_memory(elf); + } + err = init_module(mem, size, args); if (err < 0) - ERR(mod->ctx, "Failed to insert module '%s'\n", mod->path); + ERR(mod->ctx, "Failed to insert module '%s'\n", path); + if (elf != NULL) + kmod_elf_unref(elf); +elf_failed: kmod_file_unref(file); return err; @@ -1506,3 +1531,351 @@ KMOD_EXPORT void kmod_module_section_free_list(struct kmod_list *list) list = kmod_list_remove(list); } } + +struct kmod_module_info { + char *key; + char value[]; +}; + +static struct kmod_module_info *kmod_module_info_new(const char *key, size_t keylen, const char *value, size_t valuelen) +{ + struct kmod_module_info *info; + + info = malloc(sizeof(struct kmod_module_info) + keylen + valuelen + 2); + if (info == NULL) + return NULL; + + info->key = (char *)info + sizeof(struct kmod_module_info) + + valuelen + 1; + memcpy(info->key, key, keylen); + info->key[keylen] = '\0'; + memcpy(info->value, value, valuelen); + info->value[valuelen] = '\0'; + return info; +} + +static void kmod_module_info_free(struct kmod_module_info *info) +{ + free(info); +} + +/** + * kmod_module_get_info: + * @mod: kmod module + * @list: where to return list of module information. Use + * kmod_module_info_get_key() and + * kmod_module_info_get_value(). Release this list with + * kmod_module_info_unref_list() + * + * Get a list of entries in ELF section ".modinfo", these contain + * alias, license, depends, vermagic and other keys with respective + * values. + * + * After use, free the @list by calling kmod_module_info_free_list(). + * + * Returns: 0 on success or < 0 otherwise. + */ +KMOD_EXPORT int kmod_module_get_info(const struct kmod_module *mod, struct kmod_list **list) +{ + struct kmod_file *file; + struct kmod_elf *elf; + const char *path; + const void *mem; + char **strings; + 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_strings(elf, ".modinfo", &strings); + if (count < 0) { + ret = count; + goto get_strings_error; + } + + for (i = 0; i < count; i++) { + struct kmod_module_info *info; + struct kmod_list *n; + const char *key, *value; + size_t keylen, valuelen; + + key = strings[i]; + value = strchr(key, '='); + if (value == NULL) { + keylen = strlen(key); + valuelen = 0; + } else { + keylen = value - key; + value++; + valuelen = strlen(value); + } + + info = kmod_module_info_new(key, keylen, value, valuelen); + if (info == NULL) { + ret = -errno; + kmod_module_info_free_list(*list); + *list = NULL; + goto list_error; + } + + n = kmod_list_append(*list, info); + if (n != NULL) + *list = n; + else { + kmod_module_info_free(info); + kmod_module_info_free_list(*list); + *list = NULL; + ret = -ENOMEM; + goto list_error; + } + } + ret = count; + +list_error: + free(strings); +get_strings_error: + kmod_elf_unref(elf); +elf_open_error: + kmod_file_unref(file); + + return ret; +} + +/** + * kmod_module_info_get_key: + * @entry: a list entry representing a kmod module info + * + * Get the key of a kmod module info. + * + * Returns: the key of this kmod module info on success or NULL on + * failure. The string is owned by the info, do not free it. + */ +KMOD_EXPORT const char *kmod_module_info_get_key(const struct kmod_list *entry) +{ + struct kmod_module_info *info; + + if (entry == NULL) + return NULL; + + info = entry->data; + return info->key; +} + +/** + * kmod_module_info_get_value: + * @entry: a list entry representing a kmod module info + * + * Get the value of a kmod module info. + * + * Returns: the value of this kmod module info on success or NULL on + * failure. The string is owned by the info, do not free it. + */ +KMOD_EXPORT const char *kmod_module_info_get_value(const struct kmod_list *entry) +{ + struct kmod_module_info *info; + + if (entry == NULL) + return NULL; + + info = entry->data; + return info->value; +} + +/** + * kmod_module_info_free_list: + * @list: kmod module info list + * + * Release the resources taken by @list + */ +KMOD_EXPORT void kmod_module_info_free_list(struct kmod_list *list) +{ + while (list) { + kmod_module_info_free(list->data); + list = kmod_list_remove(list); + } +} + +struct kmod_module_version { + uint64_t crc; + char symbol[]; +}; + +static struct kmod_module_version *kmod_module_versions_new(uint64_t crc, const char *symbol) +{ + struct kmod_module_version *mv; + size_t symbollen = strlen(symbol) + 1; + + mv = malloc(sizeof(struct kmod_module_version) + symbollen); + if (mv == NULL) + return NULL; + + mv->crc = crc; + memcpy(mv->symbol, symbol, symbollen); + return mv; +} + +static void kmod_module_version_free(struct kmod_module_version *version) +{ + free(version); +} + +/** + * kmod_module_get_versions: + * @mod: kmod module + * @list: where to return list of module versions. Use + * kmod_module_versions_get_symbol() and + * kmod_module_versions_get_crc(). Release this list with + * kmod_module_versions_unref_list() + * + * Get a list of entries in ELF section "__versions". + * + * After use, free the @list by calling kmod_module_versions_free_list(). + * + * Returns: 0 on success or < 0 otherwise. + */ +KMOD_EXPORT int kmod_module_get_versions(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 *versions; + 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_modversions(elf, &versions); + if (count < 0) { + ret = count; + goto get_strings_error; + } + + for (i = 0; i < count; i++) { + struct kmod_module_version *mv; + struct kmod_list *n; + + mv = kmod_module_versions_new(versions[i].crc, versions[i].symbol); + if (mv == NULL) { + ret = -errno; + kmod_module_versions_free_list(*list); + *list = NULL; + goto list_error; + } + + n = kmod_list_append(*list, mv); + if (n != NULL) + *list = n; + else { + kmod_module_version_free(mv); + kmod_module_versions_free_list(*list); + *list = NULL; + ret = -ENOMEM; + goto list_error; + } + } + ret = count; + +list_error: + free(versions); +get_strings_error: + kmod_elf_unref(elf); +elf_open_error: + kmod_file_unref(file); + + return ret; +} + +/** + * kmod_module_versions_get_symbol: + * @entry: a list entry representing a kmod module versions + * + * Get the symbol of a kmod module versions. + * + * Returns: the symbol of this kmod module versions on success or NULL + * on failure. The string is owned by the versions, do not free it. + */ +KMOD_EXPORT const char *kmod_module_version_get_symbol(const struct kmod_list *entry) +{ + struct kmod_module_version *version; + + if (entry == NULL) + return NULL; + + version = entry->data; + return version->symbol; +} + +/** + * kmod_module_version_get_crc: + * @entry: a list entry representing a kmod module version + * + * 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. + */ +KMOD_EXPORT uint64_t kmod_module_version_get_crc(const struct kmod_list *entry) +{ + struct kmod_module_version *version; + + if (entry == NULL) + return 0; + + version = entry->data; + return version->crc; +} + +/** + * kmod_module_versions_free_list: + * @list: kmod module versions list + * + * Release the resources taken by @list + */ +KMOD_EXPORT void kmod_module_versions_free_list(struct kmod_list *list) +{ + while (list) { + kmod_module_version_free(list->data); + list = kmod_list_remove(list); + } +} diff --git a/libkmod/libkmod-private.h b/libkmod/libkmod-private.h index 6c8fed23..83371723 100644 --- a/libkmod/libkmod-private.h +++ b/libkmod/libkmod-private.h @@ -140,6 +140,20 @@ void *kmod_file_get_contents(const struct kmod_file *file) __must_check __attrib off_t kmod_file_get_size(const struct kmod_file *file) __must_check __attribute__((nonnull(1))); void kmod_file_unref(struct kmod_file *file) __attribute__((nonnull(1))); +/* libkmod-elf.c */ +struct kmod_elf; +struct kmod_modversion { + uint64_t crc; + char *symbol; +}; + +struct kmod_elf *kmod_elf_new(const void *memory, off_t size) __must_check __attribute__((nonnull(1))); +void kmod_elf_unref(struct kmod_elf *elf) __attribute__((nonnull(1))); +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_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))); /* util functions */ char *getline_wrapped(FILE *fp, unsigned int *linenum) __attribute__((nonnull(1))); diff --git a/libkmod/libkmod.h b/libkmod/libkmod.h index 76eaf8ea..2d53aae0 100644 --- a/libkmod/libkmod.h +++ b/libkmod/libkmod.h @@ -140,6 +140,16 @@ const char *kmod_module_get_remove_commands(const struct kmod_module *mod); int kmod_module_get_softdeps(const struct kmod_module *mod, struct kmod_list **pre, struct kmod_list **post); +int kmod_module_get_info(const struct kmod_module *mod, struct kmod_list **list); +const char *kmod_module_info_get_key(const struct kmod_list *entry); +const char *kmod_module_info_get_value(const struct kmod_list *entry); +void kmod_module_info_free_list(struct kmod_list *list); + +int kmod_module_get_versions(const struct kmod_module *mod, struct kmod_list **list); +const char *kmod_module_version_get_symbol(const struct kmod_list *entry); +uint64_t kmod_module_version_get_crc(const struct kmod_list *entry); +void kmod_module_versions_free_list(struct kmod_list *list); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/libkmod/libkmod.sym b/libkmod/libkmod.sym index d57a7398..39107a1a 100644 --- a/libkmod/libkmod.sym +++ b/libkmod/libkmod.sym @@ -47,6 +47,16 @@ global: kmod_module_get_install_commands; kmod_module_get_remove_commands; kmod_module_get_softdeps; + + kmod_module_get_info; + kmod_module_info_get_key; + kmod_module_info_get_value; + kmod_module_info_free_list; + + kmod_module_get_versions; + kmod_module_version_get_symbol; + kmod_module_version_get_crc; + kmod_module_versions_free_list; local: *; }; diff --git a/test/test-lookup.c b/test/test-lookup.c index 41616e8e..620237eb 100644 --- a/test/test-lookup.c +++ b/test/test-lookup.c @@ -133,6 +133,33 @@ int main(int argc, char *argv[]) } } + pre = NULL; + err = kmod_module_get_info(mod, &pre); + if (err > 0) { + puts("\t\tmodinfo:"); + kmod_list_foreach(d, pre) { + const char *key, *val; + key = kmod_module_info_get_key(d); + val = kmod_module_info_get_value(d); + printf("\t\t\t%s: %s\n", key, val ? val : ""); + } + kmod_module_info_free_list(pre); + } + + pre = NULL; + err = kmod_module_get_versions(mod, &pre); + if (err > 0) { + puts("\t\tmodversions:"); + kmod_list_foreach(d, pre) { + const char *symbol; + uint64_t crc; + symbol = kmod_module_version_get_symbol(d); + crc = kmod_module_version_get_crc(d); + printf("\t\t\t%s: %#"PRIx64"\n", symbol, crc); + } + kmod_module_versions_free_list(pre); + } + kmod_module_unref(mod); }