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(®ex, 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(®ex, 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