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_kconfig.h"
15
kconfig_path(char * path_buf,size_t path_buf_len)16 static const char *kconfig_path(char *path_buf, size_t path_buf_len)
17 {
18 const char *path = getenv("KCONFIG_PATH");
19 struct utsname un;
20
21 if (path) {
22 if (!access(path, F_OK))
23 return path;
24
25 tst_res(TWARN, "KCONFIG_PATH='%s' does not exist", path);
26 }
27
28 if (!access("/proc/config.gz", F_OK))
29 return "/proc/config.gz";
30
31 uname(&un);
32
33 /* Debian and derivatives */
34 snprintf(path_buf, path_buf_len, "/boot/config-%s", un.release);
35
36 if (!access(path_buf, F_OK))
37 return path_buf;
38
39 /* Clear Linux */
40 snprintf(path_buf, path_buf_len, "/lib/kernel/config-%s", un.release);
41
42 if (!access(path_buf, F_OK))
43 return path_buf;
44
45 tst_res(TINFO, "Couldn't locate kernel config!");
46
47 return NULL;
48 }
49
50 static char is_gzip;
51
open_kconfig(void)52 static FILE *open_kconfig(void)
53 {
54 FILE *fp;
55 char buf[1024];
56 char path_buf[1024];
57 const char *path = kconfig_path(path_buf, sizeof(path_buf));
58
59 if (!path)
60 return NULL;
61
62 tst_res(TINFO, "Parsing kernel config '%s'", path);
63
64 is_gzip = !!strstr(path, ".gz");
65
66 if (is_gzip) {
67 snprintf(buf, sizeof(buf), "zcat '%s'", path);
68 fp = popen(buf, "r");
69 } else {
70 fp = fopen(path, "r");
71 }
72
73 if (!fp)
74 tst_brk(TBROK | TERRNO, "Failed to open '%s'", path);
75
76 return fp;
77 }
78
close_kconfig(FILE * fp)79 static void close_kconfig(FILE *fp)
80 {
81 if (is_gzip)
82 pclose(fp);
83 else
84 fclose(fp);
85 }
86
87 struct match {
88 /* match len, string length up to \0 or = */
89 size_t len;
90 /* if set part of conf string after = */
91 const char *val;
92 /* if set the config option was matched already */
93 int match;
94 };
95
is_set(const char * str,const char * val)96 static int is_set(const char *str, const char *val)
97 {
98 size_t vlen = strlen(val);
99
100 while (isspace(*str))
101 str++;
102
103 if (strncmp(str, val, vlen))
104 return 0;
105
106 switch (str[vlen]) {
107 case ' ':
108 case '\n':
109 case '\0':
110 return 1;
111 break;
112 default:
113 return 0;
114 }
115 }
116
match(struct match * match,const char * conf,struct tst_kconfig_res * result,const char * line)117 static inline int match(struct match *match, const char *conf,
118 struct tst_kconfig_res *result, const char *line)
119 {
120 if (match->match)
121 return 0;
122
123 const char *cfg = strstr(line, "CONFIG_");
124
125 if (!cfg)
126 return 0;
127
128 if (strncmp(cfg, conf, match->len))
129 return 0;
130
131 const char *val = &cfg[match->len];
132
133 switch (cfg[match->len]) {
134 case '=':
135 break;
136 case ' ':
137 if (is_set(val, "is not set")) {
138 result->match = 'n';
139 goto match;
140 }
141 /* fall through */
142 default:
143 return 0;
144 }
145
146 if (is_set(val, "=y")) {
147 result->match = 'y';
148 goto match;
149 }
150
151 if (is_set(val, "=m")) {
152 result->match = 'm';
153 goto match;
154 }
155
156 result->match = 'v';
157 result->value = strndup(val+1, strlen(val)-2);
158
159 match:
160 match->match = 1;
161 return 1;
162 }
163
tst_kconfig_read(const char * const * kconfigs,struct tst_kconfig_res results[],size_t cnt)164 void tst_kconfig_read(const char *const *kconfigs,
165 struct tst_kconfig_res results[], size_t cnt)
166 {
167 struct match matches[cnt];
168 FILE *fp;
169 unsigned int i, j;
170 char buf[1024];
171
172 for (i = 0; i < cnt; i++) {
173 const char *val = strchr(kconfigs[i], '=');
174
175 if (strncmp("CONFIG_", kconfigs[i], 7))
176 tst_brk(TBROK, "Invalid config string '%s'", kconfigs[i]);
177
178 matches[i].match = 0;
179 matches[i].len = strlen(kconfigs[i]);
180
181 if (val) {
182 matches[i].val = val + 1;
183 matches[i].len -= strlen(val);
184 }
185
186 results[i].match = 0;
187 results[i].value = NULL;
188 }
189
190 fp = open_kconfig();
191 if (!fp)
192 tst_brk(TBROK, "Cannot parse kernel .config");
193
194 while (fgets(buf, sizeof(buf), fp)) {
195 for (i = 0; i < cnt; i++) {
196 if (match(&matches[i], kconfigs[i], &results[i], buf)) {
197 for (j = 0; j < cnt; j++) {
198 if (matches[j].match)
199 break;
200 }
201
202 if (j == cnt)
203 goto exit;
204 }
205 }
206
207 }
208
209 exit:
210 close_kconfig(fp);
211 }
212
array_len(const char * const kconfigs[])213 static size_t array_len(const char *const kconfigs[])
214 {
215 size_t i = 0;
216
217 while (kconfigs[++i]);
218
219 return i;
220 }
221
compare_res(struct tst_kconfig_res * res,const char * kconfig,char match,const char * val)222 static int compare_res(struct tst_kconfig_res *res, const char *kconfig,
223 char match, const char *val)
224 {
225 if (res->match != match) {
226 tst_res(TINFO, "Needs kernel %s, have %c", kconfig, res->match);
227 return 1;
228 }
229
230 if (match != 'v')
231 return 0;
232
233 if (strcmp(res->value, val)) {
234 tst_res(TINFO, "Needs kernel %s, have %s", kconfig, res->value);
235 return 1;
236 }
237
238 return 0;
239 }
240
tst_kconfig_check(const char * const kconfigs[])241 void tst_kconfig_check(const char *const kconfigs[])
242 {
243 size_t cnt = array_len(kconfigs);
244 struct tst_kconfig_res results[cnt];
245 unsigned int i;
246 int abort_test = 0;
247
248 tst_kconfig_read(kconfigs, results, cnt);
249
250 for (i = 0; i < cnt; i++) {
251 if (results[i].match == 0) {
252 tst_res(TINFO, "Missing kernel %s", kconfigs[i]);
253 abort_test = 1;
254 continue;
255 }
256
257 if (results[i].match == 'n') {
258 tst_res(TINFO, "Kernel %s is not set", kconfigs[i]);
259 abort_test = 1;
260 continue;
261 }
262
263 const char *val = strchr(kconfigs[i], '=');
264
265 if (val) {
266 char match = 'v';
267 val++;
268
269 if (!strcmp(val, "y"))
270 match = 'y';
271
272 if (!strcmp(val, "m"))
273 match = 'm';
274
275 if (compare_res(&results[i], kconfigs[i], match, val))
276 abort_test = 1;
277
278 }
279
280 free(results[i].value);
281 }
282
283 if (abort_test)
284 tst_brk(TCONF, "Aborting due to unsuitable kernel config, see above!");
285 }
286