536 lines
9.5 KiB
C
536 lines
9.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) 2018 Cyril Hrubis <chrubis@suse.cz>
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <sys/utsname.h>
|
|
|
|
#define TST_NO_DEFAULT_MAIN
|
|
#include "tst_test.h"
|
|
#include "tst_private.h"
|
|
#include "tst_kconfig.h"
|
|
#include "tst_bool_expr.h"
|
|
|
|
static const char *kconfig_path(char *path_buf, size_t path_buf_len)
|
|
{
|
|
const char *path = getenv("KCONFIG_PATH");
|
|
struct utsname un;
|
|
|
|
if (path) {
|
|
if (!access(path, F_OK))
|
|
return path;
|
|
|
|
tst_res(TWARN, "KCONFIG_PATH='%s' does not exist", path);
|
|
}
|
|
|
|
if (!access("/proc/config.gz", F_OK))
|
|
return "/proc/config.gz";
|
|
|
|
uname(&un);
|
|
|
|
/* Debian and derivatives */
|
|
snprintf(path_buf, path_buf_len, "/boot/config-%s", un.release);
|
|
|
|
if (!access(path_buf, F_OK))
|
|
return path_buf;
|
|
|
|
/* Clear Linux */
|
|
snprintf(path_buf, path_buf_len, "/lib/kernel/config-%s", un.release);
|
|
|
|
if (!access(path_buf, F_OK))
|
|
return path_buf;
|
|
|
|
tst_res(TINFO, "Couldn't locate kernel config!");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static char is_gzip;
|
|
|
|
static FILE *open_kconfig(void)
|
|
{
|
|
FILE *fp;
|
|
char buf[1064];
|
|
char path_buf[1024];
|
|
const char *path = kconfig_path(path_buf, sizeof(path_buf));
|
|
|
|
if (!path)
|
|
return NULL;
|
|
|
|
tst_res(TINFO, "Parsing kernel config '%s'", path);
|
|
|
|
is_gzip = !!strstr(path, ".gz");
|
|
|
|
if (is_gzip) {
|
|
snprintf(buf, sizeof(buf), "zcat '%s'", path);
|
|
fp = popen(buf, "r");
|
|
} else {
|
|
fp = fopen(path, "r");
|
|
}
|
|
|
|
if (!fp)
|
|
tst_brk(TBROK | TERRNO, "Failed to open '%s'", path);
|
|
|
|
return fp;
|
|
}
|
|
|
|
static void close_kconfig(FILE *fp)
|
|
{
|
|
if (is_gzip)
|
|
pclose(fp);
|
|
else
|
|
fclose(fp);
|
|
}
|
|
|
|
static inline int kconfig_parse_line(const char *line,
|
|
struct tst_kconfig_var *vars,
|
|
unsigned int vars_len)
|
|
{
|
|
unsigned int i, var_len = 0;
|
|
const char *var;
|
|
int is_not_set = 0;
|
|
|
|
while (isspace(*line))
|
|
line++;
|
|
|
|
if (*line == '#') {
|
|
if (!strstr(line, "is not set"))
|
|
return 0;
|
|
|
|
is_not_set = 1;
|
|
}
|
|
|
|
var = strstr(line, "CONFIG_");
|
|
|
|
if (!var)
|
|
return 0;
|
|
|
|
for (;;) {
|
|
switch (var[var_len]) {
|
|
case 'A' ... 'Z':
|
|
case '0' ... '9':
|
|
case '_':
|
|
var_len++;
|
|
break;
|
|
default:
|
|
goto out;
|
|
break;
|
|
}
|
|
}
|
|
|
|
out:
|
|
|
|
for (i = 0; i < vars_len; i++) {
|
|
const char *val;
|
|
unsigned int val_len = 0;
|
|
|
|
if (vars[i].id_len != var_len)
|
|
continue;
|
|
|
|
if (strncmp(vars[i].id, var, var_len))
|
|
continue;
|
|
|
|
if (is_not_set) {
|
|
vars[i].choice = 'n';
|
|
return 1;
|
|
}
|
|
|
|
val = var + var_len;
|
|
|
|
while (isspace(*val))
|
|
val++;
|
|
|
|
if (*val != '=')
|
|
return 0;
|
|
|
|
val++;
|
|
|
|
while (isspace(*val))
|
|
val++;
|
|
|
|
while (!isspace(val[val_len]))
|
|
val_len++;
|
|
|
|
if (val_len == 1) {
|
|
switch (val[0]) {
|
|
case 'y':
|
|
vars[i].choice = 'y';
|
|
return 1;
|
|
case 'm':
|
|
vars[i].choice = 'm';
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
vars[i].choice = 'v';
|
|
vars[i].val = strndup(val, val_len);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void tst_kconfig_read(struct tst_kconfig_var vars[], size_t vars_len)
|
|
{
|
|
char line[128];
|
|
unsigned int vars_found = 0;
|
|
|
|
FILE *fp = open_kconfig();
|
|
if (!fp)
|
|
tst_brk(TBROK, "Cannot parse kernel .config");
|
|
|
|
while (fgets(line, sizeof(line), fp)) {
|
|
if (kconfig_parse_line(line, vars, vars_len))
|
|
vars_found++;
|
|
|
|
if (vars_found == vars_len)
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
close_kconfig(fp);
|
|
}
|
|
|
|
static size_t array_len(const char *const kconfigs[])
|
|
{
|
|
size_t i = 0;
|
|
|
|
while (kconfigs[++i]);
|
|
|
|
return i;
|
|
}
|
|
|
|
static const char *strnchr(const char *s, int c, unsigned int len)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
if (s[i] == c)
|
|
return s + i;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline unsigned int get_len(const char* kconfig, unsigned int len)
|
|
{
|
|
const char *sep = strnchr(kconfig, '=', len);
|
|
|
|
if (!sep)
|
|
return len;
|
|
|
|
return sep - kconfig;
|
|
}
|
|
|
|
static void print_err(FILE *f, const struct tst_expr_tok *var,
|
|
size_t spaces, const char *err)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < var->tok_len; i++)
|
|
fputc(var->tok[i], f);
|
|
|
|
fputc('\n', f);
|
|
|
|
while (spaces--)
|
|
fputc(' ', f);
|
|
|
|
fprintf(f, "^\n%s\n\n", err);
|
|
}
|
|
|
|
static int validate_var(const struct tst_expr_tok *var)
|
|
{
|
|
size_t i = 7;
|
|
|
|
if (var->tok_len < 7 || strncmp(var->tok, "CONFIG_", 7)) {
|
|
print_err(stderr, var, 0, "Expected CONFIG_ prefix");
|
|
return 1;
|
|
}
|
|
|
|
while (var->tok[i]) {
|
|
char c;
|
|
|
|
if (i >= var->tok_len)
|
|
return 0;
|
|
|
|
c = var->tok[i];
|
|
|
|
if ((c >= 'A' && c <= 'Z') || c == '_') {
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
if (c >= '0' && c <= '9') {
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
if (c == '=') {
|
|
i++;
|
|
break;
|
|
}
|
|
|
|
print_err(stderr, var, i, "Unexpected character in variable name");
|
|
return 1;
|
|
}
|
|
|
|
if (i >= var->tok_len) {
|
|
|
|
if (var->tok[i-1] == '=') {
|
|
print_err(stderr, var, i, "Missing value");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (var->tok[i] == '"') {
|
|
do {
|
|
i++;
|
|
} while (i < var->tok_len && var->tok[i] != '"');
|
|
|
|
if (i < var->tok_len - 1) {
|
|
print_err(stderr, var, i, "Garbage after a string");
|
|
return 1;
|
|
}
|
|
|
|
if (var->tok[i] != '"') {
|
|
print_err(stderr, var, i, "Untermianted string");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
do {
|
|
i++;
|
|
} while (i < var->tok_len && isalnum(var->tok[i]));
|
|
|
|
if (i < var->tok_len) {
|
|
print_err(stderr, var, i, "Invalid character in variable value");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int validate_vars(struct tst_expr *const exprs[], unsigned int expr_cnt)
|
|
{
|
|
unsigned int i;
|
|
const struct tst_expr_tok *j;
|
|
unsigned int ret = 0;
|
|
|
|
for (i = 0; i < expr_cnt; i++) {
|
|
for (j = exprs[i]->rpn; j; j = j->next) {
|
|
if (j->op == TST_OP_VAR)
|
|
ret |= validate_var(j);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static inline unsigned int get_var_cnt(struct tst_expr *const exprs[],
|
|
unsigned int expr_cnt)
|
|
{
|
|
unsigned int i;
|
|
const struct tst_expr_tok *j;
|
|
unsigned int cnt = 0;
|
|
|
|
for (i = 0; i < expr_cnt; i++) {
|
|
for (j = exprs[i]->rpn; j; j = j->next) {
|
|
if (j->op == TST_OP_VAR)
|
|
cnt++;
|
|
}
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
static const struct tst_kconfig_var *find_var(const struct tst_kconfig_var vars[],
|
|
unsigned int var_cnt,
|
|
const char *var)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < var_cnt; i++) {
|
|
if (!strcmp(vars[i].id, var))
|
|
return &vars[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Fill in the kconfig variables array from the expressions. Also makes sure
|
|
* that each variable is copied to the array exaclty once.
|
|
*/
|
|
static inline unsigned int populate_vars(struct tst_expr *exprs[],
|
|
unsigned int expr_cnt,
|
|
struct tst_kconfig_var vars[])
|
|
{
|
|
unsigned int i;
|
|
struct tst_expr_tok *j;
|
|
unsigned int cnt = 0;
|
|
|
|
for (i = 0; i < expr_cnt; i++) {
|
|
for (j = exprs[i]->rpn; j; j = j->next) {
|
|
const struct tst_kconfig_var *var;
|
|
|
|
if (j->op != TST_OP_VAR)
|
|
continue;
|
|
|
|
vars[cnt].id_len = get_len(j->tok, j->tok_len);
|
|
|
|
if (vars[cnt].id_len + 1 >= sizeof(vars[cnt].id))
|
|
tst_brk(TBROK, "kconfig var id too long!");
|
|
|
|
strncpy(vars[cnt].id, j->tok, vars[cnt].id_len);
|
|
vars[cnt].id[vars[cnt].id_len] = 0;
|
|
vars[cnt].choice = 0;
|
|
|
|
var = find_var(vars, cnt, vars[cnt].id);
|
|
|
|
if (var)
|
|
j->priv = var;
|
|
else
|
|
j->priv = &vars[cnt++];
|
|
}
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
static int map(struct tst_expr_tok *expr)
|
|
{
|
|
const struct tst_kconfig_var *var = expr->priv;
|
|
|
|
if (var->choice == 0)
|
|
return 0;
|
|
|
|
const char *val = strnchr(expr->tok, '=', expr->tok_len);
|
|
|
|
/* CONFIG_FOO evaluates to true if y or m */
|
|
if (!val)
|
|
return var->choice == 'y' || var->choice == 'm';
|
|
|
|
val++;
|
|
|
|
unsigned int len = expr->tok_len - (val - expr->tok);
|
|
char choice = 'v';
|
|
|
|
if (!strncmp(val, "n", len))
|
|
choice = 'n';
|
|
|
|
if (!strncmp(val, "y", len))
|
|
choice = 'y';
|
|
|
|
if (!strncmp(val, "m", len))
|
|
choice = 'm';
|
|
|
|
if (choice != 'v')
|
|
return var->choice == choice;
|
|
|
|
if (strlen(var->val) != len)
|
|
return 0;
|
|
|
|
return !strncmp(val, var->val, len);
|
|
}
|
|
|
|
static void dump_vars(const struct tst_expr *expr)
|
|
{
|
|
const struct tst_expr_tok *i;
|
|
const struct tst_kconfig_var *var;
|
|
|
|
tst_res(TINFO, "Variables:");
|
|
|
|
for (i = expr->rpn; i; i = i->next) {
|
|
if (i->op != TST_OP_VAR)
|
|
continue;
|
|
|
|
var = i->priv;
|
|
|
|
if (!var->choice) {
|
|
tst_res(TINFO, " %s Undefined", var->id);
|
|
continue;
|
|
}
|
|
|
|
if (var->choice == 'v') {
|
|
tst_res(TINFO, " %s=%s", var->id, var->val);
|
|
continue;
|
|
}
|
|
|
|
tst_res(TINFO, " %s=%c", var->id, var->choice);
|
|
}
|
|
}
|
|
|
|
void tst_kconfig_check(const char *const kconfigs[])
|
|
{
|
|
size_t expr_cnt = array_len(kconfigs);
|
|
struct tst_expr *exprs[expr_cnt];
|
|
unsigned int i, var_cnt;
|
|
int abort_test = 0;
|
|
|
|
for (i = 0; i < expr_cnt; i++) {
|
|
exprs[i] = tst_bool_expr_parse(kconfigs[i]);
|
|
|
|
if (!exprs[i])
|
|
tst_brk(TBROK, "Invalid kconfig expression!");
|
|
}
|
|
|
|
if (validate_vars(exprs, expr_cnt))
|
|
tst_brk(TBROK, "Invalid kconfig variables!");
|
|
|
|
var_cnt = get_var_cnt(exprs, expr_cnt);
|
|
struct tst_kconfig_var vars[var_cnt];
|
|
|
|
var_cnt = populate_vars(exprs, expr_cnt, vars);
|
|
|
|
tst_kconfig_read(vars, var_cnt);
|
|
|
|
for (i = 0; i < expr_cnt; i++) {
|
|
int val = tst_bool_expr_eval(exprs[i], map);
|
|
|
|
if (val != 1) {
|
|
abort_test = 1;
|
|
tst_res(TINFO, "Constrain '%s' not satisfied!", kconfigs[i]);
|
|
dump_vars(exprs[i]);
|
|
}
|
|
|
|
tst_bool_expr_free(exprs[i]);
|
|
}
|
|
|
|
for (i = 0; i < var_cnt; i++) {
|
|
if (vars[i].choice == 'v')
|
|
free(vars[i].val);
|
|
}
|
|
|
|
if (abort_test)
|
|
tst_brk(TCONF, "Aborting due to unsuitable kernel config, see above!");
|
|
}
|
|
|
|
char tst_kconfig_get(const char *confname)
|
|
{
|
|
struct tst_kconfig_var var;
|
|
|
|
var.id_len = strlen(confname);
|
|
|
|
if (var.id_len >= sizeof(var.id))
|
|
tst_brk(TBROK, "Kconfig var name \"%s\" too long", confname);
|
|
|
|
strcpy(var.id, confname);
|
|
var.choice = 0;
|
|
var.val = NULL;
|
|
|
|
tst_kconfig_read(&var, 1);
|
|
|
|
if (var.choice == 'v')
|
|
free(var.val);
|
|
|
|
return var.choice;
|
|
}
|