1 /*
2 *
3 * Copyright 2015 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 #include "test/core/util/cmdline.h"
20
21 #include <limits.h>
22 #include <stdio.h>
23 #include <string.h>
24
25 #include <vector>
26
27 #include "absl/strings/str_cat.h"
28 #include "absl/strings/str_format.h"
29 #include "absl/strings/str_join.h"
30
31 #include <grpc/support/alloc.h>
32 #include <grpc/support/log.h>
33 #include <grpc/support/string_util.h>
34 #include "src/core/lib/gpr/string.h"
35
36 typedef enum { ARGTYPE_INT, ARGTYPE_BOOL, ARGTYPE_STRING } argtype;
37
38 typedef struct arg {
39 const char* name;
40 const char* help;
41 argtype type;
42 void* value;
43 struct arg* next;
44 } arg;
45
46 struct gpr_cmdline {
47 const char* description;
48 arg* args;
49 const char* argv0;
50
51 const char* extra_arg_name;
52 const char* extra_arg_help;
53 void (*extra_arg)(void* user_data, const char* arg);
54 void* extra_arg_user_data;
55
56 int (*state)(gpr_cmdline* cl, char* arg);
57 arg* cur_arg;
58
59 int survive_failure;
60 };
61
62 static int normal_state(gpr_cmdline* cl, char* arg);
63
gpr_cmdline_create(const char * description)64 gpr_cmdline* gpr_cmdline_create(const char* description) {
65 gpr_cmdline* cl = static_cast<gpr_cmdline*>(gpr_zalloc(sizeof(gpr_cmdline)));
66
67 cl->description = description;
68 cl->state = normal_state;
69
70 return cl;
71 }
72
gpr_cmdline_set_survive_failure(gpr_cmdline * cl)73 void gpr_cmdline_set_survive_failure(gpr_cmdline* cl) {
74 cl->survive_failure = 1;
75 }
76
gpr_cmdline_destroy(gpr_cmdline * cl)77 void gpr_cmdline_destroy(gpr_cmdline* cl) {
78 while (cl->args) {
79 arg* a = cl->args;
80 cl->args = a->next;
81 gpr_free(a);
82 }
83 gpr_free(cl);
84 }
85
add_arg(gpr_cmdline * cl,const char * name,const char * help,argtype type,void * value)86 static void add_arg(gpr_cmdline* cl, const char* name, const char* help,
87 argtype type, void* value) {
88 arg* a;
89
90 for (a = cl->args; a; a = a->next) {
91 GPR_ASSERT(0 != strcmp(a->name, name));
92 }
93
94 a = static_cast<arg*>(gpr_zalloc(sizeof(arg)));
95 a->name = name;
96 a->help = help;
97 a->type = type;
98 a->value = value;
99 a->next = cl->args;
100 cl->args = a;
101 }
102
gpr_cmdline_add_int(gpr_cmdline * cl,const char * name,const char * help,int * value)103 void gpr_cmdline_add_int(gpr_cmdline* cl, const char* name, const char* help,
104 int* value) {
105 add_arg(cl, name, help, ARGTYPE_INT, value);
106 }
107
gpr_cmdline_add_flag(gpr_cmdline * cl,const char * name,const char * help,int * value)108 void gpr_cmdline_add_flag(gpr_cmdline* cl, const char* name, const char* help,
109 int* value) {
110 add_arg(cl, name, help, ARGTYPE_BOOL, value);
111 }
112
gpr_cmdline_add_string(gpr_cmdline * cl,const char * name,const char * help,const char ** value)113 void gpr_cmdline_add_string(gpr_cmdline* cl, const char* name, const char* help,
114 const char** value) {
115 add_arg(cl, name, help, ARGTYPE_STRING, value);
116 }
117
gpr_cmdline_on_extra_arg(gpr_cmdline * cl,const char * name,const char * help,void (* on_extra_arg)(void * user_data,const char * arg),void * user_data)118 void gpr_cmdline_on_extra_arg(
119 gpr_cmdline* cl, const char* name, const char* help,
120 void (*on_extra_arg)(void* user_data, const char* arg), void* user_data) {
121 GPR_ASSERT(!cl->extra_arg);
122 GPR_ASSERT(on_extra_arg);
123
124 cl->extra_arg = on_extra_arg;
125 cl->extra_arg_user_data = user_data;
126 cl->extra_arg_name = name;
127 cl->extra_arg_help = help;
128 }
129
130 /* recursively descend argument list, adding the last element
131 to s first - so that arguments are added in the order they were
132 added to the list by api calls */
add_args_to_usage(arg * a,std::vector<std::string> * s)133 static void add_args_to_usage(arg* a, std::vector<std::string>* s) {
134 if (a == nullptr) return;
135 add_args_to_usage(a->next, s);
136 switch (a->type) {
137 case ARGTYPE_BOOL:
138 s->push_back(absl::StrFormat(" [--%s|--no-%s]", a->name, a->name));
139 break;
140 case ARGTYPE_STRING:
141 s->push_back(absl::StrFormat(" [--%s=string]", a->name));
142 break;
143 case ARGTYPE_INT:
144 s->push_back(absl::StrFormat(" [--%s=int]", a->name));
145 break;
146 }
147 }
148
gpr_cmdline_usage_string(gpr_cmdline * cl,const char * argv0)149 std::string gpr_cmdline_usage_string(gpr_cmdline* cl, const char* argv0) {
150 const char* name = strrchr(argv0, '/');
151 if (name != nullptr) {
152 name++;
153 } else {
154 name = argv0;
155 }
156
157 std::vector<std::string> s;
158 s.push_back(absl::StrCat("Usage: ", name));
159 add_args_to_usage(cl->args, &s);
160 if (cl->extra_arg) {
161 s.push_back(absl::StrFormat(" [%s...]", cl->extra_arg_name));
162 }
163 s.push_back("\n");
164 return absl::StrJoin(s, "");
165 }
166
print_usage_and_die(gpr_cmdline * cl)167 static int print_usage_and_die(gpr_cmdline* cl) {
168 fprintf(stderr, "%s", gpr_cmdline_usage_string(cl, cl->argv0).c_str());
169 if (!cl->survive_failure) {
170 exit(1);
171 }
172 return 0;
173 }
174
extra_state(gpr_cmdline * cl,char * str)175 static int extra_state(gpr_cmdline* cl, char* str) {
176 if (!cl->extra_arg) {
177 return print_usage_and_die(cl);
178 }
179 cl->extra_arg(cl->extra_arg_user_data, str);
180 return 1;
181 }
182
find_arg(gpr_cmdline * cl,char * name)183 static arg* find_arg(gpr_cmdline* cl, char* name) {
184 arg* a;
185
186 for (a = cl->args; a; a = a->next) {
187 if (0 == strcmp(a->name, name)) {
188 break;
189 }
190 }
191
192 if (!a) {
193 fprintf(stderr, "Unknown argument: %s\n", name);
194 return nullptr;
195 }
196
197 return a;
198 }
199
value_state(gpr_cmdline * cl,char * str)200 static int value_state(gpr_cmdline* cl, char* str) {
201 long intval;
202 char* end;
203
204 GPR_ASSERT(cl->cur_arg);
205
206 switch (cl->cur_arg->type) {
207 case ARGTYPE_INT:
208 intval = strtol(str, &end, 0);
209 if (*end || intval < INT_MIN || intval > INT_MAX) {
210 fprintf(stderr, "expected integer, got '%s' for %s\n", str,
211 cl->cur_arg->name);
212 return print_usage_and_die(cl);
213 }
214 *static_cast<int*>(cl->cur_arg->value) = static_cast<int>(intval);
215 break;
216 case ARGTYPE_BOOL:
217 if (0 == strcmp(str, "1") || 0 == strcmp(str, "true")) {
218 *static_cast<int*>(cl->cur_arg->value) = 1;
219 } else if (0 == strcmp(str, "0") || 0 == strcmp(str, "false")) {
220 *static_cast<int*>(cl->cur_arg->value) = 0;
221 } else {
222 fprintf(stderr, "expected boolean, got '%s' for %s\n", str,
223 cl->cur_arg->name);
224 return print_usage_and_die(cl);
225 }
226 break;
227 case ARGTYPE_STRING:
228 *static_cast<char**>(cl->cur_arg->value) = str;
229 break;
230 }
231
232 cl->state = normal_state;
233 return 1;
234 }
235
normal_state(gpr_cmdline * cl,char * str)236 static int normal_state(gpr_cmdline* cl, char* str) {
237 char* eq = nullptr;
238 char* tmp = nullptr;
239 char* arg_name = nullptr;
240 int r = 1;
241
242 if (0 == strcmp(str, "-help") || 0 == strcmp(str, "--help") ||
243 0 == strcmp(str, "-h")) {
244 return print_usage_and_die(cl);
245 }
246
247 cl->cur_arg = nullptr;
248
249 if (str[0] == '-') {
250 if (str[1] == '-') {
251 if (str[2] == 0) {
252 /* handle '--' to move to just extra args */
253 cl->state = extra_state;
254 return 1;
255 }
256 str += 2;
257 } else {
258 str += 1;
259 }
260 /* first byte of str is now past the leading '-' or '--' */
261 if (str[0] == 'n' && str[1] == 'o' && str[2] == '-') {
262 /* str is of the form '--no-foo' - it's a flag disable */
263 str += 3;
264 cl->cur_arg = find_arg(cl, str);
265 if (cl->cur_arg == nullptr) {
266 return print_usage_and_die(cl);
267 }
268 if (cl->cur_arg->type != ARGTYPE_BOOL) {
269 fprintf(stderr, "%s is not a flag argument\n", str);
270 return print_usage_and_die(cl);
271 }
272 *static_cast<int*>(cl->cur_arg->value) = 0;
273 return 1; /* early out */
274 }
275 eq = strchr(str, '=');
276 if (eq != nullptr) {
277 /* copy the string into a temp buffer and extract the name */
278 tmp = arg_name =
279 static_cast<char*>(gpr_malloc(static_cast<size_t>(eq - str + 1)));
280 memcpy(arg_name, str, static_cast<size_t>(eq - str));
281 arg_name[eq - str] = 0;
282 } else {
283 arg_name = str;
284 }
285 cl->cur_arg = find_arg(cl, arg_name);
286 if (cl->cur_arg == nullptr) {
287 return print_usage_and_die(cl);
288 }
289 if (eq != nullptr) {
290 /* str was of the type --foo=value, parse the value */
291 r = value_state(cl, eq + 1);
292 } else if (cl->cur_arg->type != ARGTYPE_BOOL) {
293 /* flag types don't have a '--foo value' variant, other types do */
294 cl->state = value_state;
295 } else {
296 /* flag parameter: just set the value */
297 *static_cast<int*>(cl->cur_arg->value) = 1;
298 }
299 } else {
300 r = extra_state(cl, str);
301 }
302
303 gpr_free(tmp);
304 return r;
305 }
306
gpr_cmdline_parse(gpr_cmdline * cl,int argc,char ** argv)307 int gpr_cmdline_parse(gpr_cmdline* cl, int argc, char** argv) {
308 int i;
309
310 GPR_ASSERT(argc >= 1);
311 cl->argv0 = argv[0];
312
313 for (i = 1; i < argc; i++) {
314 if (!cl->state(cl, argv[i])) {
315 return 0;
316 }
317 }
318 return 1;
319 }
320