1/* 2 Copyright(c) 2019 Red Hat Inc. 3 Copyright(c) 2014-2015 Intel Corporation 4 Copyright(c) 2010-2011 Texas Instruments Incorporated, 5 All rights reserved. 6 7 This program is free software; you can redistribute it and/or modify 8 it under the terms of version 2 of the GNU General Public License as 9 published by the Free Software Foundation. 10 11 This program is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program; if not, write to the Free Software 18 Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. 19 The full GNU General Public License is included in this distribution 20 in the file called LICENSE.GPL. 21*/ 22 23#include "aconfig.h" 24#include <stdbool.h> 25#include <stdlib.h> 26#include <stdio.h> 27#include <stdint.h> 28#include <fcntl.h> 29#include <unistd.h> 30#include <errno.h> 31#include <string.h> 32#include <sys/stat.h> 33#include <getopt.h> 34#include <assert.h> 35 36#include <alsa/asoundlib.h> 37#include <alsa/topology.h> 38#include "gettext.h" 39#ifdef ENABLE_NLS 40#include <locale.h> 41#endif 42#include "version.h" 43#include "topology.h" 44 45bool pre_process_config = false; 46 47static snd_output_t *log; 48 49static void usage(const char *name) 50{ 51 printf( 52_("Usage: %s [OPTIONS]...\n" 53"\n" 54"-h, --help help\n" 55"-c, --compile=FILE compile configuration file\n" 56"-p, --pre-process pre-process Topology2.0 configuration file before compilation\n" 57"-P, --pre-process=FILE pre-process Topology2.0 configuration file\n" 58"-d, --decode=FILE decode binary topology file\n" 59"-n, --normalize=FILE normalize configuration file\n" 60"-u, --dump=FILE dump (reparse) configuration file\n" 61"-v, --verbose=LEVEL set verbosity level (0...1)\n" 62"-o, --output=FILE set output file\n" 63#if SND_LIB_VER(1, 2, 5) < SND_LIB_VERSION 64"-D, --define=ARGS define variables (VAR1=VAL1[,VAR2=VAL2] ...)\n" 65" (may be used multiple times)\n" 66"-I, --inc-dir=DIR set include path\n" 67#endif 68"-s, --sort sort the identifiers in the normalized output\n" 69"-g, --group save configuration by group indexes\n" 70"-x, --nocheck save configuration without additional integrity checks\n" 71"-z, --dapm-nosort do not sort the DAPM widgets\n" 72"-V, --version print version\n" 73), name); 74} 75 76static void version(const char *name) 77{ 78 printf( 79_("%s version %s\n" 80"libasound version %s\n" 81"libatopology version %s\n" 82), name, SND_UTIL_VERSION_STR, 83 snd_asoundlib_version(), snd_tplg_version()); 84} 85 86static int load(const char *source_file, void **dst, size_t *dst_size) 87{ 88 int fd; 89 void *buf, *buf2; 90 size_t size, pos; 91 ssize_t r; 92 93 if (strcmp(source_file, "-") == 0) { 94 fd = fileno(stdin); 95 } else { 96 fd = open(source_file, O_RDONLY); 97 if (fd < 0) { 98 fprintf(stderr, _("Unable to open input file '%s': %s\n"), 99 source_file, strerror(errno)); 100 return 1; 101 } 102 } 103 104 size = 16*1024; 105 pos = 0; 106 buf = malloc(size); 107 if (buf == NULL) 108 goto _nomem; 109 while (1) { 110 r = read(fd, buf + pos, size - pos); 111 if (r < 0 && (errno == EAGAIN || errno == EINTR)) 112 continue; 113 if (r <= 0) 114 break; 115 pos += r; 116 size += 8*1024; 117 buf2 = realloc(buf, size); 118 if (buf2 == NULL) 119 goto _nomem; 120 buf = buf2; 121 } 122 if (r < 0) { 123 fprintf(stderr, _("Read error: %s\n"), strerror(errno)); 124 goto _err; 125 } 126 127 if (fd != fileno(stdin)) 128 close(fd); 129 130 *dst = buf; 131 *dst_size = pos; 132 return 0; 133 134_nomem: 135 fprintf(stderr, _("No enough memory\n")); 136_err: 137 if (fd != fileno(stdin)) 138 close(fd); 139 free(buf); 140 return 1; 141} 142 143static int load_topology(snd_tplg_t **tplg, char *config, 144 size_t config_size, int cflags) 145{ 146 int err; 147 148 *tplg = snd_tplg_create(cflags); 149 if (*tplg == NULL) { 150 fprintf(stderr, _("failed to create new topology context\n")); 151 return 1; 152 } 153 154 err = snd_tplg_load(*tplg, config, config_size); 155 if (err < 0) { 156 fprintf(stderr, _("Unable to load configuration: %s\n"), 157 snd_strerror(-err)); 158 snd_tplg_free(*tplg); 159 return 1; 160 } 161 162 return 0; 163} 164 165static int save(const char *output_file, void *buf, size_t size) 166{ 167 char *fname = NULL; 168 int fd; 169 ssize_t r; 170 171 if (strcmp(output_file, "-") == 0) { 172 fd = fileno(stdout); 173 } else { 174 fname = alloca(strlen(output_file) + 5); 175 strcpy(fname, output_file); 176 strcat(fname, ".new"); 177 fd = open(fname, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); 178 if (fd < 0) { 179 fprintf(stderr, _("Unable to open output file '%s': %s\n"), 180 fname, strerror(errno)); 181 return 1; 182 } 183 } 184 185 r = 0; 186 while (size > 0) { 187 r = write(fd, buf, size); 188 if (r < 0 && (errno == EAGAIN || errno == EINTR)) 189 continue; 190 if (r < 0) 191 break; 192 size -= r; 193 buf += r; 194 } 195 196 if (r < 0) { 197 fprintf(stderr, _("Write error: %s\n"), strerror(errno)); 198 if (fd != fileno(stdout)) { 199 if (fname && remove(fname)) 200 fprintf(stderr, _("Unable to remove file %s: %s\n"), 201 fname, strerror(errno)); 202 close(fd); 203 } 204 return 1; 205 } 206 207 if (fd != fileno(stdout)) 208 close(fd); 209 210 if (fname && rename(fname, output_file)) { 211 fprintf(stderr, _("Unable to rename file '%s' to '%s': %s\n"), 212 fname, output_file, strerror(errno)); 213 return 1; 214 } 215 216 return 0; 217} 218 219static int dump(const char *source_file, const char *output_file, int cflags, int sflags) 220{ 221 snd_tplg_t *tplg; 222 char *config, *text; 223 size_t size; 224 int err; 225 226 err = load(source_file, (void **)&config, &size); 227 if (err) 228 return err; 229 err = load_topology(&tplg, config, size, cflags); 230 free(config); 231 if (err) 232 return err; 233 err = snd_tplg_save(tplg, &text, sflags); 234 snd_tplg_free(tplg); 235 if (err < 0) { 236 fprintf(stderr, _("Unable to save parsed configuration: %s\n"), 237 snd_strerror(-err)); 238 return 1; 239 } 240 err = save(output_file, text, strlen(text)); 241 free(text); 242 return err; 243} 244 245static char *get_inc_path(const char *filename) 246{ 247 const char *s = strrchr(filename, '/'); 248 char *r = strdup(filename); 249 if (r) { 250 if (s) 251 r[s - filename] = '\0'; 252 else if (r[0]) 253 strcpy(r, "."); 254 } 255 return r; 256} 257 258static int pre_process_run(struct tplg_pre_processor **tplg_pp, 259 const char *source_file, const char *output_file, 260 const char *pre_processor_defs, const char *include_path) 261{ 262 size_t config_size; 263 char *config, *inc_path; 264 snd_output_type_t output_type; 265 int err; 266 267 err = load(source_file, (void **)&config, &config_size); 268 if (err) 269 return err; 270 271 /* init pre-processor */ 272 output_type = output_file == NULL ? SND_OUTPUT_BUFFER : SND_OUTPUT_STDIO; 273 err = init_pre_processor(tplg_pp, output_type, output_file); 274 if (err < 0) { 275 fprintf(stderr, _("failed to init pre-processor for Topology2.0\n")); 276 free(config); 277 return err; 278 } 279 280 /* pre-process conf file */ 281 if (!include_path) 282 inc_path = get_inc_path(source_file); 283 else 284 inc_path = strdup(include_path); 285 err = pre_process(*tplg_pp, config, config_size, pre_processor_defs, inc_path); 286 free(inc_path); 287 288 if (err < 0) 289 free_pre_processor(*tplg_pp); 290 free(config); 291 return err; 292} 293 294/* Convert Topology2.0 conf to the existing conf syntax */ 295static int pre_process_conf(const char *source_file, const char *output_file, 296 const char *pre_processor_defs, const char *include_path) 297{ 298 struct tplg_pre_processor *tplg_pp; 299 int err; 300 301 err = pre_process_run(&tplg_pp, source_file, output_file, 302 pre_processor_defs, include_path); 303 if (err < 0) 304 return err; 305 306 /* free pre-processor */ 307 free_pre_processor(tplg_pp); 308 return err; 309} 310 311static int compile(const char *source_file, const char *output_file, int cflags, 312 const char *pre_processor_defs, const char *include_path) 313{ 314 struct tplg_pre_processor *tplg_pp = NULL; 315 snd_tplg_t *tplg; 316 char *config; 317 void *bin; 318 size_t config_size, size; 319 int err; 320 321 err = load(source_file, (void **)&config, &config_size); 322 if (err) 323 return err; 324 325 /* pre-process before compiling */ 326 if (pre_process_config) { 327 char *pconfig; 328 size_t size; 329 330 err = pre_process_run(&tplg_pp, source_file, NULL, 331 pre_processor_defs, include_path); 332 if (err < 0) 333 return err; 334 335 /* load topology */ 336 size = snd_output_buffer_string(tplg_pp->output, &pconfig); 337 err = load_topology(&tplg, pconfig, size, cflags); 338 339 /* free pre-processor */ 340 free_pre_processor(tplg_pp); 341 } else { 342 err = load_topology(&tplg, config, config_size, cflags); 343 } 344 free(config); 345 if (err) 346 return err; 347 err = snd_tplg_build_bin(tplg, &bin, &size); 348 snd_tplg_free(tplg); 349 if (err < 0 || size == 0) { 350 fprintf(stderr, _("failed to compile context %s: %s\n"), 351 source_file, snd_strerror(-err)); 352 return 1; 353 } 354 err = save(output_file, bin, size); 355 free(bin); 356 return err; 357} 358 359static int decode(const char *source_file, const char *output_file, 360 int cflags, int dflags, int sflags) 361{ 362 snd_tplg_t *tplg; 363 void *bin; 364 char *text; 365 size_t size; 366 int err; 367 368 if (load(source_file, &bin, &size)) 369 return 1; 370 tplg = snd_tplg_create(cflags); 371 if (tplg == NULL) { 372 fprintf(stderr, _("failed to create new topology context\n")); 373 return 1; 374 } 375 err = snd_tplg_decode(tplg, bin, size, dflags); 376 free(bin); 377 if (err < 0) { 378 snd_tplg_free(tplg); 379 fprintf(stderr, _("failed to decode context %s: %s\n"), 380 source_file, snd_strerror(-err)); 381 return 1; 382 } 383 err = snd_tplg_save(tplg, &text, sflags); 384 snd_tplg_free(tplg); 385 if (err < 0) { 386 fprintf(stderr, _("Unable to save parsed configuration: %s\n"), 387 snd_strerror(-err)); 388 return 1; 389 } 390 err = save(output_file, text, strlen(text)); 391 free(text); 392 return err; 393} 394 395#if SND_LIB_VER(1, 2, 5) < SND_LIB_VERSION 396static int add_define(char **defs, char *d) 397{ 398 size_t len = (*defs ? strlen(*defs) : 0) + strlen(d) + 2; 399 char *m = realloc(*defs, len); 400 if (m) { 401 if (*defs) 402 strcat(m, ","); 403 strcat(m, d); 404 *defs = m; 405 return 0; 406 } 407 return 1; 408} 409#endif 410 411int main(int argc, char *argv[]) 412{ 413 static const char short_options[] = "hc:d:n:u:v:o:pP:sgxzV" 414#if SND_LIB_VER(1, 2, 5) < SND_LIB_VERSION 415 "D:I:" 416#endif 417 ; 418 static const struct option long_options[] = { 419 {"help", 0, NULL, 'h'}, 420 {"verbose", 1, NULL, 'v'}, 421 {"compile", 1, NULL, 'c'}, 422 {"pre-process", 1, NULL, 'p'}, 423 {"decode", 1, NULL, 'd'}, 424 {"normalize", 1, NULL, 'n'}, 425 {"dump", 1, NULL, 'u'}, 426 {"output", 1, NULL, 'o'}, 427#if SND_LIB_VER(1, 2, 5) < SND_LIB_VERSION 428 {"define", 1, NULL, 'D'}, 429 {"inc-dir", 1, NULL, 'I'}, 430#endif 431 {"sort", 0, NULL, 's'}, 432 {"group", 0, NULL, 'g'}, 433 {"nocheck", 0, NULL, 'x'}, 434 {"dapm-nosort", 0, NULL, 'z'}, 435 {"version", 0, NULL, 'V'}, 436 {0, 0, 0, 0}, 437 }; 438 char *source_file = NULL; 439 char *output_file = NULL; 440 const char *inc_path = NULL; 441 char *pre_processor_defs = NULL; 442 int c, err, op = 'c', cflags = 0, dflags = 0, sflags = 0, option_index; 443 444#ifdef ENABLE_NLS 445 setlocale(LC_ALL, ""); 446 textdomain(PACKAGE); 447#endif 448 449 err = snd_output_stdio_attach(&log, stderr, 0); 450 assert(err >= 0); 451 452 while ((c = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1) { 453 switch (c) { 454 case 'h': 455 usage(argv[0]); 456 return 0; 457 case 'v': 458 cflags |= SND_TPLG_CREATE_VERBOSE; 459 break; 460 case 'z': 461 cflags |= SND_TPLG_CREATE_DAPM_NOSORT; 462 break; 463 case 'c': 464 case 'd': 465 case 'n': 466 case 'u': 467 if (source_file) { 468 fprintf(stderr, _("Cannot combine operations (compile, normalize, pre-process, dump)\n")); 469 return 1; 470 } 471 source_file = optarg; 472 op = c; 473 break; 474 case 'o': 475 output_file = optarg; 476 break; 477 case 's': 478 sflags |= SND_TPLG_SAVE_SORT; 479 break; 480 case 'P': 481 op = 'P'; 482 source_file = optarg; 483 break; 484#if SND_LIB_VER(1, 2, 5) < SND_LIB_VERSION 485 case 'I': 486 inc_path = optarg; 487 break; 488#endif 489 case 'p': 490 pre_process_config = true; 491 break; 492 case 'g': 493 sflags |= SND_TPLG_SAVE_GROUPS; 494 break; 495 case 'x': 496 sflags |= SND_TPLG_SAVE_NOCHECK; 497 break; 498#if SND_LIB_VER(1, 2, 5) < SND_LIB_VERSION 499 case 'D': 500 if (add_define(&pre_processor_defs, optarg)) { 501 fprintf(stderr, _("No enough memory")); 502 return 1; 503 } 504 break; 505#endif 506 case 'V': 507 version(argv[0]); 508 return 0; 509 default: 510 fprintf(stderr, _("Try `%s --help' for more information.\n"), argv[0]); 511 return 1; 512 } 513 } 514 515 if (source_file == NULL || output_file == NULL) { 516 usage(argv[0]); 517 return 1; 518 } 519 520 if ((cflags & SND_TPLG_CREATE_VERBOSE) != 0 && 521 output_file && strcmp(output_file, "-") == 0) { 522 fprintf(stderr, _("Invalid mix of verbose level and output to stdout.\n")); 523 return 1; 524 } 525 526 if (op == 'n') { 527 if (sflags != 0 && sflags != SND_TPLG_SAVE_SORT) { 528 fprintf(stderr, _("Wrong parameters for the normalize operation!\n")); 529 return 1; 530 } 531 /* normalize has predefined output */ 532 sflags = SND_TPLG_SAVE_SORT; 533 } 534 535 switch (op) { 536 case 'c': 537 err = compile(source_file, output_file, cflags, pre_processor_defs, inc_path); 538 break; 539 case 'd': 540 err = decode(source_file, output_file, cflags, dflags, sflags); 541 break; 542 case 'P': 543 err = pre_process_conf(source_file, output_file, pre_processor_defs, inc_path); 544 break; 545 default: 546 err = dump(source_file, output_file, cflags, sflags); 547 break; 548 } 549 550 snd_output_close(log); 551 free(pre_processor_defs); 552 return err ? 1 : 0; 553} 554