use /sys/module/ rather than parsing legacy /proc/modules
[mit.git] / modinfo.c
blobd2709fe73fabdbf7212d0a492237cf173b85eb56
1 /* Extract module info: useful for both the curious and for scripts. */
2 #define _GNU_SOURCE /* asprintf rocks */
3 #include <elf.h>
4 #include <unistd.h>
5 #include <getopt.h>
6 #include <sys/types.h>
7 #include <dirent.h>
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <sys/utsname.h>
14 #include <sys/mman.h>
15 #include "zlibsupport.h"
16 #include "backwards_compat.c"
18 #define streq(a,b) (strcmp((a),(b)) == 0)
19 #define strstarts(a,start) (strncmp((a),(start), strlen(start)) == 0)
21 #ifndef MODULE_DIR
22 #define MODULE_DIR "/lib/modules"
23 #endif
25 static int elf_endian;
26 static int my_endian;
28 static inline void __endian(const void *src, void *dest, unsigned int size)
30 unsigned int i;
31 for (i = 0; i < size; i++)
32 ((unsigned char*)dest)[i] = ((unsigned char*)src)[size - i-1];
35 #define TO_NATIVE(x) \
36 ({ \
37 typeof(x) __x; \
38 if (elf_endian != my_endian) __endian(&(x), &(__x), sizeof(__x)); \
39 else __x = x; \
40 __x; \
43 static void *get_section32(void *file, unsigned long *size, const char *name)
45 Elf32_Ehdr *hdr = file;
46 Elf32_Shdr *sechdrs = file + TO_NATIVE(hdr->e_shoff);
47 const char *secnames;
48 unsigned int i;
50 secnames = file
51 + TO_NATIVE(sechdrs[TO_NATIVE(hdr->e_shstrndx)].sh_offset);
52 for (i = 1; i < TO_NATIVE(hdr->e_shnum); i++)
53 if (streq(secnames + TO_NATIVE(sechdrs[i].sh_name), name)) {
54 *size = TO_NATIVE(sechdrs[i].sh_size);
55 return file + TO_NATIVE(sechdrs[i].sh_offset);
57 return NULL;
60 static void *get_section64(void *file, unsigned long *size, const char *name)
62 Elf64_Ehdr *hdr = file;
63 Elf64_Shdr *sechdrs = file + TO_NATIVE(hdr->e_shoff);
64 const char *secnames;
65 unsigned int i;
67 secnames = file
68 + TO_NATIVE(sechdrs[TO_NATIVE(hdr->e_shstrndx)].sh_offset);
69 for (i = 1; i < TO_NATIVE(hdr->e_shnum); i++)
70 if (streq(secnames + TO_NATIVE(sechdrs[i].sh_name), name)) {
71 *size = TO_NATIVE(sechdrs[i].sh_size);
72 return file + TO_NATIVE(sechdrs[i].sh_offset);
74 return NULL;
77 static int elf_ident(void *mod, unsigned long size)
79 /* "\177ELF" <byte> where byte = 001 for 32-bit, 002 for 64 */
80 char *ident = mod;
82 if (size < EI_CLASS || memcmp(mod, ELFMAG, SELFMAG) != 0)
83 return ELFCLASSNONE;
84 elf_endian = ident[EI_DATA];
85 return ident[EI_CLASS];
88 static void *get_section(void *file, unsigned long filesize,
89 unsigned long *size, const char *name)
91 switch (elf_ident(file, filesize)) {
92 case ELFCLASS32:
93 return get_section32(file, size, name);
94 case ELFCLASS64:
95 return get_section64(file, size, name);
96 default:
97 return NULL;
101 static const char *next_string(const char *string, unsigned long *secsize)
103 /* Skip non-zero chars */
104 while (string[0]) {
105 string++;
106 if ((*secsize)-- <= 1)
107 return NULL;
110 /* Skip any zero padding. */
111 while (!string[0]) {
112 string++;
113 if ((*secsize)-- <= 1)
114 return NULL;
116 return string;
119 struct param
121 struct param *next;
122 const char *name; /* Terminated by a colon */
123 const char *param;
124 const char *type;
127 static struct param *add_param(const char *name, struct param **list)
129 struct param *i;
130 unsigned int namelen = strcspn(name, ":") + 1;
132 for (i = *list; i; i = i->next)
133 if (strncmp(i->name, name, namelen) == 0)
134 return i;
135 i = malloc(sizeof(*i) + namelen+1);
136 strncpy((char *)(i + 1), name, namelen);
137 ((char *)(i + 1))[namelen] = '\0';
138 i->name = (char *)(i + 1);
139 i->param = NULL;
140 i->type = NULL;
141 i->next = *list;
142 *list = i;
143 return i;
146 static void print_tag(const char *tag, const char *info, unsigned long size,
147 const char *filename, char sep)
149 unsigned int taglen = strlen(tag);
151 if (streq(tag, "filename")) {
152 printf("%s%c", filename, sep);
153 return;
156 for (; info; info = next_string(info, &size))
157 if (strncmp(info, tag, taglen) == 0 && info[taglen] == '=')
158 printf("%s%c", info + taglen + 1, sep);
161 static void print_all(const char *info, unsigned long size,
162 const char *filename, char sep)
164 struct param *i, *params = NULL;
166 printf("%-16s%s%c", "filename:", filename, sep);
167 for (; info; info = next_string(info, &size)) {
168 char *eq, *colon;
170 /* We expect this in parm and parmtype. */
171 colon = strchr(info, ':');
173 /* We store these for handling at the end */
174 if (strstarts(info, "parm=") && colon) {
175 i = add_param(info + strlen("parm="), &params);
176 i->param = colon + 1;
177 continue;
179 if (strstarts(info, "parmtype=") && colon) {
180 i = add_param(info + strlen("parmtype="), &params);
181 i->type = colon + 1;
182 continue;
185 if (!sep) {
186 printf("%s%c", info, sep);
187 continue;
190 eq = strchr(info, '=');
191 /* Warn if no '=' maybe? */
192 if (eq) {
193 char tag[eq - info + 2];
194 strncpy(tag, info, eq - info);
195 tag[eq-info] = ':';
196 tag[eq-info+1] = '\0';
197 printf("%-16s%s%c", tag, eq+1, sep);
201 /* Now show parameters. */
202 for (i = params; i; i = i->next) {
203 if (!i->param)
204 printf("%-16s%s%s%c", "parm:", i->name, i->type, sep);
205 else if (i->type)
206 printf("%-16s%s%s (%s)%c",
207 "parm:", i->name, i->param, i->type, sep);
208 else
209 printf("%-16s%s%s%c", "parm:", i->name, i->param, sep);
213 static struct option options[] =
215 {"author", 0, 0, 'a'},
216 {"description", 0, 0, 'd'},
217 {"license", 0, 0, 'l'},
218 {"parameters", 0, 0, 'p'},
219 {"filename", 0, 0, 'n'},
220 {"version", 0, 0, 'V'},
221 {"help", 0, 0, 'h'},
222 {"null", 0, 0, '0'},
223 {"field", 0, 0, 'F'},
224 {0, 0, 0, 0}
227 /* - and _ are equivalent, and expect suffix. */
228 static int name_matches(const char *line, const char *end, const char *modname)
230 unsigned int i;
231 char *p;
233 /* Ignore comment lines */
234 if (line[strspn(line, "\t ")] == '#')
235 return 0;
237 /* Find last / before colon. */
238 p = memchr(line, ':', end - line);
239 if (!p)
240 return 0;
241 while (p > line) {
242 if (*p == '/') {
243 p++;
244 break;
246 p--;
249 for (i = 0; modname[i]; i++) {
250 /* Module names can't have colons. */
251 if (modname[i] == ':')
252 continue;
253 if (modname[i] == p[i])
254 continue;
255 if (modname[i] == '_' && p[i] == '-')
256 continue;
257 if (modname[i] == '-' && p[i] == '_')
258 continue;
259 return 0;
261 /* Must match all the way to the extension */
262 return (p[i] == '.');
265 static char *next_line(char *p, const char *end)
267 char *eol;
269 eol = memchr(p, '\n', end - p);
270 if (eol)
271 return eol + 1;
272 return (char *)end + 1;
275 static void *grab_module(const char *name, unsigned long *size, char**filename,
276 const char *kernel)
278 char *data;
279 struct utsname buf;
280 char *depname, *p;
282 data = grab_file(name, size);
283 if (data) {
284 *filename = strdup(name);
285 return data;
287 if (errno != ENOENT) {
288 fprintf(stderr, "modinfo: could not open %s: %s\n",
289 name, strerror(errno));
290 return NULL;
293 /* Search for it in modules.dep. */
294 if (kernel) {
295 asprintf(&depname, "%s/%s/modules.dep", MODULE_DIR, kernel);
296 } else {
297 uname(&buf);
298 asprintf(&depname, "%s/%s/modules.dep", MODULE_DIR,
299 buf.release);
301 data = grab_file(depname, size);
302 if (!data) {
303 fprintf(stderr, "modinfo: could not open %s\n", depname);
304 free(depname);
305 return NULL;
307 free(depname);
309 for (p = data; p < data + *size; p = next_line(p, data + *size)) {
310 if (name_matches(p, data + *size, name)) {
311 int namelen = strcspn(p, ":");
312 *filename = malloc(namelen + 1);
313 memcpy(*filename, p, namelen);
314 (*filename)[namelen] = '\0';
315 release_file(data, *size);
316 data = grab_file(*filename, size);
317 if (!data)
318 fprintf(stderr,
319 "modinfo: could not open %s: %s\n",
320 *filename, strerror(errno));
321 return data;
324 release_file(data, *size);
325 fprintf(stderr, "modinfo: could not find module %s\n", name);
326 return NULL;
329 static void usage(const char *name)
331 fprintf(stderr, "Usage: %s [-0][-F field][-k kernelversion] module...\n"
332 " Prints out the information about one or more module(s).\n"
333 " If a fieldname is given, just print out that field (or nothing if not found).\n"
334 " Otherwise, print all information out in a readable form\n"
335 " If -0 is given, separate with nul, not newline.\n",
336 name);
339 int main(int argc, char *argv[])
341 union { short s; char c[2]; } endian_test;
342 const char *field = NULL;
343 const char *kernel = NULL;
344 char sep = '\n';
345 unsigned long infosize = 0;
346 int opt, ret = 0;
348 if (!getenv("NEW_MODINFO"))
349 try_old_version("modinfo", argv);
351 endian_test.s = 1;
352 if (endian_test.c[1] == 1) my_endian = ELFDATA2MSB;
353 else if (endian_test.c[0] == 1) my_endian = ELFDATA2LSB;
354 else
355 abort();
357 while ((opt = getopt_long(argc,argv,"adlpVhn0F:k:",options,NULL)) >= 0){
358 switch (opt) {
359 case 'a': field = "author"; break;
360 case 'd': field = "description"; break;
361 case 'l': field = "license"; break;
362 case 'p': field = "parm"; break;
363 case 'n': field = "filename"; break;
364 case 'V': printf(PACKAGE " version " VERSION "\n"); exit(0);
365 case 'F': field = optarg; break;
366 case '0': sep = '\0'; break;
367 case 'k': kernel = optarg; break;
368 default:
369 usage(argv[0]); exit(0);
372 if (argc < optind + 1)
373 usage(argv[0]);
375 for (opt = optind; opt < argc; opt++) {
376 void *info, *mod;
377 unsigned long modulesize;
378 char *filename;
380 mod = grab_module(argv[opt], &modulesize, &filename, kernel);
381 if (!mod) {
382 ret = 1;
383 continue;
386 info = get_section(mod, modulesize, &infosize, ".modinfo");
387 if (!info) {
388 release_file(mod, modulesize);
389 free(filename);
390 continue;
392 if (field)
393 print_tag(field, info, infosize, filename, sep);
394 else
395 print_all(info, infosize, filename, sep);
396 release_file(mod, modulesize);
397 free(filename);
399 return ret;