blob: 7c9c276d9700c06bb44b8234fd344e5360d106cc [file] [log] [blame]
/* Extract module info: useful for both the curious and for scripts. */
#define _GNU_SOURCE /* asprintf rocks */
#include <elf.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/utsname.h>
#include <sys/mman.h>
#include "zlibsupport.h"
#include "backwards_compat.c"
#define streq(a,b) (strcmp((a),(b)) == 0)
#define strstarts(a,start) (strncmp((a),(start), strlen(start)) == 0)
#ifndef MODULE_DIR
#define MODULE_DIR "/lib/modules"
#endif
static int elf_endian;
static int my_endian;
static inline void __endian(const void *src, void *dest, unsigned int size)
{
unsigned int i;
for (i = 0; i < size; i++)
((unsigned char*)dest)[i] = ((unsigned char*)src)[size - i-1];
}
#define TO_NATIVE(x) \
({ \
typeof(x) __x; \
if (elf_endian != my_endian) __endian(&(x), &(__x), sizeof(__x)); \
else __x = x; \
__x; \
})
static void *get_section32(void *file, unsigned long *size, const char *name)
{
Elf32_Ehdr *hdr = file;
Elf32_Shdr *sechdrs = file + TO_NATIVE(hdr->e_shoff);
const char *secnames;
unsigned int i;
secnames = file
+ TO_NATIVE(sechdrs[TO_NATIVE(hdr->e_shstrndx)].sh_offset);
for (i = 1; i < TO_NATIVE(hdr->e_shnum); i++)
if (streq(secnames + TO_NATIVE(sechdrs[i].sh_name), name)) {
*size = TO_NATIVE(sechdrs[i].sh_size);
return file + TO_NATIVE(sechdrs[i].sh_offset);
}
return NULL;
}
static void *get_section64(void *file, unsigned long *size, const char *name)
{
Elf64_Ehdr *hdr = file;
Elf64_Shdr *sechdrs = file + TO_NATIVE(hdr->e_shoff);
const char *secnames;
unsigned int i;
secnames = file
+ TO_NATIVE(sechdrs[TO_NATIVE(hdr->e_shstrndx)].sh_offset);
for (i = 1; i < TO_NATIVE(hdr->e_shnum); i++)
if (streq(secnames + TO_NATIVE(sechdrs[i].sh_name), name)) {
*size = TO_NATIVE(sechdrs[i].sh_size);
return file + TO_NATIVE(sechdrs[i].sh_offset);
}
return NULL;
}
static int elf_ident(void *mod, unsigned long size)
{
/* "\177ELF" <byte> where byte = 001 for 32-bit, 002 for 64 */
char *ident = mod;
if (size < EI_CLASS || memcmp(mod, ELFMAG, SELFMAG) != 0)
return ELFCLASSNONE;
elf_endian = ident[EI_DATA];
return ident[EI_CLASS];
}
static void *get_section(void *file, unsigned long filesize,
unsigned long *size, const char *name)
{
switch (elf_ident(file, filesize)) {
case ELFCLASS32:
return get_section32(file, size, name);
case ELFCLASS64:
return get_section64(file, size, name);
default:
return NULL;
}
}
static const char *next_string(const char *string, unsigned long *secsize)
{
/* Skip non-zero chars */
while (string[0]) {
string++;
if ((*secsize)-- <= 1)
return NULL;
}
/* Skip any zero padding. */
while (!string[0]) {
string++;
if ((*secsize)-- <= 1)
return NULL;
}
return string;
}
struct param
{
struct param *next;
const char *name; /* Terminated by a colon */
const char *param;
const char *type;
};
static struct param *add_param(const char *name, struct param **list)
{
struct param *i;
unsigned int namelen = strcspn(name, ":") + 1;
for (i = *list; i; i = i->next)
if (strncmp(i->name, name, namelen) == 0)
return i;
i = malloc(sizeof(*i) + namelen+1);
strncpy((char *)(i + 1), name, namelen);
((char *)(i + 1))[namelen] = '\0';
i->name = (char *)(i + 1);
i->param = NULL;
i->type = NULL;
i->next = *list;
*list = i;
return i;
}
static void print_tag(const char *tag, const char *info, unsigned long size,
const char *filename, char sep)
{
unsigned int taglen = strlen(tag);
if (streq(tag, "filename")) {
printf("%s%c", filename, sep);
return;
}
for (; info; info = next_string(info, &size))
if (strncmp(info, tag, taglen) == 0 && info[taglen] == '=')
printf("%s%c", info + taglen + 1, sep);
}
static void print_all(const char *info, unsigned long size,
const char *filename, char sep)
{
struct param *i, *params = NULL;
printf("%-16s%s%c", "filename:", filename, sep);
for (; info; info = next_string(info, &size)) {
char *eq, *colon;
/* We expect this in parm and parmtype. */
colon = strchr(info, ':');
/* We store these for handling at the end */
if (strstarts(info, "parm=") && colon) {
i = add_param(info + strlen("parm="), &params);
i->param = colon + 1;
continue;
}
if (strstarts(info, "parmtype=") && colon) {
i = add_param(info + strlen("parmtype="), &params);
i->type = colon + 1;
continue;
}
if (!sep) {
printf("%s%c", info, sep);
continue;
}
eq = strchr(info, '=');
/* Warn if no '=' maybe? */
if (eq) {
char tag[eq - info + 2];
strncpy(tag, info, eq - info);
tag[eq-info] = ':';
tag[eq-info+1] = '\0';
printf("%-16s%s%c", tag, eq+1, sep);
}
}
/* Now show parameters. */
for (i = params; i; i = i->next) {
if (!i->param)
printf("%-16s%s%s%c", "parm:", i->name, i->type, sep);
else if (i->type)
printf("%-16s%s%s (%s)%c",
"parm:", i->name, i->param, i->type, sep);
else
printf("%-16s%s%s%c", "parm:", i->name, i->param, sep);
}
}
static struct option options[] =
{
{"author", 0, 0, 'a'},
{"description", 0, 0, 'd'},
{"license", 0, 0, 'l'},
{"parameters", 0, 0, 'p'},
{"filename", 0, 0, 'n'},
{"version", 0, 0, 'V'},
{"help", 0, 0, 'h'},
{"null", 0, 0, '0'},
{"field", 1, 0, 'F'},
{0, 0, 0, 0}
};
/* - and _ are equivalent, and expect suffix. */
static int name_matches(const char *line, const char *end, const char *modname)
{
unsigned int i;
char *p;
/* Ignore comment lines */
if (line[strspn(line, "\t ")] == '#')
return 0;
/* Find last / before colon. */
p = memchr(line, ':', end - line);
if (!p)
return 0;
while (p > line) {
if (*p == '/') {
p++;
break;
}
p--;
}
for (i = 0; modname[i]; i++) {
/* Module names can't have colons. */
if (modname[i] == ':')
continue;
if (modname[i] == p[i])
continue;
if (modname[i] == '_' && p[i] == '-')
continue;
if (modname[i] == '-' && p[i] == '_')
continue;
return 0;
}
/* Must match all the way to the extension */
return (p[i] == '.');
}
static char *next_line(char *p, const char *end)
{
char *eol;
eol = memchr(p, '\n', end - p);
if (eol)
return eol + 1;
return (char *)end + 1;
}
static void *grab_module(const char *name, unsigned long *size, char**filename,
const char *kernel, const char *basedir)
{
char *data;
struct utsname buf;
char *depname, *p, *moddir;
if (strchr(name, '.') || strchr(name, '/')) {
data = grab_file(name, size);
if (data) {
*filename = strdup(name);
return data;
} else {
fprintf(stderr, "modinfo: could not open %s: %s\n",
name, strerror(errno));
return NULL;
}
}
if (kernel) {
if (strlen(basedir))
asprintf(&moddir, "%s/%s/%s",
basedir, MODULE_DIR, kernel);
else
asprintf(&moddir, "%s/%s",
MODULE_DIR, kernel);
} else {
uname(&buf);
if (strlen(basedir))
asprintf(&moddir, "%s/%s/%s",
basedir, MODULE_DIR, buf.release);
else
asprintf(&moddir, "%s/%s",
MODULE_DIR, buf.release);
}
asprintf(&depname, "%s/%s", moddir, "modules.dep");
/* Search for it in modules.dep. */
data = grab_file(depname, size);
if (!data) {
fprintf(stderr, "modinfo: could not open %s\n", depname);
free(depname);
return NULL;
}
free(depname);
for (p = data; p < data + *size; p = next_line(p, data + *size)) {
if (name_matches(p, data + *size, name)) {
int namelen = strcspn(p, ":");
if ('/' == p[0]) { /* old style deps - absolute path */
*filename = malloc(namelen + strlen(basedir)+2);
if (strlen(basedir)) {
sprintf(*filename, "%s/", basedir);
memcpy(*filename+strlen(basedir)+1,p,
namelen);
(*filename)[namelen
+strlen(basedir)+1] = '\0';
} else {
memcpy(*filename,p,namelen);
(*filename)[namelen] = '\0';
}
} else {
*filename = malloc(namelen + strlen(moddir)+2);
sprintf(*filename, "%s/", moddir);
memcpy(*filename+strlen(moddir)+1, p,namelen);
(*filename)[namelen+strlen(moddir)+1] ='\0';
}
release_file(data, *size);
data = grab_file(*filename, size);
if (!data)
fprintf(stderr,
"modinfo: could not open %s: %s\n",
*filename, strerror(errno));
return data;
}
}
release_file(data, *size);
fprintf(stderr, "modinfo: could not find module %s\n", name);
return NULL;
}
static void usage(const char *name)
{
fprintf(stderr, "Usage: %s [-0][-F field][-k kernelversion][-b basedir] module...\n"
" Prints out the information about one or more module(s).\n"
" If a fieldname is given, just print out that field (or nothing if not found).\n"
" Otherwise, print all information out in a readable form\n"
" If -0 is given, separate with nul, not newline.\n"
" If -b is given, use an image of the module tree.\n",
name);
}
int main(int argc, char *argv[])
{
union { short s; char c[2]; } endian_test;
const char *field = NULL;
const char *kernel = NULL;
char sep = '\n';
unsigned long infosize = 0;
int opt, ret = 0;
char *basedir = "";
if (!getenv("NEW_MODINFO"))
try_old_version("modinfo", argv);
endian_test.s = 1;
if (endian_test.c[1] == 1) my_endian = ELFDATA2MSB;
else if (endian_test.c[0] == 1) my_endian = ELFDATA2LSB;
else
abort();
while ((opt = getopt_long(argc,argv,"adlpVhn0F:k:b:",options,NULL)) >= 0){
switch (opt) {
case 'a': field = "author"; break;
case 'd': field = "description"; break;
case 'l': field = "license"; break;
case 'p': field = "parm"; break;
case 'n': field = "filename"; break;
case 'V': printf(PACKAGE " version " VERSION "\n"); exit(0);
case 'F': field = optarg; break;
case '0': sep = '\0'; break;
case 'k': kernel = optarg; break;
case 'b': basedir = optarg; break;
case 'h': usage(argv[0]); exit(0); break;
default:
usage(argv[0]); exit(1);
}
}
if (argc < optind + 1) {
usage(argv[0]);
exit(1);
}
for (opt = optind; opt < argc; opt++) {
void *info, *mod;
unsigned long modulesize;
char *filename;
mod = grab_module(argv[opt], &modulesize, &filename,
kernel, basedir);
if (!mod) {
ret = 1;
continue;
}
info = get_section(mod, modulesize, &infosize, ".modinfo");
if (!info) {
release_file(mod, modulesize);
free(filename);
continue;
}
if (field)
print_tag(field, info, infosize, filename, sep);
else
print_all(info, infosize, filename, sep);
release_file(mod, modulesize);
free(filename);
}
return ret;
}