| /* | |
| Copyright(c) 2019 Red Hat Inc. | |
| All rights reserved. | |
| This library is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU Lesser General Public License as | |
| published by the Free Software Foundation; either version 2.1 of | |
| the License, or (at your option) any later version. | |
| This program is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU Lesser General Public License for more details. | |
| Authors: Jaroslav Kysela <perex@perex.cz> | |
| */ | |
| #include "tplg_local.h" | |
| #define SAVE_ALLOC_SHIFT (13) /* 8192 bytes */ | |
| #define PRINT_ALLOC_SHIFT (10) /* 1024 bytes */ | |
| #define PRINT_BUF_SIZE_MAX (1024 * 1024) | |
| #define NEXT_CHUNK(val, shift) ((((val) >> (shift)) + 1) << (shift)) | |
| void tplg_buf_init(struct tplg_buf *buf) | |
| { | |
| buf->dst = NULL; | |
| buf->dst_len = 0; | |
| buf->printf_buf = NULL; | |
| buf->printf_buf_size = 0; | |
| } | |
| void tplg_buf_free(struct tplg_buf *buf) | |
| { | |
| free(buf->dst); | |
| free(buf->printf_buf); | |
| } | |
| char *tplg_buf_detach(struct tplg_buf *buf) | |
| { | |
| char *ret = buf->dst; | |
| free(buf->printf_buf); | |
| return ret; | |
| } | |
| int tplg_save_printf(struct tplg_buf *dst, const char *pfx, const char *fmt, ...) | |
| { | |
| va_list va; | |
| char *s; | |
| size_t n, l, t, pl; | |
| int ret = 0; | |
| if (pfx == NULL) | |
| pfx = ""; | |
| va_start(va, fmt); | |
| n = vsnprintf(dst->printf_buf, dst->printf_buf_size, fmt, va); | |
| va_end(va); | |
| if (n >= PRINT_BUF_SIZE_MAX) { | |
| ret = -EOVERFLOW; | |
| goto end; | |
| } | |
| if (n >= dst->printf_buf_size) { | |
| t = NEXT_CHUNK(n + 1, PRINT_ALLOC_SHIFT); | |
| s = realloc(dst->printf_buf, t); | |
| if (!s) { | |
| ret = -ENOMEM; | |
| goto end; | |
| } | |
| dst->printf_buf = s; | |
| dst->printf_buf_size = t; | |
| va_start(va, fmt); | |
| n = vsnprintf(dst->printf_buf, n + 1, fmt, va); | |
| va_end(va); | |
| } | |
| pl = strlen(pfx); | |
| l = dst->dst_len; | |
| t = l + pl + n + 1; | |
| /* allocate chunks */ | |
| if (dst->dst == NULL || | |
| (l >> SAVE_ALLOC_SHIFT) != (t >> SAVE_ALLOC_SHIFT)) { | |
| s = realloc(dst->dst, NEXT_CHUNK(t, SAVE_ALLOC_SHIFT)); | |
| if (s == NULL) { | |
| ret = -ENOMEM; | |
| goto end; | |
| } | |
| } else { | |
| s = dst->dst; | |
| } | |
| if (pl > 0) | |
| strcpy(s + l, pfx); | |
| strcpy(s + l + pl, dst->printf_buf); | |
| dst->dst = s; | |
| dst->dst_len = t - 1; | |
| end: | |
| return ret; | |
| } | |
| int tplg_nice_value_format(char *dst, size_t dst_size, unsigned int value) | |
| { | |
| if ((value % 1000) != 0) { | |
| if (value > 0xfffffff0) | |
| return snprintf(dst, dst_size, "%d", (int)value); | |
| if (value >= 0xffff0000) | |
| return snprintf(dst, dst_size, "0x%x", value); | |
| } | |
| return snprintf(dst, dst_size, "%u", value); | |
| } | |
| static int tplg_pprint_integer(snd_config_t *n, char **ret) | |
| { | |
| long lval; | |
| int err, type; | |
| char buf[16]; | |
| type = snd_config_get_type(n); | |
| if (type == SND_CONFIG_TYPE_INTEGER) { | |
| err = snd_config_get_integer(n, &lval); | |
| if (err < 0) | |
| return err; | |
| if (lval < INT_MIN || lval > UINT_MAX) | |
| return snd_config_get_ascii(n, ret); | |
| } else if (type == SND_CONFIG_TYPE_INTEGER64) { | |
| long long llval; | |
| err = snd_config_get_integer64(n, &llval); | |
| if (err < 0) | |
| return err; | |
| if (llval < INT_MIN || llval > UINT_MAX) | |
| return snd_config_get_ascii(n, ret); | |
| lval = llval; | |
| } else { | |
| lval = 0; | |
| } | |
| err = tplg_nice_value_format(buf, sizeof(buf), (unsigned int)lval); | |
| if (err < 0) | |
| return err; | |
| *ret = strdup(buf); | |
| if (*ret == NULL) | |
| return -ENOMEM; | |
| return 0; | |
| } | |
| static int _compar(const void *a, const void *b) | |
| { | |
| const snd_config_t *c1 = *(snd_config_t **)a; | |
| const snd_config_t *c2 = *(snd_config_t **)b; | |
| const char *id1, *id2; | |
| if (snd_config_get_id(c1, &id1)) return 0; | |
| if (snd_config_get_id(c2, &id2)) return 0; | |
| return strcmp(id1, id2); | |
| } | |
| static snd_config_t *sort_config(const char *id, snd_config_t *src) | |
| { | |
| snd_config_t *dst, **a; | |
| snd_config_iterator_t i, next; | |
| int index, array, count; | |
| if (snd_config_get_type(src) != SND_CONFIG_TYPE_COMPOUND) { | |
| if (snd_config_copy(&dst, src) >= 0) | |
| return dst; | |
| return NULL; | |
| } | |
| count = 0; | |
| snd_config_for_each(i, next, src) | |
| count++; | |
| a = malloc(sizeof(dst) * count); | |
| if (a == NULL) | |
| return NULL; | |
| array = snd_config_is_array(src); | |
| index = 0; | |
| snd_config_for_each(i, next, src) { | |
| snd_config_t *s = snd_config_iterator_entry(i); | |
| a[index++] = s; | |
| } | |
| if (array <= 0) | |
| qsort(a, count, sizeof(a[0]), _compar); | |
| if (snd_config_make_compound(&dst, id, count == 1)) | |
| goto lerr; | |
| for (index = 0; index < count; index++) { | |
| snd_config_t *s = a[index]; | |
| const char *id2; | |
| if (snd_config_get_id(s, &id2)) { | |
| snd_config_delete(dst); | |
| goto lerr; | |
| } | |
| s = sort_config(id2, s); | |
| if (s == NULL || snd_config_add(dst, s)) { | |
| if (s) | |
| snd_config_delete(s); | |
| snd_config_delete(dst); | |
| goto lerr; | |
| } | |
| } | |
| free(a); | |
| return dst; | |
| lerr: | |
| free(a); | |
| return NULL; | |
| } | |
| static int tplg_check_quoted(const unsigned char *p) | |
| { | |
| for ( ; *p != '\0'; p++) { | |
| switch (*p) { | |
| case ' ': | |
| case '=': | |
| case ';': | |
| case ',': | |
| case '.': | |
| case '{': | |
| case '}': | |
| case '\'': | |
| case '"': | |
| return 1; | |
| default: | |
| if (*p <= 31 || *p >= 127) | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| static int tplg_save_quoted(struct tplg_buf *dst, const char *str) | |
| { | |
| static const char nibble[16] = "0123456789abcdef"; | |
| unsigned char *p, *d, *t; | |
| int c; | |
| d = t = alloca(strlen(str) * 5 + 1 + 1); | |
| for (p = (unsigned char *)str; *p != '\0'; p++) { | |
| c = *p; | |
| switch (c) { | |
| case '\n': | |
| *t++ = '\\'; | |
| *t++ = 'n'; | |
| break; | |
| case '\t': | |
| *t++ = '\\'; | |
| *t++ = 't'; | |
| break; | |
| case '\v': | |
| *t++ = '\\'; | |
| *t++ = 'v'; | |
| break; | |
| case '\b': | |
| *t++ = '\\'; | |
| *t++ = 'b'; | |
| break; | |
| case '\r': | |
| *t++ = '\\'; | |
| *t++ = 'r'; | |
| break; | |
| case '\f': | |
| *t++ = '\\'; | |
| *t++ = 'f'; | |
| break; | |
| case '\'': | |
| *t++ = '\\'; | |
| *t++ = c; | |
| break; | |
| default: | |
| if (c >= 32 && c <= 126) { | |
| *t++ = c; | |
| } else { | |
| *t++ = '\\'; | |
| *t++ = 'x'; | |
| *t++ = nibble[(c >> 4) & 0x0f]; | |
| *t++ = nibble[(c >> 0) & 0x0f]; | |
| } | |
| break; | |
| } | |
| } | |
| *t = '\0'; | |
| return tplg_save_printf(dst, NULL, "'%s'", d); | |
| } | |
| static int tplg_save_string(struct tplg_buf *dst, const char *str, int id) | |
| { | |
| const unsigned char *p = (const unsigned char *)str; | |
| if (!p || !*p) | |
| return tplg_save_printf(dst, NULL, "''"); | |
| if (!id && ((*p >= '0' && *p <= '9') || *p == '-')) | |
| return tplg_save_quoted(dst, str); | |
| if (tplg_check_quoted(p)) | |
| return tplg_save_quoted(dst, str); | |
| return tplg_save_printf(dst, NULL, "%s", str); | |
| } | |
| static int save_config(struct tplg_buf *dst, int level, const char *delim, snd_config_t *src) | |
| { | |
| snd_config_iterator_t i, next; | |
| snd_config_t *s; | |
| const char *id; | |
| char *pfx; | |
| unsigned int count; | |
| int type, err, quoted, array; | |
| if (delim == NULL) | |
| delim = ""; | |
| type = snd_config_get_type(src); | |
| if (type != SND_CONFIG_TYPE_COMPOUND) { | |
| char *val; | |
| if (type == SND_CONFIG_TYPE_INTEGER || | |
| type == SND_CONFIG_TYPE_INTEGER64) { | |
| err = tplg_pprint_integer(src, &val); | |
| } else { | |
| err = snd_config_get_ascii(src, &val); | |
| } | |
| if (err < 0) | |
| return err; | |
| if (type == SND_CONFIG_TYPE_STRING) { | |
| /* hexa array pretty print */ | |
| id = strchr(val, '\n'); | |
| if (id) { | |
| err = tplg_save_printf(dst, NULL, "\n"); | |
| if (err < 0) | |
| goto retval; | |
| for (id++; *id == '\t'; id++) { | |
| err = tplg_save_printf(dst, NULL, "\t"); | |
| if (err < 0) | |
| goto retval; | |
| } | |
| delim = ""; | |
| } | |
| err = tplg_save_printf(dst, NULL, "%s'%s'\n", delim, val); | |
| } else { | |
| err = tplg_save_printf(dst, NULL, "%s%s\n", delim, val); | |
| } | |
| retval: | |
| free(val); | |
| return err; | |
| } | |
| count = 0; | |
| quoted = 0; | |
| array = snd_config_is_array(src); | |
| s = NULL; | |
| snd_config_for_each(i, next, src) { | |
| s = snd_config_iterator_entry(i); | |
| err = snd_config_get_id(s, &id); | |
| if (err < 0) | |
| return err; | |
| if (!quoted && tplg_check_quoted((unsigned char *)id)) | |
| quoted = 1; | |
| count++; | |
| } | |
| if (count == 0) | |
| return 0; | |
| if (count == 1) { | |
| err = snd_config_get_id(s, &id); | |
| if (err >= 0 && level > 0) | |
| err = tplg_save_printf(dst, NULL, "."); | |
| if (err >= 0) | |
| err = tplg_save_string(dst, id, 1); | |
| if (err >= 0) | |
| err = save_config(dst, level, " ", s); | |
| return err; | |
| } | |
| pfx = alloca(level + 1); | |
| memset(pfx, '\t', level); | |
| pfx[level] = '\0'; | |
| if (level > 0) { | |
| err = tplg_save_printf(dst, NULL, "%s%s\n", delim, | |
| array > 0 ? "[" : "{"); | |
| if (err < 0) | |
| return err; | |
| } | |
| snd_config_for_each(i, next, src) { | |
| s = snd_config_iterator_entry(i); | |
| const char *id; | |
| err = snd_config_get_id(s, &id); | |
| if (err < 0) | |
| return err; | |
| err = tplg_save_printf(dst, pfx, ""); | |
| if (err < 0) | |
| return err; | |
| if (array <= 0) { | |
| delim = " "; | |
| if (quoted) { | |
| err = tplg_save_quoted(dst, id); | |
| } else { | |
| err = tplg_save_string(dst, id, 1); | |
| } | |
| if (err < 0) | |
| return err; | |
| } else { | |
| delim = ""; | |
| } | |
| err = save_config(dst, level + 1, delim, s); | |
| if (err < 0) | |
| return err; | |
| } | |
| if (level > 0) { | |
| pfx[level - 1] = '\0'; | |
| err = tplg_save_printf(dst, pfx, "%s\n", | |
| array > 0 ? "]" : "}"); | |
| if (err < 0) | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static int tplg_save(snd_tplg_t *tplg, struct tplg_buf *dst, | |
| int gindex, const char *prefix) | |
| { | |
| struct tplg_table *tptr; | |
| struct tplg_elem *elem; | |
| struct list_head *list, *pos; | |
| char pfx2[16]; | |
| unsigned int index; | |
| int err, count; | |
| snprintf(pfx2, sizeof(pfx2), "%s\t", prefix ?: ""); | |
| /* write all blocks */ | |
| for (index = 0; index < tplg_table_items; index++) { | |
| tptr = &tplg_table[index]; | |
| list = (struct list_head *)((void *)tplg + tptr->loff); | |
| /* count elements */ | |
| count = 0; | |
| list_for_each(pos, list) { | |
| elem = list_entry(pos, struct tplg_elem, list); | |
| if (gindex >= 0 && elem->index != gindex) | |
| continue; | |
| if (tptr->save == NULL && tptr->gsave == NULL) { | |
| SNDERR("unable to create %s block (no callback)", | |
| tptr->id); | |
| err = -ENXIO; | |
| goto _err; | |
| } | |
| if (tptr->save) | |
| count++; | |
| } | |
| if (count == 0) | |
| continue; | |
| if (count > 1) { | |
| err = tplg_save_printf(dst, prefix, "%s {\n", | |
| elem->table ? | |
| elem->table->id : "_NOID_"); | |
| } else { | |
| err = tplg_save_printf(dst, prefix, "%s.", | |
| elem->table ? | |
| elem->table->id : "_NOID_"); | |
| } | |
| if (err < 0) | |
| goto _err; | |
| list_for_each(pos, list) { | |
| elem = list_entry(pos, struct tplg_elem, list); | |
| if (gindex >= 0 && elem->index != gindex) | |
| continue; | |
| if (count > 1) { | |
| err = tplg_save_printf(dst, pfx2, ""); | |
| if (err < 0) | |
| goto _err; | |
| } | |
| err = tptr->save(tplg, elem, dst, count > 1 ? pfx2 : prefix); | |
| if (err < 0) { | |
| SNDERR("failed to save %s elements: %s", | |
| tptr->id, snd_strerror(-err)); | |
| goto _err; | |
| } | |
| } | |
| if (count > 1) { | |
| err = tplg_save_printf(dst, prefix, "}\n"); | |
| if (err < 0) | |
| goto _err; | |
| } | |
| } | |
| /* save globals */ | |
| for (index = 0; index < tplg_table_items; index++) { | |
| tptr = &tplg_table[index]; | |
| if (tptr->gsave) { | |
| err = tptr->gsave(tplg, gindex, dst, prefix); | |
| if (err < 0) | |
| goto _err; | |
| } | |
| } | |
| return 0; | |
| _err: | |
| return err; | |
| } | |
| static int tplg_index_compar(const void *a, const void *b) | |
| { | |
| const int *a1 = a, *b1 = b; | |
| return *a1 - *b1; | |
| } | |
| static int tplg_index_groups(snd_tplg_t *tplg, int **indexes) | |
| { | |
| struct tplg_table *tptr; | |
| struct tplg_elem *elem; | |
| struct list_head *list, *pos; | |
| unsigned int index, j, count, size; | |
| int *a, *b; | |
| count = 0; | |
| size = 16; | |
| a = malloc(size * sizeof(a[0])); | |
| for (index = 0; index < tplg_table_items; index++) { | |
| tptr = &tplg_table[index]; | |
| list = (struct list_head *)((void *)tplg + tptr->loff); | |
| list_for_each(pos, list) { | |
| elem = list_entry(pos, struct tplg_elem, list); | |
| for (j = 0; j < count; j++) { | |
| if (a[j] == elem->index) | |
| break; | |
| } | |
| if (j < count) | |
| continue; | |
| if (count + 1 >= size) { | |
| size += 8; | |
| b = realloc(a, size * sizeof(a[0])); | |
| if (b == NULL) { | |
| free(a); | |
| return -ENOMEM; | |
| } | |
| a = b; | |
| } | |
| a[count++] = elem->index; | |
| } | |
| } | |
| a[count] = -1; | |
| qsort(a, count, sizeof(a[0]), tplg_index_compar); | |
| *indexes = a; | |
| return 0; | |
| } | |
| int snd_tplg_save(snd_tplg_t *tplg, char **dst, int flags) | |
| { | |
| struct tplg_buf buf, buf2; | |
| snd_input_t *in; | |
| snd_config_t *top, *top2; | |
| int *indexes, *a; | |
| int err; | |
| assert(tplg); | |
| assert(dst); | |
| *dst = NULL; | |
| tplg_buf_init(&buf); | |
| if (flags & SND_TPLG_SAVE_GROUPS) { | |
| err = tplg_index_groups(tplg, &indexes); | |
| if (err < 0) | |
| return err; | |
| for (a = indexes; err >= 0 && *a >= 0; a++) { | |
| err = tplg_save_printf(&buf, NULL, | |
| "IndexGroup.%d {\n", | |
| *a); | |
| if (err >= 0) | |
| err = tplg_save(tplg, &buf, *a, "\t"); | |
| if (err >= 0) | |
| err = tplg_save_printf(&buf, NULL, "}\n"); | |
| } | |
| free(indexes); | |
| } else { | |
| err = tplg_save(tplg, &buf, -1, NULL); | |
| } | |
| if (err < 0) | |
| goto _err; | |
| if (buf.dst == NULL) { | |
| err = -EINVAL; | |
| goto _err; | |
| } | |
| if (flags & SND_TPLG_SAVE_NOCHECK) { | |
| *dst = tplg_buf_detach(&buf); | |
| return 0; | |
| } | |
| /* always load configuration - check */ | |
| err = snd_input_buffer_open(&in, buf.dst, strlen(buf.dst)); | |
| if (err < 0) { | |
| SNDERR("could not create input buffer"); | |
| goto _err; | |
| } | |
| err = snd_config_top(&top); | |
| if (err < 0) { | |
| snd_input_close(in); | |
| goto _err; | |
| } | |
| err = snd_config_load(top, in); | |
| snd_input_close(in); | |
| if (err < 0) { | |
| SNDERR("could not load configuration"); | |
| snd_config_delete(top); | |
| goto _err; | |
| } | |
| if (flags & SND_TPLG_SAVE_SORT) { | |
| top2 = sort_config(NULL, top); | |
| if (top2 == NULL) { | |
| SNDERR("could not sort configuration"); | |
| snd_config_delete(top); | |
| err = -EINVAL; | |
| goto _err; | |
| } | |
| snd_config_delete(top); | |
| top = top2; | |
| } | |
| tplg_buf_init(&buf2); | |
| err = save_config(&buf2, 0, NULL, top); | |
| snd_config_delete(top); | |
| if (err < 0) { | |
| SNDERR("could not save configuration"); | |
| goto _err; | |
| } | |
| tplg_buf_free(&buf); | |
| *dst = tplg_buf_detach(&buf2); | |
| return 0; | |
| _err: | |
| tplg_buf_free(&buf); | |
| *dst = NULL; | |
| return err; | |
| } |