/*
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 "list.h"
#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;
}
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)) {
free(a);
return NULL;
}
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);
free(a);
return NULL;
}
s = sort_config(id2, s);
if (s == NULL || snd_config_add(dst, s)) {
if (s)
snd_config_delete(s);
snd_config_delete(dst);
free(a);
return NULL;
}
}
free(a);
return dst;
}
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;
}