• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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