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