libevl/utils/evl-check.c

365 lines
7.1 KiB
C

/*
* SPDX-License-Identifier: MIT
*
* Copyright (C) 2020 Philippe Gerum <rpm@xenomai.org>
*/
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <search.h>
#include <error.h>
#include <errno.h>
#include <sys/utsname.h>
#define DEFAULT_KCONFIG "/proc/config.gz"
#define DEFAULT_CHECKLIST "kconf-checklist.evl"
#define short_optlist "@hqf:L:a:H:"
static int hash_size = 16384;
static bool quiet;
static const struct option options[] = {
{
.name = "file",
.has_arg = required_argument,
.val = 'f',
},
{
.name = "check-list",
.has_arg = required_argument,
.val = 'L',
},
{
.name = "arch",
.has_arg = required_argument,
.val = 'a',
},
{
.name = "hash-size",
.has_arg = required_argument,
.val = 'H',
},
{
.name = "quiet",
.has_arg = no_argument,
.val = 'q',
},
{
.name = "help",
.has_arg = no_argument,
.val = 'h',
},
{ /* Sentinel */ }
};
static char *hash_config(FILE *kconfp)
{
char buf[BUFSIZ], *sym, *val, *arch = NULL;
ENTRY entry, *e;
int ret;
ret = hcreate(hash_size);
if (!ret)
error(1, errno, "hcreate(%d)", hash_size);
while (fgets(buf, sizeof(buf), kconfp)) {
if (*buf == '#') {
sscanf(buf, "# Linux/%m[^ ]", &arch);
continue;
}
ret = sscanf(buf, "%m[^=]=%ms\n", &sym, &val);
if (ret != 2)
continue;
if (strncmp(sym, "CONFIG_", 7))
continue;
if (strcmp(val, "y") && strcmp(val, "m"))
continue;
entry.key = sym;
entry.data = NULL;
e = hsearch(entry, FIND);
if (e)
continue; /* uhh? */
entry.data = val;
if (!hsearch(entry, ENTER))
error(1, ENOMEM, "h-table full -- try -H");
}
return arch;
}
static char *next_token(char **next)
{
char *p = *next, *s;
for (;;) {
if (!*p) {
*next = p;
return strdup("");
}
if (!isspace(*p))
break;
p++;
}
s = p;
if (!isalnum(*p) && *p != '_') {
*next = p + 1;
return strndup(s, 1);
}
do {
if (!isalnum(*p) && *p != '_') {
*next = p;
return strndup(s, p - s);
}
} while (*++p);
*next = p;
return strdup(s);
}
static const char *get_arch_alias(const char *arch)
{
if (!strcmp(arch, "arm64"))
return "aarch64";
if (!strcmp(arch, "arm"))
return "aarch32";
return arch;
}
static int apply_checklist(FILE *checkfp, const char *cpuarch)
{
char buf[BUFSIZ], *token, *next, *sym, *val;
int lineno = 0, failed = 0;
bool not, notcond;
const char *arch;
ENTRY entry, *e;
while (fgets(buf, sizeof(buf), checkfp)) {
lineno++;
next = buf;
token = next_token(&next);
if (!*token || !strcmp(token, "#")) {
free(token);
continue;
}
not = *token == '!';
if (not) {
free(token);
token = next_token(&next);
}
sym = token;
if (strncmp(sym, "CONFIG_", 7))
error(1, EINVAL,
"invalid check list symbol '%s' at line %d",
sym, lineno);
token = next_token(&next);
val = NULL;
if (*token == '=') {
free(token);
val = next_token(&next);
token = next_token(&next);
}
if (!strcmp(token, "if")) {
free(token);
token = next_token(&next);
notcond = *token == '!';
if (notcond) {
free(token);
token = next_token(&next);
}
if (strncmp(token, "CONFIG_", 7))
error(1, EINVAL,
"invalid condition symbol '%s' at line %d",
token, lineno);
entry.key = token;
entry.data = NULL;
e = hsearch(entry, FIND);
free(token);
if (!((e && !notcond) || (!e && notcond)))
continue;
token = next_token(&next);
}
if (!strcmp(token, "on")) {
free(token);
token = next_token(&next);
arch = get_arch_alias(token);
if (strncmp(cpuarch, arch, strlen(arch))) {
free(token);
continue;
}
}
free(token);
entry.key = sym;
entry.data = NULL;
e = hsearch(entry, FIND);
if (val && !strcmp(val, "n"))
not = !not;
if (e && (not || (val && strcmp(val, e->data)))) {
if (!quiet)
printf("%s=%s\n", sym, (const char *)e->data);
failed++;
} else if (!e && !not) {
if (!quiet)
printf("%s=n\n", sym);
failed++;
}
free(sym);
if (val)
free(val);
}
return failed;
}
static void usage(char *arg0)
{
fprintf(stderr, "usage: %s [options]:\n", basename(arg0));
fprintf(stderr, "-f --file=<.config> Kconfig file to check [=/proc/config.gz]\n");
fprintf(stderr, "-L --check-list=<file> configuration check list [=$libexec/kconf-checklist.evl]\n");
fprintf(stderr, "-a --arch=<cpuarch> CPU architecture assumed\n");
fprintf(stderr, "-H --hash-size=<N> set the hash table size [=16384]\n");
fprintf(stderr, "-q --quiet suppress output\n");
fprintf(stderr, "-h --help this help\n");
}
int main(int argc, char *const argv[])
{
const char *kconfig = DEFAULT_KCONFIG, *check_list = NULL,
*defarch, *arch = NULL, *p;
FILE *kconfp, *checkfp;
char *checklist_path;
struct utsname ubuf;
int c, ret;
char *cmd;
opterr = 0;
for (;;) {
c = getopt_long(argc, argv, short_optlist, options, NULL);
if (c == EOF)
break;
switch (c) {
case 0:
break;
case 'f':
kconfig = optarg;
break;
case 'L':
check_list = optarg;
break;
case 'a':
arch = optarg;
break;
case 'H':
hash_size = atoi(optarg);
if (hash_size < 16384)
hash_size = 16384;
break;
case 'q':
quiet = true;
break;
case 'h':
usage(argv[0]);
return 0;
case '@':
printf("check kernel configuration\n");
return 0;
default:
usage(argv[0]);
return 1;
}
}
if (optind < argc) {
usage(argv[0]);
return 1;
}
/*
* We may be given a gzipped input file. Finding gunzip on a
* minimalist rootfs (e.g. busybox) may be more likely than
* having the zlib development files available from a common
* cross-toolchain. So go for popen-ing gunzip on the target
* instead of depending on libz on the development host.
*/
if (!strcmp(kconfig, "-")) {
kconfp = stdin;
} else {
p = strstr(kconfig, ".gz");
if (!p || strcmp(p, ".gz")) {
kconfp = fopen(kconfig, "r");
} else {
if (access(kconfig, R_OK))
error(1, errno, "cannot access %s%s",
kconfig,
strcmp(kconfig, DEFAULT_KCONFIG) ? "" :
"\n(you need CONFIG_IKCONFIG_PROC enabled)");
ret = asprintf(&cmd, "gunzip -c %s", kconfig);
if (ret < 0)
error(1, ENOMEM, "asprintf()");
kconfp = popen(cmd, "r");
free(cmd);
}
if (kconfp == NULL)
error(1, errno, "cannot open %s for reading", kconfig);
}
defarch = hash_config(kconfp);
if (check_list == NULL) {
p = getenv("EVL_CMDDIR");
if (p == NULL)
error(1, EINVAL, "cannot find check list -- Try -L");
ret = asprintf(&checklist_path, "%s/%s", p, DEFAULT_CHECKLIST);
if (ret < 0)
error(1, ENOMEM, "asprintf()");
check_list = checklist_path;
}
if (access(check_list, R_OK))
error(1, errno, "cannot access %s", check_list);
checkfp = fopen(check_list, "r");
if (checkfp == NULL)
error(1, errno, "cannot open %s for reading", check_list);
if (arch == NULL) {
if (defarch) {
arch = get_arch_alias(defarch);
} else {
ret = uname(&ubuf);
if (ret)
error(1, errno, "utsname()");
arch = ubuf.machine;
}
} else {
arch = get_arch_alias(arch);
}
return apply_checklist(checkfp, arch);
}