• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
20 enum subcmds {
21 	SUBCMD_TRANSFER = 0,
22 	SUBCMD_LIST,
23 	SUBCMD_HELP,
24 	SUBCMD_VERSION,
25 };
26 
arg_duplicate_string(const char * str,int * err)27 char *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 
arg_parse_decimal_num(const char * str,int * err)44 long 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 
print_version(const char * const cmdname)63 static void print_version(const char *const cmdname)
64 {
65 	printf("%s: version %s\n", cmdname, SND_UTIL_VERSION_STR);
66 }
67 
print_help(void)68 static 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).
decide_subcmd(int argc,char * const * argv,enum subcmds * subcmd)84 static 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 < 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 < 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).
decide_direction(int argc,char * const * argv,snd_pcm_stream_t * direction)139 static 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 < 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 < 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 < 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 
detect_subcmd(int argc,char * const * argv,enum subcmds * subcmd)202 static 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 < 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 
detect_direction(int argc,char * const * argv,snd_pcm_stream_t * direction)225 static 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 
main(int argc,char * const * argv)244 int 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