365 lines
7.1 KiB
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);
|
|
}
|