• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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