• 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 <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