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