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
16 enum 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
print_help()28 static 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
allocate_paths(struct xfer_context * xfer,char * const * paths,unsigned int count)52 static int allocate_paths(struct xfer_context *xfer, char *const *paths,
53 unsigned int count)
54 {
55 bool stdio = false;
56 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] = strndup("-", PATH_MAX);
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
verify_cntr_format(struct xfer_context * xfer)83 static 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 < 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()'.
verify_sample_format(struct xfer_context * xfer)113 static 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 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
validate_options(struct xfer_context * xfer)171 static 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
xfer_options_parse_args(struct xfer_context * xfer,const struct xfer_data * data,int argc,char * const * argv)257 int 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
xfer_options_calculate_duration(struct xfer_context * xfer,uint64_t * total_frame_count)395 void 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
413 static const char *const allowed_duplication[] = {
414 "/dev/null",
415 "/dev/zero",
416 "/dev/full",
417 "/dev/random",
418 "/dev/urandom",
419 };
420
generate_path_with_suffix(struct xfer_context * xfer,const char * template,unsigned int index,const char * suffix)421 static 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
generate_path_without_suffix(struct xfer_context * xfer,const char * template,unsigned int index,const char * suffix)448 static int generate_path_without_suffix(struct xfer_context *xfer,
449 const char *template,
450 unsigned int index, const char *suffix)
451 {
452 static const char *const single_format = "%s";
453 static const char *const multiple_format = "%s-%i";
454 unsigned int len;
455
456 len = strlen(template) + 1;
457 if (xfer->path_count > 1)
458 len += (unsigned int)log10(xfer->path_count) + 2;
459
460 xfer->paths[index] = malloc(len);
461 if (xfer->paths[index] == NULL)
462 return -ENOMEM;
463
464 if (xfer->path_count == 1) {
465 snprintf(xfer->paths[index], len, single_format, template);
466 } else {
467 snprintf(xfer->paths[index], len, multiple_format, template,
468 index);
469 }
470
471 return 0;
472 }
473
generate_path(struct xfer_context * xfer,char * template,unsigned int index,const char * suffix)474 static int generate_path(struct xfer_context *xfer, char *template,
475 unsigned int index, const char *suffix)
476 {
477 int (*generator)(struct xfer_context *xfer, const char *template,
478 unsigned int index, const char *suffix);
479 char *pos;
480
481 if (strlen(suffix) > 0) {
482 pos = template + strlen(template) - strlen(suffix);
483 // Separate filename and suffix.
484 if (!strcmp(pos, suffix))
485 *pos = '\0';
486 }
487
488 // Select handlers.
489 if (strlen(suffix) > 0)
490 generator = generate_path_with_suffix;
491 else
492 generator = generate_path_without_suffix;
493
494 return generator(xfer, template, index, suffix);
495 }
496
create_paths(struct xfer_context * xfer,unsigned int path_count)497 static int create_paths(struct xfer_context *xfer, unsigned int path_count)
498 {
499 char *template;
500 const char *suffix;
501 int i, j;
502 int err = 0;
503
504 // Can cause memory leak.
505 assert(xfer->path_count == 1);
506 assert(xfer->paths);
507 assert(xfer->paths[0]);
508 assert(xfer->paths[0][0] != '\0');
509
510 // Release at first.
511 template = xfer->paths[0];
512 free(xfer->paths);
513 xfer->paths = NULL;
514
515 // Allocate again.
516 xfer->paths = calloc(path_count, sizeof(*xfer->paths));
517 if (xfer->paths == NULL) {
518 err = -ENOMEM;
519 goto end;
520 }
521 xfer->path_count = path_count;
522
523 suffix = container_suffix_from_format(xfer->cntr_format);
524
525 for (i = 0; i < xfer->path_count; ++i) {
526 // Some file names are allowed to be duplicated.
527 for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) {
528 if (!strcmp(template, allowed_duplication[j]))
529 break;
530 }
531 if (j < ARRAY_SIZE(allowed_duplication))
532 continue;
533
534 err = generate_path(xfer, template, i, suffix);
535 if (err < 0)
536 break;
537 }
538 end:
539 free(template);
540
541 return err;
542 }
543
fixup_paths(struct xfer_context * xfer)544 static int fixup_paths(struct xfer_context *xfer)
545 {
546 const char *suffix;
547 char *template;
548 int i, j;
549 int err = 0;
550
551 suffix = container_suffix_from_format(xfer->cntr_format);
552
553 for (i = 0; i < xfer->path_count; ++i) {
554 // Some file names are allowed to be duplicated.
555 for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) {
556 if (!strcmp(xfer->paths[i], allowed_duplication[j]))
557 break;
558 }
559 if (j < ARRAY_SIZE(allowed_duplication))
560 continue;
561
562 template = xfer->paths[i];
563 xfer->paths[i] = NULL;
564 err = generate_path(xfer, template, i, suffix);
565 free(template);
566 if (err < 0)
567 break;
568 }
569
570 return err;
571 }
572
xfer_options_fixup_paths(struct xfer_context * xfer)573 int xfer_options_fixup_paths(struct xfer_context *xfer)
574 {
575 int i, j;
576 int err;
577
578 if (xfer->path_count == 1) {
579 // Nothing to do for sign of stdin/stdout.
580 if (!strcmp(xfer->paths[0], "-"))
581 return 0;
582 if (!xfer->multiple_cntrs)
583 err = fixup_paths(xfer);
584 else
585 err = create_paths(xfer, xfer->samples_per_frame);
586 } else {
587 if (!xfer->multiple_cntrs)
588 return -EINVAL;
589 if (xfer->path_count != xfer->samples_per_frame)
590 return -EINVAL;
591 else
592 err = fixup_paths(xfer);
593 }
594 if (err < 0)
595 return err;
596
597 // Check duplication of the paths.
598 for (i = 0; i < xfer->path_count - 1; ++i) {
599 // Some file names are allowed to be duplicated.
600 for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) {
601 if (!strcmp(xfer->paths[i], allowed_duplication[j]))
602 break;
603 }
604 if (j < ARRAY_SIZE(allowed_duplication))
605 continue;
606
607 for (j = i + 1; j < xfer->path_count; ++j) {
608 if (!strcmp(xfer->paths[i], xfer->paths[j])) {
609 fprintf(stderr,
610 "Detect duplicated file names:\n");
611 err = -EINVAL;
612 break;
613 }
614 }
615 if (j < xfer->path_count)
616 break;
617 }
618
619 if (xfer->verbose > 1)
620 fprintf(stderr, "Handled file names:\n");
621 if (err < 0 || xfer->verbose > 1) {
622 for (i = 0; i < xfer->path_count; ++i)
623 fprintf(stderr, " %d: %s\n", i, xfer->paths[i]);
624 }
625
626 return err;
627 }
628