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