1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2019 Cyril Hrubis <chrubis@suse.cz>
4 * Copyright (c) 2020 Petr Vorel <pvorel@suse.cz>
5 */
6
7 #include <stdio.h>
8 #include <string.h>
9 #include <libgen.h>
10 #include <ctype.h>
11 #include <unistd.h>
12
13 #include "data_storage.h"
14
15 #define WARN(str) fprintf(stderr, "WARNING: " str "\n")
16
oneline_comment(FILE * f)17 static void oneline_comment(FILE *f)
18 {
19 int c;
20
21 do {
22 c = getc(f);
23 } while (c != '\n');
24 }
25
eat_asterisk_space(const char * c)26 static const char *eat_asterisk_space(const char *c)
27 {
28 unsigned int i = 0;
29
30 while (isspace(c[i]))
31 i++;
32
33 if (c[i] == '*') {
34 if (isspace(c[i+1]))
35 i++;
36 return &c[i+1];
37 }
38
39 return c;
40 }
41
multiline_comment(FILE * f,struct data_node * doc)42 static void multiline_comment(FILE *f, struct data_node *doc)
43 {
44 int c;
45 int state = 0;
46 char buf[4096];
47 unsigned int bufp = 0;
48
49 for (;;) {
50 c = getc(f);
51
52 if (doc) {
53 if (c == '\n') {
54 struct data_node *line;
55 buf[bufp] = 0;
56 line = data_node_string(eat_asterisk_space(buf));
57 if (data_node_array_add(doc, line))
58 WARN("doc string comment truncated");
59 bufp = 0;
60 continue;
61 }
62
63 if (bufp + 1 >= sizeof(buf))
64 continue;
65
66 buf[bufp++] = c;
67 }
68
69 switch (state) {
70 case 0:
71 if (c == '*')
72 state = 1;
73 break;
74 case 1:
75 switch (c) {
76 case '/':
77 return;
78 case '*':
79 continue;
80 default:
81 state = 0;
82 break;
83 }
84 break;
85 }
86 }
87
88 }
89
90 static const char doc_prefix[] = "\\\n";
91
maybe_doc_comment(FILE * f,struct data_node * doc)92 static void maybe_doc_comment(FILE *f, struct data_node *doc)
93 {
94 int c, i;
95
96 for (i = 0; doc_prefix[i]; i++) {
97 c = getc(f);
98
99 if (c == doc_prefix[i])
100 continue;
101
102 if (c == '*')
103 ungetc(c, f);
104
105 multiline_comment(f, NULL);
106 return;
107 }
108
109 multiline_comment(f, doc);
110 }
111
maybe_comment(FILE * f,struct data_node * doc)112 static void maybe_comment(FILE *f, struct data_node *doc)
113 {
114 int c = getc(f);
115
116 switch (c) {
117 case '/':
118 oneline_comment(f);
119 break;
120 case '*':
121 maybe_doc_comment(f, doc);
122 break;
123 default:
124 ungetc(c, f);
125 break;
126 }
127 }
128
next_token(FILE * f,struct data_node * doc)129 const char *next_token(FILE *f, struct data_node *doc)
130 {
131 size_t i = 0;
132 static char buf[4096];
133 int c;
134 int in_str = 0;
135
136 for (;;) {
137 c = fgetc(f);
138
139 if (c == EOF)
140 goto exit;
141
142 if (in_str) {
143 if (c == '"') {
144 if (i == 0 || buf[i-1] != '\\')
145 goto exit;
146 }
147
148 buf[i++] = c;
149 continue;
150 }
151
152 switch (c) {
153 case '{':
154 case '}':
155 case ';':
156 case '(':
157 case ')':
158 case '=':
159 case ',':
160 case '[':
161 case ']':
162 if (i) {
163 ungetc(c, f);
164 goto exit;
165 }
166
167 buf[i++] = c;
168 goto exit;
169 case '0' ... '9':
170 case 'a' ... 'z':
171 case 'A' ... 'Z':
172 case '.':
173 case '_':
174 case '-':
175 buf[i++] = c;
176 break;
177 case '/':
178 maybe_comment(f, doc);
179 break;
180 case '"':
181 in_str = 1;
182 break;
183 case ' ':
184 case '\n':
185 case '\t':
186 if (i)
187 goto exit;
188 break;
189 }
190 }
191
192 exit:
193 if (i == 0 && !in_str)
194 return NULL;
195
196 buf[i] = 0;
197 return buf;
198 }
199
parse_array(FILE * f,struct data_node * node)200 static int parse_array(FILE *f, struct data_node *node)
201 {
202 const char *token;
203
204 for (;;) {
205 if (!(token = next_token(f, NULL)))
206 return 1;
207
208 if (!strcmp(token, "{")) {
209 struct data_node *ret = data_node_array();
210 parse_array(f, ret);
211
212 if (data_node_array_len(ret))
213 data_node_array_add(node, ret);
214 else
215 data_node_free(ret);
216
217 continue;
218 }
219
220 if (!strcmp(token, "}"))
221 return 0;
222
223 if (!strcmp(token, ","))
224 continue;
225
226 if (!strcmp(token, "NULL"))
227 continue;
228
229 struct data_node *str = data_node_string(token);
230
231 data_node_array_add(node, str);
232 }
233
234 return 0;
235 }
236
parse_test_struct(FILE * f,struct data_node * doc,struct data_node * node)237 static int parse_test_struct(FILE *f, struct data_node *doc, struct data_node *node)
238 {
239 const char *token;
240 char *id = NULL;
241 int state = 0;
242 struct data_node *ret;
243
244 for (;;) {
245 if (!(token = next_token(f, doc)))
246 return 1;
247
248 if (!strcmp(token, "}"))
249 return 0;
250
251 switch (state) {
252 case 0:
253 id = strdup(token);
254 state = 1;
255 continue;
256 case 1:
257 if (!strcmp(token, "="))
258 state = 2;
259 else
260 WARN("Expected '='");
261 continue;
262 case 2:
263 if (!strcmp(token, "(")) {
264 state = 3;
265 continue;
266 }
267 break;
268 case 3:
269 if (!strcmp(token, ")"))
270 state = 2;
271 continue;
272
273 case 4:
274 if (!strcmp(token, ","))
275 state = 0;
276 continue;
277 }
278
279 if (!strcmp(token, "{")) {
280 ret = data_node_array();
281 parse_array(f, ret);
282 } else {
283 ret = data_node_string(token);
284 }
285
286 const char *key = id;
287 if (key[0] == '.')
288 key++;
289
290 data_node_hash_add(node, key, ret);
291 free(id);
292 state = 4;
293 }
294 }
295
296 static const char *tokens[] = {
297 "static",
298 "struct",
299 "tst_test",
300 "test",
301 "=",
302 "{",
303 };
304
parse_file(const char * fname)305 static struct data_node *parse_file(const char *fname)
306 {
307 int state = 0, found = 0;
308 const char *token;
309
310 if (access(fname, F_OK)) {
311 fprintf(stderr, "file %s does not exist\n", fname);
312 return NULL;
313 }
314
315 FILE *f = fopen(fname, "r");
316
317 struct data_node *res = data_node_hash();
318 struct data_node *doc = data_node_array();
319
320 while ((token = next_token(f, doc))) {
321 if (state < 6 && !strcmp(tokens[state], token))
322 state++;
323 else
324 state = 0;
325
326 if (state < 6)
327 continue;
328
329 found = 1;
330 parse_test_struct(f, doc, res);
331 }
332
333
334 if (data_node_array_len(doc)) {
335 data_node_hash_add(res, "doc", doc);
336 found = 1;
337 } else {
338 data_node_free(doc);
339 }
340
341 fclose(f);
342
343 if (!found) {
344 data_node_free(res);
345 return NULL;
346 }
347
348 return res;
349 }
350
351 static const char *filter_out[] = {
352 "bufs",
353 "cleanup",
354 "mntpoint",
355 "setup",
356 "tcnt",
357 "test",
358 "test_all",
359 NULL
360 };
361
362 static struct implies {
363 const char *flag;
364 const char **implies;
365 } implies[] = {
366 {"mount_device", (const char *[]) {"format_device", "needs_device",
367 "needs_tmpdir", NULL}},
368 {"format_device", (const char *[]) {"needs_device", "needs_tmpdir",
369 NULL}},
370 {"all_filesystems", (const char *[]) {"needs_device", "needs_tmpdir",
371 NULL}},
372 {"needs_device", (const char *[]) {"needs_tmpdir", NULL}},
373 {"needs_checkpoints", (const char *[]) {"needs_tmpdir", NULL}},
374 {"resource_files", (const char *[]) {"needs_tmpdir", NULL}},
375 {NULL, (const char *[]) {NULL}}
376 };
377
strip_name(char * path)378 const char *strip_name(char *path)
379 {
380 char *name = basename(path);
381 size_t len = strlen(name);
382
383 if (len > 2 && name[len-1] == 'c' && name[len-2] == '.')
384 name[len-2] = '\0';
385
386 return name;
387 }
388
main(int argc,char * argv[])389 int main(int argc, char *argv[])
390 {
391 unsigned int i, j;
392 struct data_node *res;
393
394 if (argc != 2) {
395 fprintf(stderr, "Usage: docparse filename.c\n");
396 return 1;
397 }
398
399 res = parse_file(argv[1]);
400 if (!res)
401 return 0;
402
403 /* Filter out useless data */
404 for (i = 0; filter_out[i]; i++)
405 data_node_hash_del(res, filter_out[i]);
406
407 /* Normalize the result */
408 for (i = 0; implies[i].flag; i++) {
409 if (data_node_hash_get(res, implies[i].flag)) {
410 for (j = 0; implies[i].implies[j]; j++) {
411 if (data_node_hash_get(res, implies[i].implies[j]))
412 fprintf(stderr, "%s: useless tag: %s\n",
413 argv[1], implies[i].implies[j]);
414 }
415 }
416 }
417
418 for (i = 0; implies[i].flag; i++) {
419 if (data_node_hash_get(res, implies[i].flag)) {
420 for (j = 0; implies[i].implies[j]; j++) {
421 if (!data_node_hash_get(res, implies[i].implies[j]))
422 data_node_hash_add(res, implies[i].implies[j],
423 data_node_string("1"));
424 }
425 }
426 }
427
428 data_node_hash_add(res, "fname", data_node_string(argv[1]));
429 printf(" \"%s\": ", strip_name(argv[1]));
430 data_to_json(res, stdout, 2);
431 data_node_free(res);
432
433 return 0;
434 }
435