xref: /third_party/alsa-utils/axfer/main.c (revision c72fcc34)
1// SPDX-License-Identifier: GPL-2.0
2// main.c - an entry point for this program.
3//
4// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
5//
6// Originally written as 'aplay', by Michael Beck and Jaroslav Kysela.
7//
8// Licensed under the terms of the GNU General Public License, version 2.
9
10#include "subcmd.h"
11#include "misc.h"
12
13#include "version.h"
14
15#include <stdio.h>
16#include <stdlib.h>
17#include <stdbool.h>
18#include <string.h>
19
20enum subcmds {
21	SUBCMD_TRANSFER = 0,
22	SUBCMD_LIST,
23	SUBCMD_HELP,
24	SUBCMD_VERSION,
25};
26
27char *arg_duplicate_string(const char *str, int *err)
28{
29	char *ptr;
30
31	// For safe.
32	if (strlen(str) > 1024) {
33		*err = -EINVAL;
34		return NULL;
35	}
36
37	ptr = strdup(str);
38	if (ptr == NULL)
39		*err = -ENOMEM;
40
41	return ptr;
42}
43
44long arg_parse_decimal_num(const char *str, int *err)
45{
46	long val;
47	char *endptr;
48
49	errno = 0;
50	val = strtol(str, &endptr, 0);
51	if (errno > 0) {
52		*err = -errno;
53		return 0;
54	}
55	if (*endptr != '\0') {
56		*err = -EINVAL;
57		return 0;
58	}
59
60	return val;
61}
62
63static void print_version(const char *const cmdname)
64{
65	printf("%s: version %s\n", cmdname, SND_UTIL_VERSION_STR);
66}
67
68static void print_help(void)
69{
70	printf(
71"Usage:\n"
72"  axfer transfer DIRECTION OPTIONS\n"
73"  axfer list DIRECTION OPTIONS\n"
74"  axfer version\n"
75"  axfer help\n"
76"\n"
77"  where:\n"
78"    DIRECTION = capture | playback\n"
79"    OPTIONS = -h | --help | (subcommand specific)\n"
80	);
81}
82
83// Backward compatibility to aplay(1).
84static bool decide_subcmd(int argc, char *const *argv, enum subcmds *subcmd)
85{
86	static const struct {
87		const char *const name;
88		enum subcmds subcmd;
89	} long_opts[] = {
90		{"--list-devices",	SUBCMD_LIST},
91		{"--list-pcms",		SUBCMD_LIST},
92		{"--help",  		SUBCMD_HELP},
93		{"--version",  		SUBCMD_VERSION},
94	};
95	static const struct {
96		unsigned char c;
97		enum subcmds subcmd;
98	} short_opts[] = {
99		{'l', SUBCMD_LIST},
100		{'L', SUBCMD_LIST},
101		{'h', SUBCMD_HELP},
102	};
103	char *pos;
104	int i, j;
105
106	if (argc == 1)
107		return false;
108
109	// Original command system. For long options.
110	for (i = 0; i < (int)ARRAY_SIZE(long_opts); ++i) {
111		for (j = 0; j < argc; ++j) {
112			if (!strcmp(long_opts[i].name, argv[j])) {
113				*subcmd = long_opts[i].subcmd;
114				return true;
115			}
116		}
117	}
118
119	// Original command system. For short options.
120	for (i = 1; i < argc; ++i) {
121		// Pick up short options only.
122		if (argv[i][0] != '-' || argv[i][0] == '\0' ||
123		    argv[i][1] == '-' || argv[i][1] == '\0')
124			continue;
125		for (pos = argv[i]; *pos != '\0'; ++pos) {
126			for (j = 0; j < (int)ARRAY_SIZE(short_opts); ++j) {
127				if (*pos == short_opts[j].c) {
128					*subcmd = short_opts[j].subcmd;
129					return true;
130				}
131			}
132		}
133	}
134
135	return false;
136}
137
138// Backward compatibility to aplay(1).
139static bool decide_direction(int argc, char *const *argv,
140			     snd_pcm_stream_t *direction)
141{
142	static const struct {
143		const char *const name;
144		snd_pcm_stream_t direction;
145	} long_opts[] = {
146		{"--capture",	SND_PCM_STREAM_CAPTURE},
147		{"--playback",	SND_PCM_STREAM_PLAYBACK},
148	};
149	static const struct {
150		unsigned char c;
151		snd_pcm_stream_t direction;
152	} short_opts[] = {
153		{'C',		SND_PCM_STREAM_CAPTURE},
154		{'P',		SND_PCM_STREAM_PLAYBACK},
155	};
156	static const char *const aliases[] = {
157		[SND_PCM_STREAM_CAPTURE] = "arecord",
158		[SND_PCM_STREAM_PLAYBACK] = "aplay",
159	};
160	int i, j;
161	char *pos;
162
163	// Original command system. For long options.
164	for (i = 0; i < (int)ARRAY_SIZE(long_opts); ++i) {
165		for (j = 0; j < argc; ++j) {
166			if (!strcmp(long_opts[i].name, argv[j])) {
167				*direction = long_opts[i].direction;
168				return true;
169			}
170		}
171	}
172
173	// Original command system. For short options.
174	for (i = 1; i < argc; ++i) {
175		// Pick up short options only.
176		if (argv[i][0] != '-' || argv[i][0] == '\0' ||
177		    argv[i][1] == '-' || argv[i][1] == '\0')
178			continue;
179		for (pos = argv[i]; *pos != '\0'; ++pos) {
180			for (j = 0; j < (int)ARRAY_SIZE(short_opts); ++j) {
181				if (*pos == short_opts[j].c) {
182					*direction = short_opts[j].direction;
183					return true;
184				}
185			}
186		}
187	}
188
189	// If not decided yet, judge according to command name.
190	for (i = 0; i < (int)ARRAY_SIZE(aliases); ++i) {
191		for (pos = argv[0] + strlen(argv[0]); pos != argv[0]; --pos) {
192			if (strstr(pos, aliases[i]) != NULL) {
193				*direction = i;
194				return true;
195			}
196		}
197	}
198
199	return false;
200}
201
202static bool detect_subcmd(int argc, char *const *argv, enum subcmds *subcmd)
203{
204	static const char *const subcmds[] = {
205		[SUBCMD_TRANSFER] = "transfer",
206		[SUBCMD_LIST] = "list",
207		[SUBCMD_HELP] = "help",
208		[SUBCMD_VERSION] = "version",
209	};
210	int i;
211
212	if (argc < 2)
213		return false;
214
215	for (i = 0; i < (int)ARRAY_SIZE(subcmds); ++i) {
216		if (!strcmp(argv[1], subcmds[i])) {
217			*subcmd = i;
218			return true;
219		}
220	}
221
222	return false;
223}
224
225static bool detect_direction(int argc, char *const *argv,
226			     snd_pcm_stream_t *direction)
227{
228	if (argc < 3)
229		return false;
230
231	if (!strcmp(argv[2], "capture")) {
232		*direction = SND_PCM_STREAM_CAPTURE;
233		return true;
234	}
235
236	if (!strcmp(argv[2], "playback")) {
237		*direction = SND_PCM_STREAM_PLAYBACK;
238		return true;
239	}
240
241	return false;
242}
243
244int main(int argc, char *const *argv)
245{
246	snd_pcm_stream_t direction;
247	enum subcmds subcmd;
248	int err = 0;
249
250	// For compatibility to aplay(1) implementation.
251	if (strstr(argv[0], "arecord") == argv[0] + strlen(argv[0]) - 7 ||
252	    strstr(argv[0], "aplay") == argv[0] + strlen(argv[0]) - 5) {
253		if (!decide_direction(argc, argv, &direction))
254			direction = SND_PCM_STREAM_PLAYBACK;
255		if (!decide_subcmd(argc, argv, &subcmd))
256			subcmd = SUBCMD_TRANSFER;
257	} else {
258		// The first option should be one of subcommands.
259		if (!detect_subcmd(argc, argv, &subcmd))
260			subcmd = SUBCMD_HELP;
261		// The second option should be either 'capture' or 'direction'
262		// if subcommand is neither 'version' nor 'help'.
263		if (subcmd != SUBCMD_VERSION && subcmd != SUBCMD_HELP) {
264			if (!detect_direction(argc, argv, &direction)) {
265				subcmd = SUBCMD_HELP;
266			} else {
267				// argv[0] is needed for unparsed option to use
268				// getopt_long(3).
269				argc -= 2;
270				argv += 2;
271			}
272		}
273	}
274
275	if (subcmd == SUBCMD_TRANSFER)
276		err = subcmd_transfer(argc, argv, direction);
277	else if (subcmd == SUBCMD_LIST)
278		err = subcmd_list(argc, argv, direction);
279	else if (subcmd == SUBCMD_VERSION)
280		print_version(argv[0]);
281	else
282		print_help();
283	if (err < 0)
284		return EXIT_FAILURE;
285
286	return EXIT_SUCCESS;
287}
288