xref: /third_party/ltp/lib/tst_kconfig.c (revision f08c3bdf)
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (c) 2018 Cyril Hrubis <chrubis@suse.cz>
4 */
5
6#include <stdlib.h>
7#include <stdio.h>
8#include <string.h>
9#include <ctype.h>
10#include <sys/utsname.h>
11
12#define TST_NO_DEFAULT_MAIN
13#include "tst_test.h"
14#include "tst_private.h"
15#include "tst_kconfig.h"
16#include "tst_bool_expr.h"
17
18static int kconfig_skip_check(void)
19{
20	char *skipped = getenv("KCONFIG_SKIP_CHECK");
21
22	if (skipped) {
23		tst_res(TINFO, "Skipping kernel config check as requested");
24		return 1;
25	}
26
27	return 0;
28}
29
30static const char *kconfig_path(char *path_buf, size_t path_buf_len)
31{
32	const char *path = getenv("KCONFIG_PATH");
33	struct utsname un;
34
35	if (path) {
36		if (!access(path, F_OK))
37			return path;
38
39		tst_res(TWARN, "KCONFIG_PATH='%s' does not exist", path);
40	}
41
42	if (!access("/proc/config.gz", F_OK))
43		return "/proc/config.gz";
44
45	uname(&un);
46
47	/* Common install module path */
48	snprintf(path_buf, path_buf_len, "/lib/modules/%s/build/.config", un.release);
49
50	if (!access(path_buf, F_OK))
51		return path_buf;
52
53	snprintf(path_buf, path_buf_len, "/lib/modules/%s/config", un.release);
54
55	if (!access(path_buf, F_OK))
56		return path_buf;
57
58	/* Debian and derivatives */
59	snprintf(path_buf, path_buf_len, "/boot/config-%s", un.release);
60
61	if (!access(path_buf, F_OK))
62		return path_buf;
63
64	/* Clear Linux */
65	snprintf(path_buf, path_buf_len, "/lib/kernel/config-%s", un.release);
66
67	if (!access(path_buf, F_OK))
68		return path_buf;
69
70	tst_res(TINFO, "Couldn't locate kernel config!");
71
72	return NULL;
73}
74
75static char is_gzip;
76
77static FILE *open_kconfig(void)
78{
79	FILE *fp;
80	char buf[1064];
81	char path_buf[1024];
82	const char *path = kconfig_path(path_buf, sizeof(path_buf));
83
84	if (!path)
85		return NULL;
86
87	tst_res(TINFO, "Parsing kernel config '%s'", path);
88
89	is_gzip = !!strstr(path, ".gz");
90
91	if (is_gzip) {
92		snprintf(buf, sizeof(buf), "zcat '%s'", path);
93		fp = popen(buf, "r");
94	} else {
95		fp = fopen(path, "r");
96	}
97
98	if (!fp)
99		tst_brk(TBROK | TERRNO, "Failed to open '%s'", path);
100
101	return fp;
102}
103
104static void close_kconfig(FILE *fp)
105{
106	if (is_gzip)
107		pclose(fp);
108	else
109		fclose(fp);
110}
111
112static inline int kconfig_parse_line(const char *line,
113                                     struct tst_kconfig_var *vars,
114                                     unsigned int vars_len)
115{
116	unsigned int i, var_len = 0;
117	const char *var;
118	int is_not_set = 0;
119
120	while (isspace(*line))
121		line++;
122
123	if (*line == '#') {
124		if (!strstr(line, "is not set"))
125			return 0;
126
127		is_not_set = 1;
128	}
129
130	var = strstr(line, "CONFIG_");
131
132	if (!var)
133		return 0;
134
135	for (;;) {
136		switch (var[var_len]) {
137		case 'A' ... 'Z':
138		case '0' ... '9':
139		case '_':
140			var_len++;
141		break;
142		default:
143			goto out;
144		break;
145		}
146	}
147
148out:
149
150	for (i = 0; i < vars_len; i++) {
151		const char *val;
152		unsigned int val_len = 0;
153
154		if (vars[i].id_len != var_len)
155			continue;
156
157		if (strncmp(vars[i].id, var, var_len))
158			continue;
159
160		if (is_not_set) {
161			vars[i].choice = 'n';
162			return 1;
163		}
164
165		val = var + var_len;
166
167		while (isspace(*val))
168			val++;
169
170		if (*val != '=')
171			return 0;
172
173		val++;
174
175		while (isspace(*val))
176			val++;
177
178		while (!isspace(val[val_len]))
179			val_len++;
180
181		if (val_len == 1) {
182			switch (val[0]) {
183			case 'y':
184				vars[i].choice = 'y';
185				return 1;
186			case 'm':
187				vars[i].choice = 'm';
188				return 1;
189			}
190		}
191
192		vars[i].choice = 'v';
193		vars[i].val = strndup(val, val_len);
194	}
195
196	return 0;
197}
198
199void tst_kconfig_read(struct tst_kconfig_var vars[], size_t vars_len)
200{
201	char line[128];
202	unsigned int vars_found = 0;
203
204	FILE *fp = open_kconfig();
205	if (!fp)
206		tst_brk(TBROK, "Cannot parse kernel .config");
207
208	while (fgets(line, sizeof(line), fp)) {
209		if (kconfig_parse_line(line, vars, vars_len))
210			vars_found++;
211
212		if (vars_found == vars_len)
213			goto exit;
214	}
215
216exit:
217	close_kconfig(fp);
218}
219
220static size_t array_len(const char *const kconfigs[])
221{
222	size_t i = 0;
223
224	while (kconfigs[++i]);
225
226	return i;
227}
228
229static const char *strnchr(const char *s, int c, unsigned int len)
230{
231	unsigned int i;
232
233	for (i = 0; i < len; i++) {
234		if (s[i] == c)
235			return s + i;
236	}
237
238	return NULL;
239}
240
241static inline unsigned int get_len(const char* kconfig, unsigned int len)
242{
243	const char *sep = strnchr(kconfig, '=', len);
244
245	if (!sep)
246		return len;
247
248	return sep - kconfig;
249}
250
251static void print_err(FILE *f, const struct tst_expr_tok *var,
252                      size_t spaces, const char *err)
253{
254	size_t i;
255
256	for (i = 0; i < var->tok_len; i++)
257		fputc(var->tok[i], f);
258
259	fputc('\n', f);
260
261	while (spaces--)
262		fputc(' ', f);
263
264	fprintf(f, "^\n%s\n\n", err);
265}
266
267static int validate_var(const struct tst_expr_tok *var)
268{
269	size_t i = 7;
270
271	if (var->tok_len < 7 || strncmp(var->tok, "CONFIG_", 7)) {
272		print_err(stderr, var, 0, "Expected CONFIG_ prefix");
273		return 1;
274	}
275
276	while (var->tok[i]) {
277		char c;
278
279		if (i >= var->tok_len)
280			return 0;
281
282		c = var->tok[i];
283
284		if ((c >= 'A' && c <= 'Z') || c == '_') {
285			i++;
286			continue;
287		}
288
289		if (c >= '0' && c <= '9') {
290			i++;
291			continue;
292		}
293
294		if (c == '=') {
295			i++;
296			break;
297		}
298
299		print_err(stderr, var, i, "Unexpected character in variable name");
300		return 1;
301	}
302
303	if (i >= var->tok_len) {
304
305		if (var->tok[i-1] == '=') {
306			print_err(stderr, var, i, "Missing value");
307			return -1;
308		}
309
310		return 0;
311	}
312
313	if (var->tok[i] == '"') {
314		do {
315			i++;
316		} while (i < var->tok_len && var->tok[i] != '"');
317
318		if (i < var->tok_len - 1) {
319			print_err(stderr, var, i, "Garbage after a string");
320			return 1;
321		}
322
323		if (var->tok[i] != '"') {
324			print_err(stderr, var, i, "Untermianted string");
325			return 1;
326		}
327
328		return 0;
329	}
330
331	do {
332		i++;
333	} while (i < var->tok_len && isalnum(var->tok[i]));
334
335	if (i < var->tok_len) {
336		print_err(stderr, var, i, "Invalid character in variable value");
337		return 1;
338	}
339
340	return 0;
341}
342
343static int validate_vars(struct tst_expr *const exprs[], unsigned int expr_cnt)
344{
345	unsigned int i;
346	const struct tst_expr_tok *j;
347	unsigned int ret = 0;
348
349	for (i = 0; i < expr_cnt; i++) {
350		for (j = exprs[i]->rpn; j; j = j->next) {
351			if (j->op == TST_OP_VAR)
352				ret |= validate_var(j);
353		}
354	}
355
356	return ret;
357}
358
359
360static inline unsigned int get_var_cnt(struct tst_expr *const exprs[],
361                                       unsigned int expr_cnt)
362{
363	unsigned int i;
364	const struct tst_expr_tok *j;
365	unsigned int cnt = 0;
366
367	for (i = 0; i < expr_cnt; i++) {
368		for (j = exprs[i]->rpn; j; j = j->next) {
369			if (j->op == TST_OP_VAR)
370				cnt++;
371		}
372	}
373
374	return cnt;
375}
376
377static const struct tst_kconfig_var *find_var(const struct tst_kconfig_var vars[],
378                                        unsigned int var_cnt,
379                                        const char *var)
380{
381	unsigned int i;
382
383	for (i = 0; i < var_cnt; i++) {
384		if (!strcmp(vars[i].id, var))
385			return &vars[i];
386	}
387
388	return NULL;
389}
390
391/*
392 * Fill in the kconfig variables array from the expressions. Also makes sure
393 * that each variable is copied to the array exaclty once.
394 */
395static inline unsigned int populate_vars(struct tst_expr *exprs[],
396                                         unsigned int expr_cnt,
397                                    struct tst_kconfig_var vars[])
398{
399	unsigned int i;
400	struct tst_expr_tok *j;
401	unsigned int cnt = 0;
402
403	for (i = 0; i < expr_cnt; i++) {
404		for (j = exprs[i]->rpn; j; j = j->next) {
405			const struct tst_kconfig_var *var;
406
407			if (j->op != TST_OP_VAR)
408				continue;
409
410			vars[cnt].id_len = get_len(j->tok, j->tok_len);
411
412			if (vars[cnt].id_len + 1 >= sizeof(vars[cnt].id))
413				tst_brk(TBROK, "kconfig var id too long!");
414
415			strncpy(vars[cnt].id, j->tok, vars[cnt].id_len);
416			vars[cnt].id[vars[cnt].id_len] = 0;
417			vars[cnt].choice = 0;
418			vars[cnt].val = NULL;
419
420			var = find_var(vars, cnt, vars[cnt].id);
421
422			if (var)
423				j->priv = var;
424			else
425				j->priv = &vars[cnt++];
426		}
427	}
428
429	return cnt;
430}
431
432static int map(struct tst_expr_tok *expr)
433{
434	const struct tst_kconfig_var *var = expr->priv;
435
436	if (var->choice == 0)
437		return 0;
438
439	const char *val = strnchr(expr->tok, '=', expr->tok_len);
440
441	/* CONFIG_FOO evaluates to true if y or m */
442	if (!val)
443		return var->choice == 'y' || var->choice == 'm';
444
445	val++;
446
447	unsigned int len = expr->tok_len - (val - expr->tok);
448	char choice = 'v';
449
450	if (!strncmp(val, "n", len))
451		choice = 'n';
452
453	if (!strncmp(val, "y", len))
454		choice = 'y';
455
456	if (!strncmp(val, "m", len))
457		choice = 'm';
458
459	if (choice != 'v')
460		return var->choice == choice;
461
462	if (var->choice != 'v')
463		return 0;
464
465	if (strlen(var->val) != len)
466		return 0;
467
468	return !strncmp(val, var->val, len);
469}
470
471static void dump_vars(const struct tst_expr *expr)
472{
473	const struct tst_expr_tok *i;
474	const struct tst_kconfig_var *var;
475
476	tst_res(TINFO, "Variables:");
477
478	for (i = expr->rpn; i; i = i->next) {
479		if (i->op != TST_OP_VAR)
480			continue;
481
482		var = i->priv;
483
484		if (!var->choice) {
485			tst_res(TINFO, " %s Undefined", var->id);
486			continue;
487		}
488
489		if (var->choice == 'v') {
490			tst_res(TINFO, " %s=%s", var->id, var->val);
491			continue;
492		}
493
494		tst_res(TINFO, " %s=%c", var->id, var->choice);
495	}
496}
497
498int tst_kconfig_check(const char *const kconfigs[])
499{
500	size_t expr_cnt = array_len(kconfigs);
501	struct tst_expr *exprs[expr_cnt];
502	unsigned int i, var_cnt;
503	int ret = 0;
504
505	if (kconfig_skip_check())
506		return 0;
507
508	for (i = 0; i < expr_cnt; i++) {
509		exprs[i] = tst_bool_expr_parse(kconfigs[i]);
510
511		if (!exprs[i])
512			tst_brk(TBROK, "Invalid kconfig expression!");
513	}
514
515	if (validate_vars(exprs, expr_cnt))
516		tst_brk(TBROK, "Invalid kconfig variables!");
517
518	var_cnt = get_var_cnt(exprs, expr_cnt);
519	struct tst_kconfig_var vars[var_cnt];
520
521	var_cnt = populate_vars(exprs, expr_cnt, vars);
522
523	tst_kconfig_read(vars, var_cnt);
524
525	for (i = 0; i < expr_cnt; i++) {
526		int val = tst_bool_expr_eval(exprs[i], map);
527
528		if (val != 1) {
529			ret = 1;
530			tst_res(TINFO, "Constraint '%s' not satisfied!", kconfigs[i]);
531			dump_vars(exprs[i]);
532		}
533
534		tst_bool_expr_free(exprs[i]);
535	}
536
537	for (i = 0; i < var_cnt; i++) {
538		if (vars[i].choice == 'v')
539			free(vars[i].val);
540	}
541
542	return ret;
543}
544
545char tst_kconfig_get(const char *confname)
546{
547	struct tst_kconfig_var var;
548
549	if (kconfig_skip_check())
550		return 0;
551
552	var.id_len = strlen(confname);
553
554	if (var.id_len >= sizeof(var.id))
555		tst_brk(TBROK, "Kconfig var name \"%s\" too long", confname);
556
557	strcpy(var.id, confname);
558	var.choice = 0;
559	var.val = NULL;
560
561	tst_kconfig_read(&var, 1);
562
563	if (var.choice == 'v')
564		free(var.val);
565
566	return var.choice;
567}
568