1// SPDX-License-Identifier: GPL-2.0 2// 3// xfer-options.c - a parser of commandline options for xfer. 4// 5// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp> 6// 7// Licensed under the terms of the GNU General Public License, version 2. 8 9#include "xfer.h" 10#include "misc.h" 11 12#include <getopt.h> 13#include <math.h> 14#include <limits.h> 15 16enum no_short_opts { 17 // 128 or later belong to non us-ascii character set. 18 OPT_XFER_TYPE = 128, 19 OPT_DUMP_HW_PARAMS, 20 OPT_PERIOD_SIZE, 21 OPT_BUFFER_SIZE, 22 // Obsoleted. 23 OPT_MAX_FILE_TIME, 24 OPT_USE_STRFTIME, 25 OPT_PROCESS_ID_FILE, 26}; 27 28static void print_help() 29{ 30 printf( 31"Usage:\n" 32" axfer transfer DIRECTION [ COMMON-OPTIONS ] [ BACKEND-OPTIONS ]\n" 33"\n" 34" where:\n" 35" DIRECTION = capture | playback\n" 36" COMMON-OPTIONS =\n" 37" -h, --help help\n" 38" -v, --verbose verbose\n" 39" -q, --quiet quiet mode\n" 40" -d, --duration=# interrupt after # seconds\n" 41" -s, --samples=# interrupt after # frames\n" 42" -f, --format=FORMAT sample format (case-insensitive)\n" 43" -c, --channels=# channels\n" 44" -r, --rate=# numeric sample rate in unit of Hz or kHz\n" 45" -t, --file-type=TYPE file type (wav, au, sparc, voc or raw, case-insentive)\n" 46" -I, --separate-channels one file for each channel\n" 47" --dump-hw-params dump hw_params of the device\n" 48" --xfer-type=BACKEND backend type (libasound, libffado)\n" 49 ); 50} 51 52static int allocate_paths(struct xfer_context *xfer, char *const *paths, 53 unsigned int count) 54{ 55 bool stdio = false; 56 unsigned int i; 57 58 if (count == 0) { 59 stdio = true; 60 count = 1; 61 } 62 63 xfer->paths = calloc(count, sizeof(xfer->paths[0])); 64 if (xfer->paths == NULL) 65 return -ENOMEM; 66 xfer->path_count = count; 67 68 if (stdio) { 69 xfer->paths[0] = strdup("-"); 70 if (xfer->paths[0] == NULL) 71 return -ENOMEM; 72 } else { 73 for (i = 0; i < count; ++i) { 74 xfer->paths[i] = strndup(paths[i], PATH_MAX); 75 if (xfer->paths[i] == NULL) 76 return -ENOMEM; 77 } 78 } 79 80 return 0; 81} 82 83static int verify_cntr_format(struct xfer_context *xfer) 84{ 85 static const struct { 86 const char *const literal; 87 enum container_format cntr_format; 88 } *entry, entries[] = { 89 {"raw", CONTAINER_FORMAT_RAW}, 90 {"voc", CONTAINER_FORMAT_VOC}, 91 {"wav", CONTAINER_FORMAT_RIFF_WAVE}, 92 {"au", CONTAINER_FORMAT_AU}, 93 {"sparc", CONTAINER_FORMAT_AU}, 94 }; 95 int i; 96 97 for (i = 0; i < (int)ARRAY_SIZE(entries); ++i) { 98 entry = &entries[i]; 99 if (strcasecmp(xfer->cntr_format_literal, entry->literal)) 100 continue; 101 102 xfer->cntr_format = entry->cntr_format; 103 return 0; 104 } 105 106 fprintf(stderr, "unrecognized file format '%s'\n", 107 xfer->cntr_format_literal); 108 109 return -EINVAL; 110} 111 112// This should be called after 'verify_cntr_format()'. 113static int verify_sample_format(struct xfer_context *xfer) 114{ 115 static const struct { 116 const char *const literal; 117 unsigned int frames_per_second; 118 unsigned int samples_per_frame; 119 snd_pcm_format_t le_format; 120 snd_pcm_format_t be_format; 121 } *entry, entries[] = { 122 {"cd", 44100, 2, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE}, 123 {"cdr", 44100, 2, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE}, 124 {"dat", 48000, 2, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE}, 125 }; 126 unsigned int i; 127 128 xfer->sample_format = snd_pcm_format_value(xfer->sample_format_literal); 129 if (xfer->sample_format != SND_PCM_FORMAT_UNKNOWN) 130 return 0; 131 132 for (i = 0; i < ARRAY_SIZE(entries); ++i) { 133 entry = &entries[i]; 134 if (strcmp(entry->literal, xfer->sample_format_literal)) 135 continue; 136 137 if (xfer->frames_per_second > 0 && 138 xfer->frames_per_second != entry->frames_per_second) { 139 fprintf(stderr, 140 "'%s' format can't be used with rate except " 141 "for %u.\n", 142 entry->literal, entry->frames_per_second); 143 return -EINVAL; 144 } 145 146 if (xfer->samples_per_frame > 0 && 147 xfer->samples_per_frame != entry->samples_per_frame) { 148 fprintf(stderr, 149 "'%s' format can't be used with channel except " 150 "for %u.\n", 151 entry->literal, entry->samples_per_frame); 152 return -EINVAL; 153 } 154 155 xfer->frames_per_second = entry->frames_per_second; 156 xfer->samples_per_frame = entry->samples_per_frame; 157 if (xfer->cntr_format == CONTAINER_FORMAT_AU) 158 xfer->sample_format = entry->be_format; 159 else 160 xfer->sample_format = entry->le_format; 161 162 return 0; 163 } 164 165 fprintf(stderr, "wrong extended format '%s'\n", 166 xfer->sample_format_literal); 167 168 return -EINVAL; 169} 170 171static int validate_options(struct xfer_context *xfer) 172{ 173 unsigned int val; 174 int err = 0; 175 176 if (xfer->cntr_format_literal == NULL) { 177 if (xfer->direction == SND_PCM_STREAM_CAPTURE) { 178 // To stdout. 179 if (xfer->path_count == 1 && 180 !strcmp(xfer->paths[0], "-")) { 181 xfer->cntr_format = CONTAINER_FORMAT_RAW; 182 } else { 183 // Use first path as a representative. 184 xfer->cntr_format = container_format_from_path( 185 xfer->paths[0]); 186 } 187 } 188 // For playback, perform auto-detection. 189 } else { 190 err = verify_cntr_format(xfer); 191 } 192 if (err < 0) 193 return err; 194 195 if (xfer->multiple_cntrs) { 196 if (!strcmp(xfer->paths[0], "-")) { 197 fprintf(stderr, 198 "An option for separated channels is not " 199 "available with stdin/stdout.\n"); 200 return -EINVAL; 201 } 202 203 // For captured PCM frames, even if one path is given for 204 // container files, it can be used to generate several paths. 205 // For this purpose, please see 206 // 'xfer_options_fixup_paths()'. 207 if (xfer->direction == SND_PCM_STREAM_PLAYBACK) { 208 // Require several paths for containers. 209 if (xfer->path_count == 1) { 210 fprintf(stderr, 211 "An option for separated channels " 212 "requires several files to playback " 213 "PCM frames.\n"); 214 return -EINVAL; 215 } 216 } 217 } else { 218 // A single path is available only. 219 if (xfer->path_count > 1) { 220 fprintf(stderr, 221 "When using several files, an option for " 222 "sepatated channels is used with.\n"); 223 return -EINVAL; 224 } 225 } 226 227 xfer->sample_format = SND_PCM_FORMAT_UNKNOWN; 228 if (xfer->sample_format_literal) { 229 err = verify_sample_format(xfer); 230 if (err < 0) 231 return err; 232 } 233 234 val = xfer->frames_per_second; 235 if (xfer->frames_per_second == 0) 236 xfer->frames_per_second = 8000; 237 if (xfer->frames_per_second < 1000) 238 xfer->frames_per_second *= 1000; 239 if (xfer->frames_per_second < 2000 || 240 xfer->frames_per_second > 192000) { 241 fprintf(stderr, "bad speed value '%u'\n", val); 242 return -EINVAL; 243 } 244 245 if (xfer->samples_per_frame > 0) { 246 if (xfer->samples_per_frame < 1 || 247 xfer->samples_per_frame > 256) { 248 fprintf(stderr, "invalid channels argument '%u'\n", 249 xfer->samples_per_frame); 250 return -EINVAL; 251 } 252 } 253 254 return err; 255} 256 257int xfer_options_parse_args(struct xfer_context *xfer, 258 const struct xfer_data *data, int argc, 259 char *const *argv) 260{ 261 static const char *short_opts = "CPhvqd:s:f:c:r:t:IV:i"; 262 static const struct option long_opts[] = { 263 // For generic purposes. 264 {"capture", 0, 0, 'C'}, 265 {"playback", 0, 0, 'P'}, 266 {"xfer-type", 1, 0, OPT_XFER_TYPE}, 267 {"help", 0, 0, 'h'}, 268 {"verbose", 0, 0, 'v'}, 269 {"quiet", 0, 0, 'q'}, 270 {"duration", 1, 0, 'd'}, 271 {"samples", 1, 0, 's'}, 272 // For transfer backend. 273 {"format", 1, 0, 'f'}, 274 {"channels", 1, 0, 'c'}, 275 {"rate", 1, 0, 'r'}, 276 // For containers. 277 {"file-type", 1, 0, 't'}, 278 // For mapper. 279 {"separate-channels", 0, 0, 'I'}, 280 // For debugging. 281 {"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS}, 282 // Obsoleted. 283 {"max-file-time", 1, 0, OPT_MAX_FILE_TIME}, 284 {"use-strftime", 0, 0, OPT_USE_STRFTIME}, 285 {"process-id-file", 1, 0, OPT_PROCESS_ID_FILE}, 286 {"vumeter", 1, 0, 'V'}, 287 {"interactive", 0, 0, 'i'}, 288 }; 289 char *s_opts; 290 struct option *l_opts; 291 int l_index; 292 int key; 293 int err = 0; 294 295 // Concatenate short options. 296 s_opts = malloc(strlen(data->s_opts) + strlen(short_opts) + 1); 297 if (s_opts == NULL) 298 return -ENOMEM; 299 strcpy(s_opts, data->s_opts); 300 strcpy(s_opts + strlen(s_opts), short_opts); 301 s_opts[strlen(data->s_opts) + strlen(short_opts)] = '\0'; 302 303 // Concatenate long options, including a sentinel. 304 l_opts = calloc(ARRAY_SIZE(long_opts) * data->l_opts_count + 1, 305 sizeof(*l_opts)); 306 if (l_opts == NULL) { 307 free(s_opts); 308 return -ENOMEM; 309 } 310 memcpy(l_opts, long_opts, ARRAY_SIZE(long_opts) * sizeof(*l_opts)); 311 memcpy(&l_opts[ARRAY_SIZE(long_opts)], data->l_opts, 312 data->l_opts_count * sizeof(*l_opts)); 313 314 // Parse options. 315 l_index = 0; 316 optarg = NULL; 317 optind = 1; 318 opterr = 1; // use error output. 319 optopt = 0; 320 while (1) { 321 key = getopt_long(argc, argv, s_opts, l_opts, &l_index); 322 if (key < 0) 323 break; 324 else if (key == 'C') 325 ; // already parsed. 326 else if (key == 'P') 327 ; // already parsed. 328 else if (key == OPT_XFER_TYPE) 329 ; // already parsed. 330 else if (key == 'h') 331 xfer->help = true; 332 else if (key == 'v') 333 ++xfer->verbose; 334 else if (key == 'q') 335 xfer->quiet = true; 336 else if (key == 'd') 337 xfer->duration_seconds = arg_parse_decimal_num(optarg, &err); 338 else if (key == 's') 339 xfer->duration_frames = arg_parse_decimal_num(optarg, &err); 340 else if (key == 'f') 341 xfer->sample_format_literal = arg_duplicate_string(optarg, &err); 342 else if (key == 'c') 343 xfer->samples_per_frame = arg_parse_decimal_num(optarg, &err); 344 else if (key == 'r') 345 xfer->frames_per_second = arg_parse_decimal_num(optarg, &err); 346 else if (key == 't') 347 xfer->cntr_format_literal = arg_duplicate_string(optarg, &err); 348 else if (key == 'I') 349 xfer->multiple_cntrs = true; 350 else if (key == OPT_DUMP_HW_PARAMS) 351 xfer->dump_hw_params = true; 352 else if (key == '?') { 353 free(l_opts); 354 free(s_opts); 355 return -EINVAL; 356 } 357 else if (key == OPT_MAX_FILE_TIME || 358 key == OPT_USE_STRFTIME || 359 key == OPT_PROCESS_ID_FILE || 360 key == 'V' || 361 key == 'i') { 362 fprintf(stderr, 363 "An option '--%s' is obsoleted and has no " 364 "effect.\n", 365 l_opts[l_index].name); 366 err = -EINVAL; 367 } else { 368 err = xfer->ops->parse_opt(xfer, key, optarg); 369 if (err < 0 && err != -ENXIO) 370 break; 371 } 372 } 373 374 free(l_opts); 375 free(s_opts); 376 377 if (xfer->help) { 378 print_help(); 379 if (xfer->ops->help) { 380 printf("\n"); 381 printf(" BACKEND-OPTIONS (%s) =\n", 382 xfer_label_from_type(xfer->type)); 383 xfer->ops->help(xfer); 384 } 385 return 0; 386 } 387 388 err = allocate_paths(xfer, argv + optind, argc - optind); 389 if (err < 0) 390 return err; 391 392 return validate_options(xfer); 393} 394 395void xfer_options_calculate_duration(struct xfer_context *xfer, 396 uint64_t *total_frame_count) 397{ 398 uint64_t frame_count; 399 400 if (xfer->duration_seconds > 0) { 401 frame_count = (uint64_t)xfer->duration_seconds * (uint64_t)xfer->frames_per_second; 402 if (frame_count < *total_frame_count) 403 *total_frame_count = frame_count; 404 } 405 406 if (xfer->duration_frames > 0) { 407 frame_count = xfer->duration_frames; 408 if (frame_count < *total_frame_count) 409 *total_frame_count = frame_count; 410 } 411} 412 413static const char *const allowed_duplication[] = { 414 "/dev/null", 415 "/dev/zero", 416 "/dev/full", 417 "/dev/random", 418 "/dev/urandom", 419}; 420 421static int generate_path_with_suffix(struct xfer_context *xfer, 422 const char *template, unsigned int index, 423 const char *suffix) 424{ 425 static const char *const single_format = "%s%s"; 426 static const char *const multiple_format = "%s-%i%s"; 427 unsigned int len; 428 429 len = strlen(template) + strlen(suffix) + 1; 430 if (xfer->path_count > 1) 431 len += (unsigned int)log10(xfer->path_count) + 2; 432 433 xfer->paths[index] = malloc(len); 434 if (xfer->paths[index] == NULL) 435 return -ENOMEM; 436 437 if (xfer->path_count == 1) { 438 snprintf(xfer->paths[index], len, single_format, template, 439 suffix); 440 } else { 441 snprintf(xfer->paths[index], len, multiple_format, template, 442 index, suffix); 443 } 444 445 return 0; 446} 447 448static int generate_path_without_suffix(struct xfer_context *xfer, 449 const char *template, 450 unsigned int index, 451 const char *suffix ATTRIBUTE_UNUSED) 452{ 453 static const char *const single_format = "%s"; 454 static const char *const multiple_format = "%s-%i"; 455 unsigned int len; 456 457 len = strlen(template) + 1; 458 if (xfer->path_count > 1) 459 len += (unsigned int)log10(xfer->path_count) + 2; 460 461 xfer->paths[index] = malloc(len); 462 if (xfer->paths[index] == NULL) 463 return -ENOMEM; 464 465 if (xfer->path_count == 1) { 466 snprintf(xfer->paths[index], len, single_format, template); 467 } else { 468 snprintf(xfer->paths[index], len, multiple_format, template, 469 index); 470 } 471 472 return 0; 473} 474 475static int generate_path(struct xfer_context *xfer, char *template, 476 unsigned int index, const char *suffix) 477{ 478 int (*generator)(struct xfer_context *xfer, const char *template, 479 unsigned int index, const char *suffix); 480 char *pos; 481 482 if (strlen(suffix) > 0) { 483 pos = template + strlen(template) - strlen(suffix); 484 // Separate filename and suffix. 485 if (!strcmp(pos, suffix)) 486 *pos = '\0'; 487 } 488 489 // Select handlers. 490 if (strlen(suffix) > 0) 491 generator = generate_path_with_suffix; 492 else 493 generator = generate_path_without_suffix; 494 495 return generator(xfer, template, index, suffix); 496} 497 498static int create_paths(struct xfer_context *xfer, unsigned int path_count) 499{ 500 char *template; 501 const char *suffix; 502 unsigned int i, j; 503 int err = 0; 504 505 // Can cause memory leak. 506 assert(xfer->path_count == 1); 507 assert(xfer->paths); 508 assert(xfer->paths[0]); 509 assert(xfer->paths[0][0] != '\0'); 510 511 // Release at first. 512 template = xfer->paths[0]; 513 free(xfer->paths); 514 xfer->paths = NULL; 515 516 // Allocate again. 517 xfer->paths = calloc(path_count, sizeof(*xfer->paths)); 518 if (xfer->paths == NULL) { 519 err = -ENOMEM; 520 goto end; 521 } 522 xfer->path_count = path_count; 523 524 suffix = container_suffix_from_format(xfer->cntr_format); 525 526 for (i = 0; i < xfer->path_count; ++i) { 527 // Some file names are allowed to be duplicated. 528 for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) { 529 if (!strcmp(template, allowed_duplication[j])) 530 break; 531 } 532 if (j < ARRAY_SIZE(allowed_duplication)) 533 continue; 534 535 err = generate_path(xfer, template, i, suffix); 536 if (err < 0) 537 break; 538 } 539end: 540 free(template); 541 542 return err; 543} 544 545static int fixup_paths(struct xfer_context *xfer) 546{ 547 const char *suffix; 548 char *template; 549 unsigned int i, j; 550 int err = 0; 551 552 suffix = container_suffix_from_format(xfer->cntr_format); 553 554 for (i = 0; i < xfer->path_count; ++i) { 555 // Some file names are allowed to be duplicated. 556 for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) { 557 if (!strcmp(xfer->paths[i], allowed_duplication[j])) 558 break; 559 } 560 if (j < ARRAY_SIZE(allowed_duplication)) 561 continue; 562 563 template = xfer->paths[i]; 564 xfer->paths[i] = NULL; 565 err = generate_path(xfer, template, i, suffix); 566 free(template); 567 if (err < 0) 568 break; 569 } 570 571 return err; 572} 573 574int xfer_options_fixup_paths(struct xfer_context *xfer) 575{ 576 unsigned int i, j; 577 int err; 578 579 if (xfer->path_count == 1) { 580 // Nothing to do for sign of stdin/stdout. 581 if (!strcmp(xfer->paths[0], "-")) 582 return 0; 583 if (!xfer->multiple_cntrs) 584 err = fixup_paths(xfer); 585 else 586 err = create_paths(xfer, xfer->samples_per_frame); 587 } else { 588 if (!xfer->multiple_cntrs) 589 return -EINVAL; 590 if (xfer->path_count != xfer->samples_per_frame) 591 return -EINVAL; 592 else 593 err = fixup_paths(xfer); 594 } 595 if (err < 0) 596 return err; 597 598 // Check duplication of the paths. 599 for (i = 0; i < xfer->path_count - 1; ++i) { 600 // Some file names are allowed to be duplicated. 601 for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) { 602 if (!strcmp(xfer->paths[i], allowed_duplication[j])) 603 break; 604 } 605 if (j < ARRAY_SIZE(allowed_duplication)) 606 continue; 607 608 for (j = i + 1; j < xfer->path_count; ++j) { 609 if (!strcmp(xfer->paths[i], xfer->paths[j])) { 610 fprintf(stderr, 611 "Detect duplicated file names:\n"); 612 err = -EINVAL; 613 break; 614 } 615 } 616 if (j < xfer->path_count) 617 break; 618 } 619 620 if (xfer->verbose > 1) 621 fprintf(stderr, "Handled file names:\n"); 622 if (err < 0 || xfer->verbose > 1) { 623 for (i = 0; i < xfer->path_count; ++i) 624 fprintf(stderr, " %d: %s\n", i, xfer->paths[i]); 625 } 626 627 return err; 628} 629