• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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