diff options
Diffstat (limited to 'src/kernel/tests/lib/tst_kconfig.c')
-rw-r--r-- | src/kernel/tests/lib/tst_kconfig.c | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/src/kernel/tests/lib/tst_kconfig.c b/src/kernel/tests/lib/tst_kconfig.c new file mode 100644 index 0000000..d49187b --- /dev/null +++ b/src/kernel/tests/lib/tst_kconfig.c @@ -0,0 +1,285 @@ +// 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_kconfig.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); +} + +struct match { + /* match len, string length up to \0 or = */ + size_t len; + /* if set part of conf string after = */ + const char *val; + /* if set the config option was matched already */ + int match; +}; + +static int is_set(const char *str, const char *val) +{ + size_t vlen = strlen(val); + + while (isspace(*str)) + str++; + + if (strncmp(str, val, vlen)) + return 0; + + switch (str[vlen]) { + case ' ': + case '\n': + case '\0': + return 1; + break; + default: + return 0; + } +} + +static inline int match(struct match *match, const char *conf, + struct tst_kconfig_res *result, const char *line) +{ + if (match->match) + return 0; + + const char *cfg = strstr(line, "CONFIG_"); + + if (!cfg) + return 0; + + if (strncmp(cfg, conf, match->len)) + return 0; + + const char *val = &cfg[match->len]; + + switch (cfg[match->len]) { + case '=': + break; + case ' ': + if (is_set(val, "is not set")) { + result->match = 'n'; + goto match; + } + /* fall through */ + default: + return 0; + } + + if (is_set(val, "=y")) { + result->match = 'y'; + goto match; + } + + if (is_set(val, "=m")) { + result->match = 'm'; + goto match; + } + + result->match = 'v'; + result->value = strndup(val+1, strlen(val)-2); + +match: + match->match = 1; + return 1; +} + +void tst_kconfig_read(const char *const *kconfigs, + struct tst_kconfig_res results[], size_t cnt) +{ + struct match matches[cnt]; + FILE *fp; + unsigned int i, j; + char buf[1024]; + + for (i = 0; i < cnt; i++) { + const char *val = strchr(kconfigs[i], '='); + + if (strncmp("CONFIG_", kconfigs[i], 7)) + tst_brk(TBROK, "Invalid config string '%s'", kconfigs[i]); + + matches[i].match = 0; + matches[i].len = strlen(kconfigs[i]); + + if (val) { + matches[i].val = val + 1; + matches[i].len -= strlen(val); + } + + results[i].match = 0; + results[i].value = NULL; + } + + fp = open_kconfig(); + if (!fp) + tst_brk(TBROK, "Cannot parse kernel .config"); + + while (fgets(buf, sizeof(buf), fp)) { + for (i = 0; i < cnt; i++) { + if (match(&matches[i], kconfigs[i], &results[i], buf)) { + for (j = 0; j < cnt; j++) { + if (matches[j].match) + break; + } + + if (j == cnt) + 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 int compare_res(struct tst_kconfig_res *res, const char *kconfig, + char match, const char *val) +{ + if (res->match != match) { + tst_res(TINFO, "Needs kernel %s, have %c", kconfig, res->match); + return 1; + } + + if (match != 'v') + return 0; + + if (strcmp(res->value, val)) { + tst_res(TINFO, "Needs kernel %s, have %s", kconfig, res->value); + return 1; + } + + return 0; +} + +void tst_kconfig_check(const char *const kconfigs[]) +{ + size_t cnt = array_len(kconfigs); + struct tst_kconfig_res results[cnt]; + unsigned int i; + int abort_test = 0; + + tst_kconfig_read(kconfigs, results, cnt); + + for (i = 0; i < cnt; i++) { + if (results[i].match == 0) { + tst_res(TINFO, "Missing kernel %s", kconfigs[i]); + abort_test = 1; + continue; + } + + if (results[i].match == 'n') { + tst_res(TINFO, "Kernel %s is not set", kconfigs[i]); + abort_test = 1; + continue; + } + + const char *val = strchr(kconfigs[i], '='); + + if (val) { + char match = 'v'; + val++; + + if (!strcmp(val, "y")) + match = 'y'; + + if (!strcmp(val, "m")) + match = 'm'; + + if (compare_res(&results[i], kconfigs[i], match, val)) + abort_test = 1; + + } + + free(results[i].value); + } + + if (abort_test) + tst_brk(TCONF, "Aborting due to unsuitable kernel config, see above!"); +} |