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