• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2   Copyright(c) 2021 Intel Corporation
3   All rights reserved.
4 
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of version 2 of the GNU General Public License as
7   published by the Free Software Foundation.
8 
9   This program is distributed in the hope that it will be useful, but
10   WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   General Public License for more details.
13 
14   You should have received a copy of the GNU General Public License
15   along with this program; if not, write to the Free Software
16   Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
17   The full GNU General Public License is included in this distribution
18   in the file called LICENSE.GPL.
19 */
20 
21 #include <stdarg.h>
22 #include <stdio.h>
23 #include <stddef.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <regex.h>
28 
29 #include <alsa/asoundlib.h>
30 #include "gettext.h"
31 #include "topology.h"
32 #include "pre-processor.h"
33 
34 /*
35  * Helper function to find config by id.
36  * Topology2.0 object names are constructed with attribute values separated by '.'.
37  * So snd_config_search() cannot be used as it interprets the '.' as the node separator.
38  */
tplg_find_config(snd_config_t * config,const char * name)39 snd_config_t *tplg_find_config(snd_config_t *config, const char *name)
40 {
41 	snd_config_iterator_t i, next;
42 	snd_config_t *n;
43 	const char *id;
44 
45 	snd_config_for_each(i, next, config) {
46 		n = snd_config_iterator_entry(i);
47 		if (snd_config_get_id(n, &id) < 0)
48 			continue;
49 
50 		if (!strcmp(id, name))
51 			return n;
52 	}
53 
54 	return NULL;
55 }
56 
57 /* make a new config and add it to parent */
tplg_config_make_add(snd_config_t ** config,const char * id,snd_config_type_t type,snd_config_t * parent)58 int tplg_config_make_add(snd_config_t **config, const char *id, snd_config_type_t type,
59 			 snd_config_t *parent)
60 {
61 	int ret;
62 
63 	ret = snd_config_make(config, id, type);
64 	if (ret < 0)
65 		return ret;
66 
67 	ret = snd_config_add(parent, *config);
68 	if (ret < 0)
69 		snd_config_delete(*config);
70 
71 	return ret;
72 }
73 
74 /*
75  * The pre-processor will need to concat multiple strings separate by '.' to construct the object
76  * name and search for configs with ID's separated by '.'.
77  * This function helps concat input strings in the specified input format
78  */
tplg_snprintf(char * fmt,...)79 char *tplg_snprintf(char *fmt, ...)
80 {
81 	char *string;
82 	int len = 1;
83 
84 	va_list va;
85 
86 	va_start(va, fmt);
87 	len += vsnprintf(NULL, 0, fmt, va);
88 	va_end(va);
89 
90 	string = calloc(1, len);
91 	if (!string)
92 		return NULL;
93 
94 	va_start(va, fmt);
95 	vsnprintf(string, len, fmt, va);
96 	va_end(va);
97 
98 	return string;
99 }
100 
101 #ifdef TPLG_DEBUG
tplg_pp_debug(char * fmt,...)102 void tplg_pp_debug(char *fmt, ...)
103 {
104 	char msg[DEBUG_MAX_LENGTH];
105 	va_list va;
106 
107 	va_start(va, fmt);
108 	vsnprintf(msg, DEBUG_MAX_LENGTH, fmt, va);
109 	va_end(va);
110 
111 	fprintf(stdout, "%s\n", msg);
112 }
113 
tplg_pp_config_debug(struct tplg_pre_processor * tplg_pp,snd_config_t * cfg)114 void tplg_pp_config_debug(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg)
115 {
116 	snd_config_save(cfg, tplg_pp->dbg_output);
117 }
118 #else
tplg_pp_debug(char * fmt,...)119 void tplg_pp_debug(char *fmt, ...) {}
tplg_pp_config_debug(struct tplg_pre_processor * tplg_pp,snd_config_t * cfg)120 void tplg_pp_config_debug(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg){}
121 #endif
122 
pre_process_config(struct tplg_pre_processor * tplg_pp,snd_config_t * cfg)123 static int pre_process_config(struct tplg_pre_processor *tplg_pp, snd_config_t *cfg)
124 {
125 	snd_config_iterator_t i, next, i2, next2;
126 	snd_config_t *n, *n2;
127 	const char *id;
128 	int err;
129 
130 	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
131 		fprintf(stderr, "compound type expected at top level");
132 		return -EINVAL;
133 	}
134 
135 	/* parse topology objects */
136 	snd_config_for_each(i, next, cfg) {
137 		n = snd_config_iterator_entry(i);
138 		if (snd_config_get_id(n, &id) < 0)
139 			continue;
140 
141 		if (strcmp(id, "Object"))
142 			continue;
143 
144 		if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
145 			fprintf(stderr, "compound type expected for %s", id);
146 			return -EINVAL;
147 		}
148 
149 		snd_config_for_each(i2, next2, n) {
150 			n2 = snd_config_iterator_entry(i2);
151 
152 			if (snd_config_get_id(n2, &id) < 0)
153 				continue;
154 
155 			if (snd_config_get_type(n2) != SND_CONFIG_TYPE_COMPOUND) {
156 				fprintf(stderr, "compound type expected for %s", id);
157 				return -EINVAL;
158 			}
159 
160 			/* pre-process Object instance. Top-level object have no parent */
161 			err = tplg_pre_process_objects(tplg_pp, n2, NULL);
162 			if (err < 0)
163 				return err;
164 		}
165 	}
166 
167 	return 0;
168 }
169 
free_pre_preprocessor(struct tplg_pre_processor * tplg_pp)170 void free_pre_preprocessor(struct tplg_pre_processor *tplg_pp)
171 {
172 	snd_output_close(tplg_pp->output);
173 	snd_output_close(tplg_pp->dbg_output);
174 	snd_config_delete(tplg_pp->output_cfg);
175 	free(tplg_pp);
176 }
177 
init_pre_processor(struct tplg_pre_processor ** tplg_pp,snd_output_type_t type,const char * output_file)178 int init_pre_processor(struct tplg_pre_processor **tplg_pp, snd_output_type_t type,
179 		       const char *output_file)
180 {
181 	struct tplg_pre_processor *_tplg_pp;
182 	int ret;
183 
184 	_tplg_pp = calloc(1, sizeof(struct tplg_pre_processor));
185 	if (!_tplg_pp)
186 		return -ENOMEM;
187 
188 	*tplg_pp = _tplg_pp;
189 
190 	/* create output top-level config node */
191 	ret = snd_config_top(&_tplg_pp->output_cfg);
192 	if (ret < 0)
193 		goto err;
194 
195 	/* open output based on type */
196 	if (type == SND_OUTPUT_STDIO) {
197 		ret = snd_output_stdio_open(&_tplg_pp->output, output_file, "w");
198 		if (ret < 0) {
199 			fprintf(stderr, "failed to open file output\n");
200 			goto open_err;
201 		}
202 	} else {
203 		ret = snd_output_buffer_open(&_tplg_pp->output);
204 		if (ret < 0) {
205 			fprintf(stderr, "failed to open buffer output\n");
206 			goto open_err;
207 		}
208 	}
209 
210 	/* debug output */
211 	ret = snd_output_stdio_attach(&_tplg_pp->dbg_output, stdout, 0);
212 	if (ret < 0) {
213 		fprintf(stderr, "failed to open stdout output\n");
214 		goto out_close;
215 	}
216 
217 	return 0;
218 out_close:
219 	snd_output_close(_tplg_pp->output);
220 open_err:
221 	snd_config_delete(_tplg_pp->output_cfg);
222 err:
223 	free(_tplg_pp);
224 	return ret;
225 }
226 
227 #if SND_LIB_VER(1, 2, 5) < SND_LIB_VERSION
pre_process_defines(struct tplg_pre_processor * tplg_pp,const char * pre_processor_defs,snd_config_t * top)228 static int pre_process_defines(struct tplg_pre_processor *tplg_pp, const char *pre_processor_defs,
229 			       snd_config_t *top)
230 {
231 	snd_config_t *conf_defines, *defines;
232 	int ret;
233 
234 	ret = snd_config_search(tplg_pp->input_cfg, "Define", &conf_defines);
235 	if (ret < 0)
236 		return 0;
237 
238 	if (snd_config_get_type(conf_defines) != SND_CONFIG_TYPE_COMPOUND)
239 		return 0;
240 
241 	/*
242 	 * load and merge the command line defines with the variables in the conf file to override
243 	 * default values
244 	 */
245 	if (pre_processor_defs != NULL) {
246 		ret = snd_config_load_string(&defines, pre_processor_defs, strlen(pre_processor_defs));
247 		if (ret < 0) {
248 			fprintf(stderr, "Failed to load pre-processor command line definitions\n");
249 			return ret;
250 		}
251 
252 		ret = snd_config_merge(conf_defines, defines, true);
253 		if (ret < 0) {
254 			fprintf(stderr, "Failed to override variable definitions\n");
255 			return ret;
256 		}
257 	}
258 
259 	return 0;
260 }
261 
pre_process_variables_expand_fcn(snd_config_t ** dst,const char * str,void * private_data)262 static int pre_process_variables_expand_fcn(snd_config_t **dst, const char *str,
263 					    void *private_data)
264 {
265 	struct tplg_pre_processor *tplg_pp = private_data;
266 	snd_config_iterator_t i, next;
267 	snd_config_t *conf_defines;
268 	int ret;
269 
270 	ret = snd_config_search(tplg_pp->input_cfg, "Define", &conf_defines);
271 	if (ret < 0)
272 		return 0;
273 
274 	/* find variable definition */
275 	snd_config_for_each(i, next, conf_defines) {
276 		snd_config_t *n;
277 		const char *id;
278 
279 		n = snd_config_iterator_entry(i);
280 		if (snd_config_get_id(n, &id) < 0)
281 			continue;
282 
283 		if (strcmp(id, str))
284 			continue;
285 
286 		/* found definition. Match type and return appropriate config */
287 		if (snd_config_get_type(n) == SND_CONFIG_TYPE_STRING) {
288 			const char *s;
289 
290 			if (snd_config_get_string(n, &s) < 0)
291 				continue;
292 
293 			return snd_config_imake_string(dst, NULL, s);
294 		}
295 
296 		if (snd_config_get_type(n) == SND_CONFIG_TYPE_INTEGER) {
297 			long v;
298 
299 			if (snd_config_get_integer(n, &v) < 0)
300 				continue;
301 
302 			ret = snd_config_imake_integer(dst, NULL, v);
303 			return ret;
304 		}
305 
306 	}
307 
308 	fprintf(stderr, "No definition for variable %s\n", str);
309 
310 	return -EINVAL;
311 }
312 
313 static int pre_process_includes(struct tplg_pre_processor *tplg_pp, snd_config_t *top,
314 				const char *pre_processor_defs, const char *inc_path);
315 
pre_process_include_conf(struct tplg_pre_processor * tplg_pp,snd_config_t * config,const char * pre_processor_defs,snd_config_t ** new,snd_config_t * variable,const char * inc_path)316 static int pre_process_include_conf(struct tplg_pre_processor *tplg_pp, snd_config_t *config,
317 				    const char *pre_processor_defs, snd_config_t **new,
318 				    snd_config_t *variable, const char *inc_path)
319 {
320 	snd_config_iterator_t i, next;
321 	const char *variable_name;
322 	char *value;
323 	int ret;
324 
325 	if (snd_config_get_id(variable, &variable_name) < 0)
326 		return 0;
327 
328 	switch(snd_config_get_type(variable)) {
329 	case SND_CONFIG_TYPE_STRING:
330 	{
331 		const char *s;
332 
333 		if (snd_config_get_string(variable, &s) < 0) {
334 			SNDERR("Invalid value for variable %s\n", variable_name);
335 			return -EINVAL;
336 		}
337 		value = strdup(s);
338 		if (!value)
339 			return -ENOMEM;
340 		break;
341 	}
342 	case SND_CONFIG_TYPE_INTEGER:
343 	{
344 		long v;
345 
346 		ret = snd_config_get_integer(variable, &v);
347 		if (ret < 0) {
348 			SNDERR("Invalid value for variable %s\n", variable_name);
349 			return ret;
350 		}
351 
352 		value = tplg_snprintf("%ld", v);
353 		if (!value)
354 			return -ENOMEM;
355 		break;
356 	}
357 	default:
358 		SNDERR("Invalid type for variable definition %s\n", variable_name);
359 		return -EINVAL;
360 	}
361 
362 	/* create top-level config node */
363 	ret = snd_config_top(new);
364 	if (ret < 0) {
365 		SNDERR("failed to create top-level node for include conf %s\n", variable_name);
366 		goto err;
367 	}
368 
369 	snd_config_for_each(i, next, config) {
370 		snd_input_t *in;
371 		snd_config_t *n;
372 		regex_t regex;
373 		const char *filename;
374 		const char *id;
375 		char *full_path;
376 
377 		n = snd_config_iterator_entry(i);
378 		if (snd_config_get_id(n, &id) < 0)
379 			continue;
380 
381 		ret = regcomp(&regex, id, 0);
382 		if (ret) {
383 			fprintf(stderr, "Could not compile regex\n");
384 			goto err;
385 		}
386 
387 		/* Execute regular expression */
388 		ret = regexec(&regex, value, 0, NULL, REG_ICASE);
389 		if (ret)
390 			continue;
391 
392 		/* regex matched. now include the conf file */
393 		ret = snd_config_get_string(n, &filename);
394 		if (ret < 0)
395 			goto err;
396 
397 		if (filename && filename[0] != '/')
398 			full_path = tplg_snprintf("%s/%s", inc_path, filename);
399 		else
400 			full_path = tplg_snprintf("%s", filename);
401 
402 		ret = snd_input_stdio_open(&in, full_path, "r");
403 		if (ret < 0) {
404 			fprintf(stderr, "Unable to open included conf file %s\n", full_path);
405 			free(full_path);
406 			goto err;
407 		}
408 		free(full_path);
409 
410 		/* load config */
411 		ret = snd_config_load(*new, in);
412 		snd_input_close(in);
413 		if (ret < 0) {
414 			fprintf(stderr, "Unable to load included configuration\n");
415 			goto err;
416 		}
417 
418 		/* process any args in the included file */
419 		ret = pre_process_defines(tplg_pp, pre_processor_defs, *new);
420 		if (ret < 0) {
421 			fprintf(stderr, "Failed to parse arguments in input config\n");
422 			goto err;
423 		}
424 
425 		/* recursively process any nested includes */
426 		return pre_process_includes(tplg_pp, *new, pre_processor_defs, inc_path);
427 	}
428 
429 err:
430 	free(value);
431 	return ret;
432 }
433 
pre_process_includes(struct tplg_pre_processor * tplg_pp,snd_config_t * top,const char * pre_processor_defs,const char * inc_path)434 static int pre_process_includes(struct tplg_pre_processor *tplg_pp, snd_config_t *top,
435 				const char *pre_processor_defs, const char *inc_path)
436 {
437 	snd_config_iterator_t i, next;
438 	snd_config_t *includes, *conf_defines;
439 	const char *top_id;
440 	int ret;
441 
442 	ret = snd_config_search(top, "IncludeByKey", &includes);
443 	if (ret < 0)
444 		return 0;
445 
446 	snd_config_get_id(top, &top_id);
447 
448 	ret = snd_config_search(tplg_pp->input_cfg, "Define", &conf_defines);
449 	if (ret < 0)
450 		return 0;
451 
452 	snd_config_for_each(i, next, includes) {
453 		snd_config_t *n, *new, *define;
454 		const char *id;
455 
456 		n = snd_config_iterator_entry(i);
457 		if (snd_config_get_id(n, &id) < 0)
458 			continue;
459 
460 		/* find id from variable definitions */
461 		ret = snd_config_search(conf_defines, id, &define);
462 		if (ret < 0) {
463 			fprintf(stderr, "No variable defined for %s\n", id);
464 			return ret;
465 		}
466 
467 		/* create conf node from included file */
468 		ret = pre_process_include_conf(tplg_pp, n, pre_processor_defs, &new, define, inc_path);
469 		if (ret < 0) {
470 			fprintf(stderr, "Unable to process include file \n");
471 			return ret;
472 		}
473 
474 		/* merge the included conf file with the top-level conf */
475 		ret = snd_config_merge(top, new, 0);
476 		if (ret < 0) {
477 			fprintf(stderr, "Failed to add included conf\n");
478 			return ret;
479 		}
480 	}
481 
482 	/* remove all includes from current top */
483 	snd_config_remove(includes);
484 
485 	return 0;
486 }
487 
pre_process_includes_all(struct tplg_pre_processor * tplg_pp,snd_config_t * top,const char * pre_processor_defs,const char * inc_path)488 static int pre_process_includes_all(struct tplg_pre_processor *tplg_pp, snd_config_t *top,
489 				const char *pre_processor_defs, const char *inc_path)
490 {
491 	snd_config_iterator_t i, next;
492 	int ret;
493 
494 	if (snd_config_get_type(top) != SND_CONFIG_TYPE_COMPOUND)
495 		return 0;
496 
497 	/* process includes at this node */
498 	ret = pre_process_includes(tplg_pp, top, pre_processor_defs, inc_path);
499 	if (ret < 0) {
500 		fprintf(stderr, "Failed to process includes\n");
501 		return ret;
502 	}
503 
504 	/* process includes at all child nodes */
505 	snd_config_for_each(i, next, top) {
506 		snd_config_t *n;
507 
508 		n = snd_config_iterator_entry(i);
509 
510 		ret = pre_process_includes_all(tplg_pp, n, pre_processor_defs, inc_path);
511 		if (ret < 0)
512 			return ret;
513 	}
514 
515 	return 0;
516 }
517 #endif /* version < 1.2.6 */
518 
pre_process(struct tplg_pre_processor * tplg_pp,char * config,size_t config_size,const char * pre_processor_defs,const char * inc_path)519 int pre_process(struct tplg_pre_processor *tplg_pp, char *config, size_t config_size,
520 		const char *pre_processor_defs, const char *inc_path)
521 {
522 	snd_input_t *in;
523 	snd_config_t *top;
524 	int err;
525 
526 	/* create input buffer */
527 	err = snd_input_buffer_open(&in, config, config_size);
528 	if (err < 0) {
529 		fprintf(stderr, "Unable to open input buffer\n");
530 		return err;
531 	}
532 
533 	/* create top-level config node */
534 	err = snd_config_top(&top);
535 	if (err < 0)
536 		goto input_close;
537 
538 	/* load config */
539 	err = snd_config_load(top, in);
540 	if (err < 0) {
541 		fprintf(stderr, "Unable not load configuration\n");
542 		goto err;
543 	}
544 
545 	tplg_pp->input_cfg = top;
546 
547 #if SND_LIB_VER(1, 2, 5) < SND_LIB_VERSION
548 	/* parse command line definitions */
549 	err = pre_process_defines(tplg_pp, pre_processor_defs, tplg_pp->input_cfg);
550 	if (err < 0) {
551 		fprintf(stderr, "Failed to parse arguments in input config\n");
552 		goto err;
553 	}
554 
555 	/* include conditional conf files */
556 	err = pre_process_includes_all(tplg_pp, tplg_pp->input_cfg, pre_processor_defs, inc_path);
557 	if (err < 0) {
558 		fprintf(stderr, "Failed to process conditional includes in input config\n");
559 		goto err;
560 	}
561 
562 	/* expand pre-processor variables */
563 	err = snd_config_expand_custom(tplg_pp->input_cfg, tplg_pp->input_cfg, pre_process_variables_expand_fcn,
564 				       tplg_pp, &tplg_pp->input_cfg);
565 	if (err < 0) {
566 		fprintf(stderr, "Failed to expand pre-processor definitions in input config\n");
567 		goto err;
568 	}
569 #endif
570 
571 	err = pre_process_config(tplg_pp, tplg_pp->input_cfg);
572 	if (err < 0) {
573 		fprintf(stderr, "Unable to pre-process configuration\n");
574 		goto err;
575 	}
576 
577 	/* save config to output */
578 	err = snd_config_save(tplg_pp->output_cfg, tplg_pp->output);
579 	if (err < 0)
580 		fprintf(stderr, "failed to save pre-processed output file\n");
581 
582 err:
583 	snd_config_delete(top);
584 input_close:
585 	snd_input_close(in);
586 
587 	return err;
588 }
589