| // 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[1024]; |
| 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!"); |
| } |