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