| /* |
| * kmod-modinfo - query kernel module information using libkmod. |
| * |
| * Copyright (C) 2011-2013 ProFUSION embedded systems |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program 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 General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <errno.h> |
| #include <getopt.h> |
| #include <limits.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/utsname.h> |
| |
| #include <shared/util.h> |
| |
| #include <libkmod/libkmod.h> |
| |
| #include "kmod.h" |
| |
| static char separator = '\n'; |
| static const char *field = NULL; |
| |
| struct param { |
| struct param *next; |
| const char *name; |
| const char *param; |
| const char *type; |
| int namelen; |
| int paramlen; |
| int typelen; |
| }; |
| |
| static struct param *add_param(const char *name, int namelen, const char *param, int paramlen, const char *type, int typelen, struct param **list) |
| { |
| struct param *it; |
| |
| for (it = *list; it != NULL; it = it->next) { |
| if (it->namelen == namelen && |
| memcmp(it->name, name, namelen) == 0) |
| break; |
| } |
| |
| if (it == NULL) { |
| it = malloc(sizeof(struct param)); |
| if (it == NULL) |
| return NULL; |
| it->next = *list; |
| *list = it; |
| it->name = name; |
| it->namelen = namelen; |
| it->param = NULL; |
| it->type = NULL; |
| it->paramlen = 0; |
| it->typelen = 0; |
| } |
| |
| if (param != NULL) { |
| it->param = param; |
| it->paramlen = paramlen; |
| } |
| |
| if (type != NULL) { |
| it->type = type; |
| it->typelen = typelen; |
| } |
| |
| return it; |
| } |
| |
| static int process_parm(const char *key, const char *value, struct param **params) |
| { |
| const char *name, *param, *type; |
| int namelen, paramlen, typelen; |
| struct param *it; |
| const char *colon = strchr(value, ':'); |
| if (colon == NULL) { |
| ERR("Found invalid \"%s=%s\": missing ':'\n", |
| key, value); |
| return 0; |
| } |
| |
| name = value; |
| namelen = colon - value; |
| if (streq(key, "parm")) { |
| param = colon + 1; |
| paramlen = strlen(param); |
| type = NULL; |
| typelen = 0; |
| } else { |
| param = NULL; |
| paramlen = 0; |
| type = colon + 1; |
| typelen = strlen(type); |
| } |
| |
| it = add_param(name, namelen, param, paramlen, type, typelen, params); |
| if (it == NULL) { |
| ERR("Out of memory!\n"); |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| static int modinfo_params_do(const struct kmod_list *list) |
| { |
| const struct kmod_list *l; |
| struct param *params = NULL; |
| int err = 0; |
| |
| kmod_list_foreach(l, list) { |
| const char *key = kmod_module_info_get_key(l); |
| const char *value = kmod_module_info_get_value(l); |
| if (!streq(key, "parm") && !streq(key, "parmtype")) |
| continue; |
| |
| err = process_parm(key, value, ¶ms); |
| if (err < 0) |
| goto end; |
| } |
| |
| while (params != NULL) { |
| struct param *p = params; |
| params = p->next; |
| |
| if (p->param == NULL) |
| printf("%.*s: (%.*s)%c", |
| p->namelen, p->name, p->typelen, p->type, |
| separator); |
| else if (p->type != NULL) |
| printf("%.*s:%.*s (%.*s)%c", |
| p->namelen, p->name, |
| p->paramlen, p->param, |
| p->typelen, p->type, |
| separator); |
| else |
| printf("%.*s:%.*s%c", |
| p->namelen, p->name, |
| p->paramlen, p->param, |
| separator); |
| |
| free(p); |
| } |
| |
| end: |
| while (params != NULL) { |
| void *tmp = params; |
| params = params->next; |
| free(tmp); |
| } |
| |
| return err; |
| } |
| |
| static int modinfo_do(struct kmod_module *mod) |
| { |
| struct kmod_list *l, *list = NULL; |
| struct param *params = NULL; |
| int err, is_builtin; |
| const char *filename = kmod_module_get_path(mod); |
| |
| is_builtin = (filename == NULL); |
| |
| if (is_builtin) { |
| if (field == NULL) |
| printf("%-16s%s%c", "name:", |
| kmod_module_get_name(mod), separator); |
| else if (field != NULL && streq(field, "name")) |
| printf("%s%c", kmod_module_get_name(mod), separator); |
| filename = "(builtin)"; |
| } |
| |
| if (field != NULL && streq(field, "filename")) { |
| printf("%s%c", filename, separator); |
| return 0; |
| } else if (field == NULL) { |
| printf("%-16s%s%c", "filename:", |
| filename, separator); |
| } |
| |
| err = kmod_module_get_info(mod, &list); |
| if (err < 0) { |
| if (is_builtin && err == -ENOENT) { |
| /* |
| * This is an old kernel that does not have a file |
| * with information about built-in modules. |
| */ |
| return 0; |
| } |
| ERR("could not get modinfo from '%s': %s\n", |
| kmod_module_get_name(mod), strerror(-err)); |
| return err; |
| } |
| |
| if (field != NULL && streq(field, "parm")) { |
| err = modinfo_params_do(list); |
| goto end; |
| } |
| |
| kmod_list_foreach(l, list) { |
| const char *key = kmod_module_info_get_key(l); |
| const char *value = kmod_module_info_get_value(l); |
| int keylen; |
| |
| if (field != NULL) { |
| if (!streq(field, key)) |
| continue; |
| /* filtered output contains no key, just value */ |
| printf("%s%c", value, separator); |
| continue; |
| } |
| |
| if (streq(key, "parm") || streq(key, "parmtype")) { |
| err = process_parm(key, value, ¶ms); |
| if (err < 0) |
| goto end; |
| continue; |
| } |
| |
| if (separator == '\0') { |
| printf("%s=%s%c", key, value, separator); |
| continue; |
| } |
| |
| keylen = strlen(key); |
| printf("%s:%-*s%s%c", key, 15 - keylen, "", value, separator); |
| } |
| |
| if (field != NULL) |
| goto end; |
| |
| while (params != NULL) { |
| struct param *p = params; |
| params = p->next; |
| |
| if (p->param == NULL) |
| printf("%-16s%.*s:%.*s%c", "parm:", |
| p->namelen, p->name, p->typelen, p->type, |
| separator); |
| else if (p->type != NULL) |
| printf("%-16s%.*s:%.*s (%.*s)%c", "parm:", |
| p->namelen, p->name, |
| p->paramlen, p->param, |
| p->typelen, p->type, |
| separator); |
| else |
| printf("%-16s%.*s:%.*s%c", |
| "parm:", |
| p->namelen, p->name, |
| p->paramlen, p->param, |
| separator); |
| |
| free(p); |
| } |
| |
| end: |
| while (params != NULL) { |
| void *tmp = params; |
| params = params->next; |
| free(tmp); |
| } |
| kmod_module_info_free_list(list); |
| |
| return err; |
| } |
| |
| static int modinfo_path_do(struct kmod_ctx *ctx, const char *path) |
| { |
| struct kmod_module *mod; |
| int err = kmod_module_new_from_path(ctx, path, &mod); |
| if (err < 0) { |
| ERR("Module file %s not found.\n", path); |
| return err; |
| } |
| err = modinfo_do(mod); |
| kmod_module_unref(mod); |
| return err; |
| } |
| |
| static int modinfo_name_do(struct kmod_ctx *ctx, const char *name) |
| { |
| struct kmod_module *mod = NULL; |
| int err; |
| |
| err = kmod_module_new_from_name_lookup(ctx, name, &mod); |
| if (err < 0 || mod == NULL) { |
| ERR("Module name %s not found.\n", name); |
| return err < 0 ? err : -ENOENT; |
| } |
| |
| err = modinfo_do(mod); |
| kmod_module_unref(mod); |
| |
| return err; |
| } |
| |
| |
| static int modinfo_alias_do(struct kmod_ctx *ctx, const char *alias) |
| { |
| struct kmod_list *l, *list = NULL; |
| int err = kmod_module_new_from_lookup(ctx, alias, &list); |
| if (err < 0) { |
| ERR("Module alias %s not found.\n", alias); |
| return err; |
| } |
| |
| if (list == NULL) { |
| ERR("Module %s not found.\n", alias); |
| return -ENOENT; |
| } |
| |
| kmod_list_foreach(l, list) { |
| struct kmod_module *mod = kmod_module_get_module(l); |
| int r = modinfo_do(mod); |
| kmod_module_unref(mod); |
| if (r < 0) |
| err = r; |
| } |
| kmod_module_unref_list(list); |
| return err; |
| } |
| |
| static const char cmdopts_s[] = "adlpn0mF:k:b:Vh"; |
| static const struct option cmdopts[] = { |
| {"author", no_argument, 0, 'a'}, |
| {"description", no_argument, 0, 'd'}, |
| {"license", no_argument, 0, 'l'}, |
| {"parameters", no_argument, 0, 'p'}, |
| {"filename", no_argument, 0, 'n'}, |
| {"null", no_argument, 0, '0'}, |
| {"modname", no_argument, 0, 'm'}, |
| {"field", required_argument, 0, 'F'}, |
| {"set-version", required_argument, 0, 'k'}, |
| {"basedir", required_argument, 0, 'b'}, |
| {"version", no_argument, 0, 'V'}, |
| {"help", no_argument, 0, 'h'}, |
| {NULL, 0, 0, 0} |
| }; |
| |
| static void help(void) |
| { |
| printf("Usage:\n" |
| "\t%s [options] <modulename|filename> [args]\n" |
| "Options:\n" |
| "\t-a, --author Print only 'author'\n" |
| "\t-d, --description Print only 'description'\n" |
| "\t-l, --license Print only 'license'\n" |
| "\t-p, --parameters Print only 'parm'\n" |
| "\t-n, --filename Print only 'filename'\n" |
| "\t-0, --null Use \\0 instead of \\n\n" |
| "\t-m, --modname Handle argument as module name instead of alias or filename\n" |
| "\t-F, --field=FIELD Print only provided FIELD\n" |
| "\t-k, --set-version=VERSION Use VERSION instead of `uname -r`\n" |
| "\t-b, --basedir=DIR Use DIR as filesystem root for /lib/modules\n" |
| "\t-V, --version Show version\n" |
| "\t-h, --help Show this help\n", |
| program_invocation_short_name); |
| } |
| |
| static bool is_module_filename(const char *name) |
| { |
| struct stat st; |
| |
| if (stat(name, &st) == 0 && S_ISREG(st.st_mode) && |
| path_ends_with_kmod_ext(name, strlen(name))) |
| return true; |
| |
| return false; |
| } |
| |
| static int do_modinfo(int argc, char *argv[]) |
| { |
| struct kmod_ctx *ctx; |
| char dirname_buf[PATH_MAX]; |
| const char *dirname = NULL; |
| const char *kversion = NULL; |
| const char *root = NULL; |
| const char *null_config = NULL; |
| bool arg_is_modname = false; |
| int i, err; |
| |
| for (;;) { |
| int c, idx = 0; |
| c = getopt_long(argc, argv, cmdopts_s, cmdopts, &idx); |
| if (c == -1) |
| break; |
| switch (c) { |
| 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 '0': |
| separator = '\0'; |
| break; |
| case 'm': |
| arg_is_modname = true; |
| break; |
| case 'F': |
| field = optarg; |
| break; |
| case 'k': |
| kversion = optarg; |
| break; |
| case 'b': |
| root = optarg; |
| break; |
| case 'h': |
| help(); |
| return EXIT_SUCCESS; |
| case 'V': |
| puts(PACKAGE " version " VERSION); |
| puts(KMOD_FEATURES); |
| return EXIT_SUCCESS; |
| case '?': |
| return EXIT_FAILURE; |
| default: |
| ERR("unexpected getopt_long() value '%c'.\n", c); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| if (optind >= argc) { |
| ERR("missing module or filename.\n"); |
| return EXIT_FAILURE; |
| } |
| |
| if (root != NULL || kversion != NULL) { |
| struct utsname u; |
| if (root == NULL) |
| root = ""; |
| if (kversion == NULL) { |
| if (uname(&u) < 0) { |
| ERR("uname() failed: %m\n"); |
| return EXIT_FAILURE; |
| } |
| kversion = u.release; |
| } |
| snprintf(dirname_buf, sizeof(dirname_buf), "%s/lib/modules/%s", |
| root, kversion); |
| dirname = dirname_buf; |
| } |
| |
| ctx = kmod_new(dirname, &null_config); |
| if (!ctx) { |
| ERR("kmod_new() failed!\n"); |
| return EXIT_FAILURE; |
| } |
| |
| err = 0; |
| for (i = optind; i < argc; i++) { |
| const char *name = argv[i]; |
| int r; |
| |
| if (arg_is_modname) |
| r = modinfo_name_do(ctx, name); |
| else if (is_module_filename(name)) |
| r = modinfo_path_do(ctx, name); |
| else |
| r = modinfo_alias_do(ctx, name); |
| |
| if (r < 0) |
| err = r; |
| } |
| |
| kmod_unref(ctx); |
| return err >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; |
| } |
| |
| const struct kmod_cmd kmod_cmd_compat_modinfo = { |
| .name = "modinfo", |
| .cmd = do_modinfo, |
| .help = "compat modinfo command", |
| }; |