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_helpnull28 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 	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 
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 < (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()'.
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 	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 
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 ATTRIBUTE_UNUSED)448 static 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 
generate_path(struct xfer_context *xfer, char *template, unsigned int index, const char *suffix)475 static 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 
create_paths(struct xfer_context *xfer, unsigned int path_count)498 static 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 	}
539 end:
540 	free(template);
541 
542 	return err;
543 }
544 
fixup_paths(struct xfer_context *xfer)545 static 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 
xfer_options_fixup_paths(struct xfer_context *xfer)574 int 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