xref: /aosp_15_r20/external/ltp/metadata/metaparse.c (revision 49cdfc7efb34551c7342be41a7384b9c40d7cab7)
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