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