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