1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2019-2021 Cyril Hrubis <[email protected]>
4 * Copyright (c) 2020 Petr Vorel <[email protected]>
5 */
6
7 #define _GNU_SOURCE
8
9 #include <search.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <libgen.h>
13 #include <ctype.h>
14 #include <unistd.h>
15 #include <errno.h>
16
17 #include "data_storage.h"
18
19 #define INCLUDE_PATH_MAX 5
20
21 static int verbose;
22 static char *cmdline_includepath[INCLUDE_PATH_MAX];
23 static unsigned int cmdline_includepaths;
24 static char *includepath;
25
26 #define WARN(str) fprintf(stderr, "WARNING: " str "\n")
27
oneline_comment(FILE * f)28 static void oneline_comment(FILE *f)
29 {
30 int c;
31
32 do {
33 c = getc(f);
34 } while (c != '\n');
35 }
36
eat_asterisk_space(const char * c)37 static const char *eat_asterisk_space(const char *c)
38 {
39 unsigned int i = 0;
40
41 while (isspace(c[i]))
42 i++;
43
44 if (c[i] == '*') {
45 if (isspace(c[i+1]))
46 i++;
47 return &c[i+1];
48 }
49
50 return c;
51 }
52
multiline_comment(FILE * f,struct data_node * doc)53 static void multiline_comment(FILE *f, struct data_node *doc)
54 {
55 int c;
56 int state = 0;
57 char buf[4096];
58 unsigned int bufp = 0;
59
60 for (;;) {
61 c = getc(f);
62
63 if (doc) {
64 if (c == '\n') {
65 struct data_node *line;
66 buf[bufp] = 0;
67 line = data_node_string(eat_asterisk_space(buf));
68 if (data_node_array_add(doc, line))
69 WARN("doc string comment truncated");
70 bufp = 0;
71 continue;
72 }
73
74 if (bufp + 1 >= sizeof(buf))
75 continue;
76
77 buf[bufp++] = c;
78 }
79
80 switch (state) {
81 case 0:
82 if (c == '*')
83 state = 1;
84 break;
85 case 1:
86 switch (c) {
87 case '/':
88 return;
89 case '*':
90 continue;
91 default:
92 state = 0;
93 break;
94 }
95 break;
96 }
97 }
98
99 }
100
101 static const char doc_prefix[] = "\\\n";
102
maybe_doc_comment(FILE * f,struct data_node * doc)103 static void maybe_doc_comment(FILE *f, struct data_node *doc)
104 {
105 int c, i;
106
107 for (i = 0; doc_prefix[i]; i++) {
108 c = getc(f);
109
110 if (c == doc_prefix[i])
111 continue;
112
113 if (c == '*')
114 ungetc(c, f);
115
116 multiline_comment(f, NULL);
117 return;
118 }
119
120 multiline_comment(f, doc);
121 }
122
maybe_comment(FILE * f,struct data_node * doc)123 static void maybe_comment(FILE *f, struct data_node *doc)
124 {
125 int c = getc(f);
126
127 switch (c) {
128 case '/':
129 oneline_comment(f);
130 break;
131 case '*':
132 maybe_doc_comment(f, doc);
133 break;
134 default:
135 ungetc(c, f);
136 break;
137 }
138 }
139
next_token2(FILE * f,char * buf,size_t buf_len,struct data_node * doc)140 static char *next_token2(FILE *f, char *buf, size_t buf_len, struct data_node *doc)
141 {
142 size_t i = 0;
143 int c;
144 int in_str = 0;
145
146 buf_len--;
147
148 for (;;) {
149 c = fgetc(f);
150
151 if (c == EOF)
152 goto exit;
153
154 if (in_str) {
155 if (c == '"') {
156 if (i == 0 || buf[i-1] != '\\')
157 goto exit;
158 }
159
160 if (i < buf_len)
161 buf[i++] = c;
162 continue;
163 }
164
165 switch (c) {
166 case '{':
167 case '}':
168 case ';':
169 case '(':
170 case ')':
171 case '=':
172 case ',':
173 case '[':
174 case ']':
175 case '#':
176 if (i) {
177 ungetc(c, f);
178 goto exit;
179 }
180
181 if (i < buf_len)
182 buf[i++] = c;
183 goto exit;
184 case '0' ... '9':
185 case 'a' ... 'z':
186 case 'A' ... 'Z':
187 case '.':
188 case '_':
189 case '-':
190 buf[i++] = c;
191 break;
192 case '/':
193 maybe_comment(f, doc);
194 break;
195 case '"':
196 in_str = 1;
197 break;
198 case ' ':
199 case '\n':
200 case '\t':
201 if (i)
202 goto exit;
203 break;
204 }
205 }
206
207 exit:
208 if (i == 0 && !in_str)
209 return NULL;
210
211 buf[i] = 0;
212 return buf;
213 }
214
next_token(FILE * f,struct data_node * doc)215 static char *next_token(FILE *f, struct data_node *doc)
216 {
217 static char buf[4096];
218
219 return next_token2(f, buf, sizeof(buf), doc);
220 }
221
open_file(const char * dir,const char * fname)222 static FILE *open_file(const char *dir, const char *fname)
223 {
224 FILE *f;
225 char *path;
226
227 if (asprintf(&path, "%s/%s", dir, fname) < 0)
228 return NULL;
229
230 f = fopen(path, "r");
231
232 free(path);
233
234 return f;
235 }
236
open_include(FILE * f)237 static FILE *open_include(FILE *f)
238 {
239 char buf[256], *fname;
240 FILE *inc;
241 unsigned int i;
242
243 if (!fscanf(f, "%s\n", buf))
244 return NULL;
245
246 if (buf[0] != '"')
247 return NULL;
248
249 fname = buf + 1;
250
251 if (!buf[0])
252 return NULL;
253
254 fname[strlen(fname)-1] = 0;
255
256 inc = open_file(includepath, fname);
257 if (inc) {
258 if (verbose)
259 fprintf(stderr, "INCLUDE %s/%s\n", includepath, fname);
260
261 return inc;
262 }
263
264 for (i = 0; i < cmdline_includepaths; i++) {
265 inc = open_file(cmdline_includepath[i], fname);
266
267 if (!inc)
268 continue;
269
270 if (verbose) {
271 fprintf(stderr, "INCLUDE %s/%s\n",
272 cmdline_includepath[i], fname);
273 }
274
275 return inc;
276 }
277
278 return NULL;
279 }
280
close_include(FILE * inc)281 static void close_include(FILE *inc)
282 {
283 if (verbose)
284 fprintf(stderr, "INCLUDE END\n");
285
286 fclose(inc);
287 }
288
parse_array(FILE * f,struct data_node * node)289 static int parse_array(FILE *f, struct data_node *node)
290 {
291 const char *token;
292
293 for (;;) {
294 if (!(token = next_token(f, NULL)))
295 return 1;
296
297 if (!strcmp(token, "{")) {
298 struct data_node *ret = data_node_array();
299 parse_array(f, ret);
300
301 if (data_node_array_len(ret))
302 data_node_array_add(node, ret);
303 else
304 data_node_free(ret);
305
306 continue;
307 }
308
309 if (!strcmp(token, "}"))
310 return 0;
311
312 if (!strcmp(token, ","))
313 continue;
314
315 if (!strcmp(token, "NULL"))
316 continue;
317
318 struct data_node *str = data_node_string(token);
319
320 data_node_array_add(node, str);
321 }
322
323 return 0;
324 }
325
try_apply_macro(char ** res)326 static void try_apply_macro(char **res)
327 {
328 ENTRY macro = {
329 .key = *res,
330 };
331
332 ENTRY *ret;
333
334 ret = hsearch(macro, FIND);
335
336 if (!ret)
337 return;
338
339 if (verbose)
340 fprintf(stderr, "APPLYING MACRO %s=%s\n", ret->key, (char*)ret->data);
341
342 *res = ret->data;
343 }
344
parse_get_array_len(FILE * f)345 static int parse_get_array_len(FILE *f)
346 {
347 const char *token;
348 int cnt = 0, depth = 0, prev_comma = 0;
349
350 if (!(token = next_token(f, NULL)))
351 return 0;
352
353 if (strcmp(token, "{"))
354 return 0;
355
356 for (;;) {
357 if (!(token = next_token(f, NULL)))
358 return 0;
359
360 if (!strcmp(token, "{"))
361 depth++;
362
363 if (!strcmp(token, "}"))
364 depth--;
365 else
366 prev_comma = 0;
367
368 if (!strcmp(token, ",") && !depth) {
369 prev_comma = 1;
370 cnt++;
371 }
372
373 if (depth < 0)
374 return cnt + !prev_comma;
375 }
376 }
377
look_for_array_size(FILE * f,const char * arr_id,struct data_node ** res)378 static void look_for_array_size(FILE *f, const char *arr_id, struct data_node **res)
379 {
380 const char *token;
381 char buf[2][2048] = {};
382 int cur_buf = 0;
383 int prev_buf = 1;
384
385 for (;;) {
386 if (!(token = next_token2(f, buf[cur_buf], sizeof(buf[cur_buf]), NULL)))
387 break;
388
389 if (!strcmp(token, "=") && !strcmp(buf[prev_buf], arr_id)) {
390 int arr_len = parse_get_array_len(f);
391
392 if (verbose)
393 fprintf(stderr, "ARRAY %s LENGTH = %i\n", arr_id, arr_len);
394
395 *res = data_node_int(arr_len);
396
397 break;
398 }
399
400 if (strcmp(buf[cur_buf], "]") && strcmp(buf[cur_buf], "[")) {
401 cur_buf = !cur_buf;
402 prev_buf = !prev_buf;
403 }
404 }
405 }
406
parse_array_size(FILE * f,struct data_node ** res)407 static int parse_array_size(FILE *f, struct data_node **res)
408 {
409 const char *token;
410 char *arr_id;
411 long pos;
412 int hash = 0;
413
414 *res = NULL;
415
416 if (!(token = next_token(f, NULL)))
417 return 1;
418
419 if (strcmp(token, "("))
420 return 1;
421
422 if (!(token = next_token(f, NULL)))
423 return 1;
424
425 arr_id = strdup(token);
426
427 if (verbose)
428 fprintf(stderr, "COMPUTING ARRAY '%s' LENGHT\n", arr_id);
429
430 pos = ftell(f);
431
432 rewind(f);
433
434 look_for_array_size(f, arr_id, res);
435
436 if (!*res) {
437 FILE *inc;
438
439 rewind(f);
440
441 for (;;) {
442 if (!(token = next_token(f, NULL)))
443 break;
444
445 if (token[0] == '#') {
446 hash = 1;
447 continue;
448 }
449
450 if (!hash)
451 continue;
452
453 if (!strcmp(token, "include")) {
454 inc = open_include(f);
455
456 if (inc) {
457 look_for_array_size(inc, arr_id, res);
458 close_include(inc);
459 }
460 }
461
462 if (*res)
463 break;
464 }
465 }
466
467 free(arr_id);
468
469 if (fseek(f, pos, SEEK_SET))
470 return 1;
471
472 return 0;
473 }
474
parse_test_struct(FILE * f,struct data_node * doc,struct data_node * node)475 static int parse_test_struct(FILE *f, struct data_node *doc, struct data_node *node)
476 {
477 char *token;
478 char *id = NULL;
479 int state = 0;
480 struct data_node *ret;
481
482 for (;;) {
483 if (!(token = next_token(f, doc)))
484 return 1;
485
486 if (!strcmp(token, "}"))
487 return 0;
488
489 switch (state) {
490 case 0:
491 id = strdup(token);
492 state = 1;
493 continue;
494 case 1:
495 if (!strcmp(token, "="))
496 state = 2;
497 else
498 WARN("Expected '='");
499 continue;
500 case 2:
501 if (!strcmp(token, "(")) {
502 state = 3;
503 continue;
504 }
505 break;
506 case 3:
507 if (!strcmp(token, ")"))
508 state = 2;
509 continue;
510
511 case 4:
512 if (!strcmp(token, ","))
513 state = 0;
514 continue;
515 }
516
517 if (!strcmp(token, "{")) {
518 ret = data_node_array();
519 parse_array(f, ret);
520 } else if (!strcmp(token, "ARRAY_SIZE")) {
521 if (parse_array_size(f, &ret))
522 return 1;
523 } else {
524 try_apply_macro(&token);
525 ret = data_node_string(token);
526 }
527
528 if (!ret)
529 continue;
530
531 const char *key = id;
532 if (key[0] == '.')
533 key++;
534
535 data_node_hash_add(node, key, ret);
536 free(id);
537 state = 4;
538 }
539 }
540
541 static const char *tokens[] = {
542 "static",
543 "struct",
544 "tst_test",
545 "test",
546 "=",
547 "{",
548 };
549
macro_get_string(FILE * f,char * buf,char * buf_end)550 static void macro_get_string(FILE *f, char *buf, char *buf_end)
551 {
552 int c;
553 char *buf_start = buf;
554
555 for (;;) {
556 c = fgetc(f);
557
558 switch (c) {
559 case EOF:
560 *buf = 0;
561 return;
562 case '"':
563 if (buf == buf_start || buf[-1] != '\\') {
564 *buf = 0;
565 return;
566 }
567 buf[-1] = '"';
568 break;
569 default:
570 if (buf < buf_end)
571 *(buf++) = c;
572 }
573 }
574 }
575
macro_get_val(FILE * f,char * buf,size_t buf_len)576 static void macro_get_val(FILE *f, char *buf, size_t buf_len)
577 {
578 int c, prev = 0;
579 char *buf_end = buf + buf_len - 1;
580
581 while (isspace(c = fgetc(f)));
582
583 if (c == '"') {
584 macro_get_string(f, buf, buf_end);
585 return;
586 }
587
588 for (;;) {
589 switch (c) {
590 case '\n':
591 if (prev == '\\') {
592 buf--;
593 } else {
594 *buf = 0;
595 return;
596 }
597 break;
598 case EOF:
599 *buf = 0;
600 return;
601 case ' ':
602 case '\t':
603 break;
604 default:
605 if (buf < buf_end)
606 *(buf++) = c;
607 }
608
609 prev = c;
610 c = fgetc(f);
611 }
612 }
613
parse_macro(FILE * f)614 static void parse_macro(FILE *f)
615 {
616 char name[128];
617 char val[256];
618
619 if (!fscanf(f, "%s[^\n]", name))
620 return;
621
622 if (fgetc(f) == '\n')
623 return;
624
625 macro_get_val(f, val, sizeof(val));
626
627 if (name[0] == '_')
628 return;
629
630 ENTRY e = {
631 .key = strdup(name),
632 .data = strdup(val),
633 };
634
635 if (verbose)
636 fprintf(stderr, " MACRO %s=%s\n", e.key, (char*)e.data);
637
638 hsearch(e, ENTER);
639 }
640
parse_include_macros(FILE * f)641 static void parse_include_macros(FILE *f)
642 {
643 FILE *inc;
644 const char *token;
645 int hash = 0;
646
647 inc = open_include(f);
648 if (!inc)
649 return;
650
651 while ((token = next_token(inc, NULL))) {
652 if (token[0] == '#') {
653 hash = 1;
654 continue;
655 }
656
657 if (!hash)
658 continue;
659
660 if (!strcmp(token, "define"))
661 parse_macro(inc);
662
663 hash = 0;
664 }
665
666 close_include(inc);
667 }
668
parse_file(const char * fname)669 static struct data_node *parse_file(const char *fname)
670 {
671 int state = 0, found = 0;
672 const char *token;
673
674 if (access(fname, F_OK)) {
675 fprintf(stderr, "file %s does not exist\n", fname);
676 return NULL;
677 }
678
679 FILE *f = fopen(fname, "r");
680
681 includepath = dirname(strdup(fname));
682
683 struct data_node *res = data_node_hash();
684 struct data_node *doc = data_node_array();
685
686 while ((token = next_token(f, doc))) {
687 if (state < 6 && !strcmp(tokens[state], token)) {
688 state++;
689 } else {
690 if (token[0] == '#') {
691 token = next_token(f, doc);
692 if (token) {
693 if (!strcmp(token, "define"))
694 parse_macro(f);
695
696 if (!strcmp(token, "include"))
697 parse_include_macros(f);
698 }
699 }
700
701 state = 0;
702 }
703
704 if (state < 6)
705 continue;
706
707 found = 1;
708 parse_test_struct(f, doc, res);
709 }
710
711 if (data_node_array_len(doc)) {
712 data_node_hash_add(res, "doc", doc);
713 found = 1;
714 } else {
715 data_node_free(doc);
716 }
717
718 fclose(f);
719
720 if (!found) {
721 data_node_free(res);
722 return NULL;
723 }
724
725 return res;
726 }
727
728 static struct typemap {
729 const char *id;
730 enum data_type type;
731 } tst_test_typemap[] = {
732 {.id = "test_variants", .type = DATA_INT},
733 {}
734 };
735
convert_str2int(struct data_node * res,const char * id,const char * str_val)736 static void convert_str2int(struct data_node *res, const char *id, const char *str_val)
737 {
738 long val;
739 char *endptr;
740
741 errno = 0;
742 val = strtol(str_val, &endptr, 10);
743
744 if (errno || *endptr) {
745 fprintf(stderr, "Cannot convert %s value %s to int!\n", id, str_val);
746 exit(1);
747 }
748
749 if (verbose)
750 fprintf(stderr, "NORMALIZING %s TO INT %li\n", id, val);
751
752 data_node_hash_del(res, id);
753 data_node_hash_add(res, id, data_node_int(val));
754 }
755
check_normalize_types(struct data_node * res)756 static void check_normalize_types(struct data_node *res)
757 {
758 unsigned int i;
759
760 for (i = 0; tst_test_typemap[i].id; i++) {
761 struct data_node *n;
762 struct typemap *typemap = &tst_test_typemap[i];
763
764 n = data_node_hash_get(res, typemap->id);
765 if (!n)
766 continue;
767
768 if (n->type == typemap->type)
769 continue;
770
771 if (n->type == DATA_STRING && typemap->type == DATA_INT) {
772 convert_str2int(res, typemap->id, n->string.val);
773 continue;
774 }
775
776 fprintf(stderr, "Cannot convert %s from %s to %s!\n",
777 typemap->id, data_type_name(n->type),
778 data_type_name(typemap->type));
779 exit(1);
780 }
781 }
782
783 static const char *filter_out[] = {
784 "bufs",
785 "cleanup",
786 "mntpoint",
787 "setup",
788 "tcnt",
789 "test",
790 "test_all",
791 NULL
792 };
793
794 static struct implies {
795 const char *flag;
796 const char **implies;
797 } implies[] = {
798 {"mount_device", (const char *[]) {"format_device", "needs_device",
799 "needs_tmpdir", NULL}},
800 {"format_device", (const char *[]) {"needs_device", "needs_tmpdir",
801 NULL}},
802 {"all_filesystems", (const char *[]) {"needs_device", "needs_tmpdir",
803 NULL}},
804 {"needs_device", (const char *[]) {"needs_tmpdir", NULL}},
805 {"needs_checkpoints", (const char *[]) {"needs_tmpdir", NULL}},
806 {"resource_files", (const char *[]) {"needs_tmpdir", NULL}},
807 {NULL, (const char *[]) {NULL}}
808 };
809
strip_name(char * path)810 const char *strip_name(char *path)
811 {
812 char *name = basename(path);
813 size_t len = strlen(name);
814
815 if (len > 2 && name[len-1] == 'c' && name[len-2] == '.')
816 name[len-2] = '\0';
817
818 return name;
819 }
820
print_help(const char * prgname)821 static void print_help(const char *prgname)
822 {
823 printf("usage: %s [-vh] input.c\n\n", prgname);
824 printf("-v sets verbose mode\n");
825 printf("-I add include path\n");
826 printf("-h prints this help\n\n");
827 exit(0);
828 }
829
main(int argc,char * argv[])830 int main(int argc, char *argv[])
831 {
832 unsigned int i, j;
833 struct data_node *res;
834 int opt;
835
836 while ((opt = getopt(argc, argv, "hI:v")) != -1) {
837 switch (opt) {
838 case 'h':
839 print_help(argv[0]);
840 break;
841 case 'I':
842 if (cmdline_includepaths >= INCLUDE_PATH_MAX) {
843 fprintf(stderr, "Too much include paths!");
844 exit(1);
845 }
846
847 cmdline_includepath[cmdline_includepaths++] = optarg;
848 break;
849 case 'v':
850 verbose = 1;
851 break;
852 }
853 }
854
855 if (optind >= argc) {
856 fprintf(stderr, "No input filename.c\n");
857 return 1;
858 }
859
860 if (!hcreate(128)) {
861 fprintf(stderr, "Failed to initialize hash table\n");
862 return 1;
863 }
864
865 res = parse_file(argv[optind]);
866 if (!res)
867 return 0;
868
869 /* Filter out useless data */
870 for (i = 0; filter_out[i]; i++)
871 data_node_hash_del(res, filter_out[i]);
872
873 /* Normalize the result */
874 for (i = 0; implies[i].flag; i++) {
875 if (data_node_hash_get(res, implies[i].flag)) {
876 for (j = 0; implies[i].implies[j]; j++) {
877 if (data_node_hash_get(res, implies[i].implies[j]))
878 fprintf(stderr, "%s: useless tag: %s\n",
879 argv[optind], implies[i].implies[j]);
880 }
881 }
882 }
883
884 /* Normalize types */
885 check_normalize_types(res);
886
887 for (i = 0; implies[i].flag; i++) {
888 if (data_node_hash_get(res, implies[i].flag)) {
889 for (j = 0; implies[i].implies[j]; j++) {
890 if (!data_node_hash_get(res, implies[i].implies[j]))
891 data_node_hash_add(res, implies[i].implies[j],
892 data_node_string("1"));
893 }
894 }
895 }
896
897 data_node_hash_add(res, "fname", data_node_string(argv[optind]));
898 printf(" \"%s\": ", strip_name(argv[optind]));
899 data_to_json(res, stdout, 2);
900 data_node_free(res);
901
902 return 0;
903 }
904