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