xref: /third_party/alsa-lib/src/topology/save.c (revision d5ac70f0)
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
25void 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
33void tplg_buf_free(struct tplg_buf *buf)
34{
35	free(buf->dst);
36	free(buf->printf_buf);
37}
38
39char *tplg_buf_detach(struct tplg_buf *buf)
40{
41	char *ret = buf->dst;
42	free(buf->printf_buf);
43	return ret;
44}
45
46int 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;
99end:
100	return ret;
101}
102
103int 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
114static 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
147static 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
157static 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;
201lerr:
202	free(a);
203	return NULL;
204}
205
206static 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
229static 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
283static 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
299static 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		}
340retval:
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
420static 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
508static int tplg_index_compar(const void *a, const void *b)
509{
510	const int *a1 = a, *b1 = b;
511	return *a1 - *b1;
512}
513
514static 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
557int 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