1 /*
2 Copyright(c) 2019 Red Hat Inc.
3 All rights reserved.
4
5 This library is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as
7 published by the Free Software Foundation; either version 2.1 of
8 the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
14
15 Authors: Jaroslav Kysela <perex@perex.cz>
16 */
17
18 #include "tplg_local.h"
19
20 #define SAVE_ALLOC_SHIFT (13) /* 8192 bytes */
21 #define PRINT_ALLOC_SHIFT (10) /* 1024 bytes */
22 #define PRINT_BUF_SIZE_MAX (1024 * 1024)
23 #define NEXT_CHUNK(val, shift) ((((val) >> (shift)) + 1) << (shift))
24
tplg_buf_init(struct tplg_buf * buf)25 void tplg_buf_init(struct tplg_buf *buf)
26 {
27 buf->dst = NULL;
28 buf->dst_len = 0;
29 buf->printf_buf = NULL;
30 buf->printf_buf_size = 0;
31 }
32
tplg_buf_free(struct tplg_buf * buf)33 void tplg_buf_free(struct tplg_buf *buf)
34 {
35 free(buf->dst);
36 free(buf->printf_buf);
37 }
38
tplg_buf_detach(struct tplg_buf * buf)39 char *tplg_buf_detach(struct tplg_buf *buf)
40 {
41 char *ret = buf->dst;
42 free(buf->printf_buf);
43 return ret;
44 }
45
tplg_save_printf(struct tplg_buf * dst,const char * pfx,const char * fmt,...)46 int tplg_save_printf(struct tplg_buf *dst, const char *pfx, const char *fmt, ...)
47 {
48 va_list va;
49 char *s;
50 size_t n, l, t, pl;
51 int ret = 0;
52
53 if (pfx == NULL)
54 pfx = "";
55
56 va_start(va, fmt);
57 n = vsnprintf(dst->printf_buf, dst->printf_buf_size, fmt, va);
58 va_end(va);
59
60 if (n >= PRINT_BUF_SIZE_MAX) {
61 ret = -EOVERFLOW;
62 goto end;
63 }
64
65 if (n >= dst->printf_buf_size) {
66 t = NEXT_CHUNK(n + 1, PRINT_ALLOC_SHIFT);
67 s = realloc(dst->printf_buf, t);
68 if (!s) {
69 ret = -ENOMEM;
70 goto end;
71 }
72 dst->printf_buf = s;
73 dst->printf_buf_size = t;
74 va_start(va, fmt);
75 n = vsnprintf(dst->printf_buf, n + 1, fmt, va);
76 va_end(va);
77 }
78
79 pl = strlen(pfx);
80 l = dst->dst_len;
81 t = l + pl + n + 1;
82 /* allocate chunks */
83 if (dst->dst == NULL ||
84 (l >> SAVE_ALLOC_SHIFT) != (t >> SAVE_ALLOC_SHIFT)) {
85 s = realloc(dst->dst, NEXT_CHUNK(t, SAVE_ALLOC_SHIFT));
86 if (s == NULL) {
87 ret = -ENOMEM;
88 goto end;
89 }
90 } else {
91 s = dst->dst;
92 }
93
94 if (pl > 0)
95 strcpy(s + l, pfx);
96 strcpy(s + l + pl, dst->printf_buf);
97 dst->dst = s;
98 dst->dst_len = t - 1;
99 end:
100 return ret;
101 }
102
tplg_nice_value_format(char * dst,size_t dst_size,unsigned int value)103 int tplg_nice_value_format(char *dst, size_t dst_size, unsigned int value)
104 {
105 if ((value % 1000) != 0) {
106 if (value > 0xfffffff0)
107 return snprintf(dst, dst_size, "%d", (int)value);
108 if (value >= 0xffff0000)
109 return snprintf(dst, dst_size, "0x%x", value);
110 }
111 return snprintf(dst, dst_size, "%u", value);
112 }
113
tplg_pprint_integer(snd_config_t * n,char ** ret)114 static int tplg_pprint_integer(snd_config_t *n, char **ret)
115 {
116 long lval;
117 int err, type;
118 char buf[16];
119
120 type = snd_config_get_type(n);
121 if (type == SND_CONFIG_TYPE_INTEGER) {
122 err = snd_config_get_integer(n, &lval);
123 if (err < 0)
124 return err;
125 if (lval < INT_MIN || lval > UINT_MAX)
126 return snd_config_get_ascii(n, ret);
127 } else if (type == SND_CONFIG_TYPE_INTEGER64) {
128 long long llval;
129 err = snd_config_get_integer64(n, &llval);
130 if (err < 0)
131 return err;
132 if (llval < INT_MIN || llval > UINT_MAX)
133 return snd_config_get_ascii(n, ret);
134 lval = llval;
135 } else {
136 lval = 0;
137 }
138 err = tplg_nice_value_format(buf, sizeof(buf), (unsigned int)lval);
139 if (err < 0)
140 return err;
141 *ret = strdup(buf);
142 if (*ret == NULL)
143 return -ENOMEM;
144 return 0;
145 }
146
_compar(const void * a,const void * b)147 static int _compar(const void *a, const void *b)
148 {
149 const snd_config_t *c1 = *(snd_config_t **)a;
150 const snd_config_t *c2 = *(snd_config_t **)b;
151 const char *id1, *id2;
152 if (snd_config_get_id(c1, &id1)) return 0;
153 if (snd_config_get_id(c2, &id2)) return 0;
154 return strcmp(id1, id2);
155 }
156
sort_config(const char * id,snd_config_t * src)157 static snd_config_t *sort_config(const char *id, snd_config_t *src)
158 {
159 snd_config_t *dst, **a;
160 snd_config_iterator_t i, next;
161 int index, array, count;
162
163 if (snd_config_get_type(src) != SND_CONFIG_TYPE_COMPOUND) {
164 if (snd_config_copy(&dst, src) >= 0)
165 return dst;
166 return NULL;
167 }
168 count = 0;
169 snd_config_for_each(i, next, src)
170 count++;
171 a = malloc(sizeof(dst) * count);
172 if (a == NULL)
173 return NULL;
174 array = snd_config_is_array(src);
175 index = 0;
176 snd_config_for_each(i, next, src) {
177 snd_config_t *s = snd_config_iterator_entry(i);
178 a[index++] = s;
179 }
180 if (array <= 0)
181 qsort(a, count, sizeof(a[0]), _compar);
182 if (snd_config_make_compound(&dst, id, count == 1))
183 goto lerr;
184 for (index = 0; index < count; index++) {
185 snd_config_t *s = a[index];
186 const char *id2;
187 if (snd_config_get_id(s, &id2)) {
188 snd_config_delete(dst);
189 goto lerr;
190 }
191 s = sort_config(id2, s);
192 if (s == NULL || snd_config_add(dst, s)) {
193 if (s)
194 snd_config_delete(s);
195 snd_config_delete(dst);
196 goto lerr;
197 }
198 }
199 free(a);
200 return dst;
201 lerr:
202 free(a);
203 return NULL;
204 }
205
tplg_check_quoted(const unsigned char * p)206 static int tplg_check_quoted(const unsigned char *p)
207 {
208 for ( ; *p != '\0'; p++) {
209 switch (*p) {
210 case ' ':
211 case '=':
212 case ';':
213 case ',':
214 case '.':
215 case '{':
216 case '}':
217 case '\'':
218 case '"':
219 return 1;
220 default:
221 if (*p <= 31 || *p >= 127)
222 return 1;
223
224 }
225 }
226 return 0;
227 }
228
tplg_save_quoted(struct tplg_buf * dst,const char * str)229 static int tplg_save_quoted(struct tplg_buf *dst, const char *str)
230 {
231 static const char nibble[16] = "0123456789abcdef";
232 unsigned char *p, *d, *t;
233 int c;
234
235 d = t = alloca(strlen(str) * 5 + 1 + 1);
236 for (p = (unsigned char *)str; *p != '\0'; p++) {
237 c = *p;
238 switch (c) {
239 case '\n':
240 *t++ = '\\';
241 *t++ = 'n';
242 break;
243 case '\t':
244 *t++ = '\\';
245 *t++ = 't';
246 break;
247 case '\v':
248 *t++ = '\\';
249 *t++ = 'v';
250 break;
251 case '\b':
252 *t++ = '\\';
253 *t++ = 'b';
254 break;
255 case '\r':
256 *t++ = '\\';
257 *t++ = 'r';
258 break;
259 case '\f':
260 *t++ = '\\';
261 *t++ = 'f';
262 break;
263 case '\'':
264 *t++ = '\\';
265 *t++ = c;
266 break;
267 default:
268 if (c >= 32 && c <= 126) {
269 *t++ = c;
270 } else {
271 *t++ = '\\';
272 *t++ = 'x';
273 *t++ = nibble[(c >> 4) & 0x0f];
274 *t++ = nibble[(c >> 0) & 0x0f];
275 }
276 break;
277 }
278 }
279 *t = '\0';
280 return tplg_save_printf(dst, NULL, "'%s'", d);
281 }
282
tplg_save_string(struct tplg_buf * dst,const char * str,int id)283 static int tplg_save_string(struct tplg_buf *dst, const char *str, int id)
284 {
285 const unsigned char *p = (const unsigned char *)str;
286
287 if (!p || !*p)
288 return tplg_save_printf(dst, NULL, "''");
289
290 if (!id && ((*p >= '0' && *p <= '9') || *p == '-'))
291 return tplg_save_quoted(dst, str);
292
293 if (tplg_check_quoted(p))
294 return tplg_save_quoted(dst, str);
295
296 return tplg_save_printf(dst, NULL, "%s", str);
297 }
298
save_config(struct tplg_buf * dst,int level,const char * delim,snd_config_t * src)299 static int save_config(struct tplg_buf *dst, int level, const char *delim, snd_config_t *src)
300 {
301 snd_config_iterator_t i, next;
302 snd_config_t *s;
303 const char *id;
304 char *pfx;
305 unsigned int count;
306 int type, err, quoted, array;
307
308 if (delim == NULL)
309 delim = "";
310
311 type = snd_config_get_type(src);
312 if (type != SND_CONFIG_TYPE_COMPOUND) {
313 char *val;
314 if (type == SND_CONFIG_TYPE_INTEGER ||
315 type == SND_CONFIG_TYPE_INTEGER64) {
316 err = tplg_pprint_integer(src, &val);
317 } else {
318 err = snd_config_get_ascii(src, &val);
319 }
320 if (err < 0)
321 return err;
322 if (type == SND_CONFIG_TYPE_STRING) {
323 /* hexa array pretty print */
324 id = strchr(val, '\n');
325 if (id) {
326 err = tplg_save_printf(dst, NULL, "\n");
327 if (err < 0)
328 goto retval;
329 for (id++; *id == '\t'; id++) {
330 err = tplg_save_printf(dst, NULL, "\t");
331 if (err < 0)
332 goto retval;
333 }
334 delim = "";
335 }
336 err = tplg_save_printf(dst, NULL, "%s'%s'\n", delim, val);
337 } else {
338 err = tplg_save_printf(dst, NULL, "%s%s\n", delim, val);
339 }
340 retval:
341 free(val);
342 return err;
343 }
344
345 count = 0;
346 quoted = 0;
347 array = snd_config_is_array(src);
348 s = NULL;
349 snd_config_for_each(i, next, src) {
350 s = snd_config_iterator_entry(i);
351 err = snd_config_get_id(s, &id);
352 if (err < 0)
353 return err;
354 if (!quoted && tplg_check_quoted((unsigned char *)id))
355 quoted = 1;
356 count++;
357 }
358 if (count == 0)
359 return 0;
360
361 if (count == 1) {
362 err = snd_config_get_id(s, &id);
363 if (err >= 0 && level > 0)
364 err = tplg_save_printf(dst, NULL, ".");
365 if (err >= 0)
366 err = tplg_save_string(dst, id, 1);
367 if (err >= 0)
368 err = save_config(dst, level, " ", s);
369 return err;
370 }
371
372 pfx = alloca(level + 1);
373 memset(pfx, '\t', level);
374 pfx[level] = '\0';
375
376 if (level > 0) {
377 err = tplg_save_printf(dst, NULL, "%s%s\n", delim,
378 array > 0 ? "[" : "{");
379 if (err < 0)
380 return err;
381 }
382
383 snd_config_for_each(i, next, src) {
384 s = snd_config_iterator_entry(i);
385 const char *id;
386 err = snd_config_get_id(s, &id);
387 if (err < 0)
388 return err;
389 err = tplg_save_printf(dst, pfx, "");
390 if (err < 0)
391 return err;
392 if (array <= 0) {
393 delim = " ";
394 if (quoted) {
395 err = tplg_save_quoted(dst, id);
396 } else {
397 err = tplg_save_string(dst, id, 1);
398 }
399 if (err < 0)
400 return err;
401 } else {
402 delim = "";
403 }
404 err = save_config(dst, level + 1, delim, s);
405 if (err < 0)
406 return err;
407 }
408
409 if (level > 0) {
410 pfx[level - 1] = '\0';
411 err = tplg_save_printf(dst, pfx, "%s\n",
412 array > 0 ? "]" : "}");
413 if (err < 0)
414 return err;
415 }
416
417 return 0;
418 }
419
tplg_save(snd_tplg_t * tplg,struct tplg_buf * dst,int gindex,const char * prefix)420 static int tplg_save(snd_tplg_t *tplg, struct tplg_buf *dst,
421 int gindex, const char *prefix)
422 {
423 struct tplg_table *tptr;
424 struct tplg_elem *elem;
425 struct list_head *list, *pos;
426 char pfx2[16];
427 unsigned int index;
428 int err, count;
429
430 snprintf(pfx2, sizeof(pfx2), "%s\t", prefix ?: "");
431
432 /* write all blocks */
433 for (index = 0; index < tplg_table_items; index++) {
434 tptr = &tplg_table[index];
435 list = (struct list_head *)((void *)tplg + tptr->loff);
436
437 /* count elements */
438 count = 0;
439 list_for_each(pos, list) {
440 elem = list_entry(pos, struct tplg_elem, list);
441 if (gindex >= 0 && elem->index != gindex)
442 continue;
443 if (tptr->save == NULL && tptr->gsave == NULL) {
444 SNDERR("unable to create %s block (no callback)",
445 tptr->id);
446 err = -ENXIO;
447 goto _err;
448 }
449 if (tptr->save)
450 count++;
451 }
452
453 if (count == 0)
454 continue;
455
456 if (count > 1) {
457 err = tplg_save_printf(dst, prefix, "%s {\n",
458 elem->table ?
459 elem->table->id : "_NOID_");
460 } else {
461 err = tplg_save_printf(dst, prefix, "%s.",
462 elem->table ?
463 elem->table->id : "_NOID_");
464 }
465
466 if (err < 0)
467 goto _err;
468
469 list_for_each(pos, list) {
470 elem = list_entry(pos, struct tplg_elem, list);
471 if (gindex >= 0 && elem->index != gindex)
472 continue;
473 if (count > 1) {
474 err = tplg_save_printf(dst, pfx2, "");
475 if (err < 0)
476 goto _err;
477 }
478 err = tptr->save(tplg, elem, dst, count > 1 ? pfx2 : prefix);
479 if (err < 0) {
480 SNDERR("failed to save %s elements: %s",
481 tptr->id, snd_strerror(-err));
482 goto _err;
483 }
484 }
485 if (count > 1) {
486 err = tplg_save_printf(dst, prefix, "}\n");
487 if (err < 0)
488 goto _err;
489 }
490 }
491
492 /* save globals */
493 for (index = 0; index < tplg_table_items; index++) {
494 tptr = &tplg_table[index];
495 if (tptr->gsave) {
496 err = tptr->gsave(tplg, gindex, dst, prefix);
497 if (err < 0)
498 goto _err;
499 }
500 }
501
502 return 0;
503
504 _err:
505 return err;
506 }
507
tplg_index_compar(const void * a,const void * b)508 static int tplg_index_compar(const void *a, const void *b)
509 {
510 const int *a1 = a, *b1 = b;
511 return *a1 - *b1;
512 }
513
tplg_index_groups(snd_tplg_t * tplg,int ** indexes)514 static int tplg_index_groups(snd_tplg_t *tplg, int **indexes)
515 {
516 struct tplg_table *tptr;
517 struct tplg_elem *elem;
518 struct list_head *list, *pos;
519 unsigned int index, j, count, size;
520 int *a, *b;
521
522 count = 0;
523 size = 16;
524 a = malloc(size * sizeof(a[0]));
525
526 for (index = 0; index < tplg_table_items; index++) {
527 tptr = &tplg_table[index];
528 list = (struct list_head *)((void *)tplg + tptr->loff);
529 list_for_each(pos, list) {
530 elem = list_entry(pos, struct tplg_elem, list);
531 for (j = 0; j < count; j++) {
532 if (a[j] == elem->index)
533 break;
534 }
535 if (j < count)
536 continue;
537 if (count + 1 >= size) {
538 size += 8;
539 b = realloc(a, size * sizeof(a[0]));
540 if (b == NULL) {
541 free(a);
542 return -ENOMEM;
543 }
544 a = b;
545 }
546 a[count++] = elem->index;
547 }
548 }
549 a[count] = -1;
550
551 qsort(a, count, sizeof(a[0]), tplg_index_compar);
552
553 *indexes = a;
554 return 0;
555 }
556
snd_tplg_save(snd_tplg_t * tplg,char ** dst,int flags)557 int snd_tplg_save(snd_tplg_t *tplg, char **dst, int flags)
558 {
559 struct tplg_buf buf, buf2;
560 snd_input_t *in;
561 snd_config_t *top, *top2;
562 int *indexes, *a;
563 int err;
564
565 assert(tplg);
566 assert(dst);
567 *dst = NULL;
568
569 tplg_buf_init(&buf);
570
571 if (flags & SND_TPLG_SAVE_GROUPS) {
572 err = tplg_index_groups(tplg, &indexes);
573 if (err < 0)
574 return err;
575 for (a = indexes; err >= 0 && *a >= 0; a++) {
576 err = tplg_save_printf(&buf, NULL,
577 "IndexGroup.%d {\n",
578 *a);
579 if (err >= 0)
580 err = tplg_save(tplg, &buf, *a, "\t");
581 if (err >= 0)
582 err = tplg_save_printf(&buf, NULL, "}\n");
583 }
584 free(indexes);
585 } else {
586 err = tplg_save(tplg, &buf, -1, NULL);
587 }
588
589 if (err < 0)
590 goto _err;
591
592 if (buf.dst == NULL) {
593 err = -EINVAL;
594 goto _err;
595 }
596
597 if (flags & SND_TPLG_SAVE_NOCHECK) {
598 *dst = tplg_buf_detach(&buf);
599 return 0;
600 }
601
602 /* always load configuration - check */
603 err = snd_input_buffer_open(&in, buf.dst, strlen(buf.dst));
604 if (err < 0) {
605 SNDERR("could not create input buffer");
606 goto _err;
607 }
608
609 err = snd_config_top(&top);
610 if (err < 0) {
611 snd_input_close(in);
612 goto _err;
613 }
614
615 err = snd_config_load(top, in);
616 snd_input_close(in);
617 if (err < 0) {
618 SNDERR("could not load configuration");
619 snd_config_delete(top);
620 goto _err;
621 }
622
623 if (flags & SND_TPLG_SAVE_SORT) {
624 top2 = sort_config(NULL, top);
625 if (top2 == NULL) {
626 SNDERR("could not sort configuration");
627 snd_config_delete(top);
628 err = -EINVAL;
629 goto _err;
630 }
631 snd_config_delete(top);
632 top = top2;
633 }
634
635 tplg_buf_init(&buf2);
636 err = save_config(&buf2, 0, NULL, top);
637 snd_config_delete(top);
638 if (err < 0) {
639 SNDERR("could not save configuration");
640 goto _err;
641 }
642
643 tplg_buf_free(&buf);
644 *dst = tplg_buf_detach(&buf2);
645 return 0;
646
647 _err:
648 tplg_buf_free(&buf);
649 *dst = NULL;
650 return err;
651 }
652