blob: eff46a16557b014f00abef9424e6d2f85ccce469 [file] [log] [blame]
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -08001/*
2 *
Craig Tiller2e190362016-03-25 14:33:26 -07003 * Copyright 2015-2016, Google Inc.
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -08004 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 * * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following disclaimer
14 * in the documentation and/or other materials provided with the
15 * distribution.
16 * * Neither the name of Google Inc. nor the names of its
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 *
32 */
33
nnoble9f312b42015-01-07 01:19:35 -080034#include <grpc/support/cmdline.h>
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080035
36#include <limits.h>
37#include <stdio.h>
38#include <string.h>
39
nnoble9f312b42015-01-07 01:19:35 -080040#include <grpc/support/alloc.h>
41#include <grpc/support/log.h>
Masood Malekghassemi701af602015-06-03 15:01:17 -070042#include <grpc/support/string_util.h>
Craig Tillerf40df232016-03-25 13:38:14 -070043#include "src/core/support/string.h"
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080044
45typedef enum { ARGTYPE_INT, ARGTYPE_BOOL, ARGTYPE_STRING } argtype;
46
47typedef struct arg {
48 const char *name;
49 const char *help;
50 argtype type;
51 void *value;
52 struct arg *next;
53} arg;
54
55struct gpr_cmdline {
56 const char *description;
57 arg *args;
58 const char *argv0;
59
60 const char *extra_arg_name;
61 const char *extra_arg_help;
62 void (*extra_arg)(void *user_data, const char *arg);
63 void *extra_arg_user_data;
64
Craig Tiller1fa1ab82015-12-10 09:22:55 -080065 int (*state)(gpr_cmdline *cl, char *arg);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080066 arg *cur_arg;
Craig Tiller1fa1ab82015-12-10 09:22:55 -080067
68 int survive_failure;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080069};
70
Craig Tiller1fa1ab82015-12-10 09:22:55 -080071static int normal_state(gpr_cmdline *cl, char *arg);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080072
73gpr_cmdline *gpr_cmdline_create(const char *description) {
74 gpr_cmdline *cl = gpr_malloc(sizeof(gpr_cmdline));
75 memset(cl, 0, sizeof(gpr_cmdline));
76
77 cl->description = description;
78 cl->state = normal_state;
79
80 return cl;
81}
82
Craig Tiller1fa1ab82015-12-10 09:22:55 -080083void gpr_cmdline_set_survive_failure(gpr_cmdline *cl) {
84 cl->survive_failure = 1;
85}
86
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080087void gpr_cmdline_destroy(gpr_cmdline *cl) {
88 while (cl->args) {
89 arg *a = cl->args;
90 cl->args = a->next;
91 gpr_free(a);
92 }
93 gpr_free(cl);
94}
95
96static void add_arg(gpr_cmdline *cl, const char *name, const char *help,
97 argtype type, void *value) {
98 arg *a;
99
100 for (a = cl->args; a; a = a->next) {
101 GPR_ASSERT(0 != strcmp(a->name, name));
102 }
103
104 a = gpr_malloc(sizeof(arg));
105 memset(a, 0, sizeof(arg));
106 a->name = name;
107 a->help = help;
108 a->type = type;
109 a->value = value;
110 a->next = cl->args;
111 cl->args = a;
112}
113
114void gpr_cmdline_add_int(gpr_cmdline *cl, const char *name, const char *help,
115 int *value) {
116 add_arg(cl, name, help, ARGTYPE_INT, value);
117}
118
119void gpr_cmdline_add_flag(gpr_cmdline *cl, const char *name, const char *help,
120 int *value) {
121 add_arg(cl, name, help, ARGTYPE_BOOL, value);
122}
123
124void gpr_cmdline_add_string(gpr_cmdline *cl, const char *name, const char *help,
125 char **value) {
126 add_arg(cl, name, help, ARGTYPE_STRING, value);
127}
128
129void gpr_cmdline_on_extra_arg(
130 gpr_cmdline *cl, const char *name, const char *help,
131 void (*on_extra_arg)(void *user_data, const char *arg), void *user_data) {
132 GPR_ASSERT(!cl->extra_arg);
133 GPR_ASSERT(on_extra_arg);
134
135 cl->extra_arg = on_extra_arg;
136 cl->extra_arg_user_data = user_data;
137 cl->extra_arg_name = name;
138 cl->extra_arg_help = help;
139}
140
Craig Tillere7023612015-05-28 08:00:14 -0700141/* recursively descend argument list, adding the last element
142 to s first - so that arguments are added in the order they were
143 added to the list by api calls */
144static void add_args_to_usage(gpr_strvec *s, arg *a) {
145 char *tmp;
146
147 if (!a) return;
148 add_args_to_usage(s, a->next);
149
150 switch (a->type) {
151 case ARGTYPE_BOOL:
152 gpr_asprintf(&tmp, " [--%s|--no-%s]", a->name, a->name);
153 gpr_strvec_add(s, tmp);
154 break;
155 case ARGTYPE_STRING:
156 gpr_asprintf(&tmp, " [--%s=string]", a->name);
157 gpr_strvec_add(s, tmp);
158 break;
159 case ARGTYPE_INT:
160 gpr_asprintf(&tmp, " [--%s=int]", a->name);
161 gpr_strvec_add(s, tmp);
162 break;
163 }
164}
165
166char *gpr_cmdline_usage_string(gpr_cmdline *cl, const char *argv0) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800167 /* TODO(ctiller): make this prettier */
Craig Tillere7023612015-05-28 08:00:14 -0700168 gpr_strvec s;
169 char *tmp;
170 const char *name = strrchr(argv0, '/');
171
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800172 if (name) {
173 name++;
174 } else {
Craig Tillere7023612015-05-28 08:00:14 -0700175 name = argv0;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800176 }
Craig Tillere7023612015-05-28 08:00:14 -0700177
178 gpr_strvec_init(&s);
179
180 gpr_asprintf(&tmp, "Usage: %s", name);
181 gpr_strvec_add(&s, tmp);
182 add_args_to_usage(&s, cl->args);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800183 if (cl->extra_arg) {
Craig Tillere7023612015-05-28 08:00:14 -0700184 gpr_asprintf(&tmp, " [%s...]", cl->extra_arg_name);
185 gpr_strvec_add(&s, tmp);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800186 }
Craig Tillere7023612015-05-28 08:00:14 -0700187 gpr_strvec_add(&s, gpr_strdup("\n"));
188
189 tmp = gpr_strvec_flatten(&s, NULL);
190 gpr_strvec_destroy(&s);
191 return tmp;
192}
193
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800194static int print_usage_and_die(gpr_cmdline *cl) {
Craig Tillere7023612015-05-28 08:00:14 -0700195 char *usage = gpr_cmdline_usage_string(cl, cl->argv0);
196 fprintf(stderr, "%s", usage);
197 gpr_free(usage);
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800198 if (!cl->survive_failure) {
199 exit(1);
200 }
201 return 0;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800202}
203
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800204static int extra_state(gpr_cmdline *cl, char *str) {
205 if (!cl->extra_arg) {
206 return print_usage_and_die(cl);
207 }
Craig Tillerb9d35962015-09-11 13:31:16 -0700208 cl->extra_arg(cl->extra_arg_user_data, str);
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800209 return 1;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800210}
211
212static arg *find_arg(gpr_cmdline *cl, char *name) {
213 arg *a;
214
215 for (a = cl->args; a; a = a->next) {
216 if (0 == strcmp(a->name, name)) {
217 break;
218 }
219 }
220
221 if (!a) {
222 fprintf(stderr, "Unknown argument: %s\n", name);
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800223 return NULL;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800224 }
225
226 return a;
227}
228
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800229static int value_state(gpr_cmdline *cl, char *str) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800230 long intval;
231 char *end;
232
233 GPR_ASSERT(cl->cur_arg);
234
235 switch (cl->cur_arg->type) {
236 case ARGTYPE_INT:
Craig Tillerb9d35962015-09-11 13:31:16 -0700237 intval = strtol(str, &end, 0);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800238 if (*end || intval < INT_MIN || intval > INT_MAX) {
Craig Tillerb9d35962015-09-11 13:31:16 -0700239 fprintf(stderr, "expected integer, got '%s' for %s\n", str,
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800240 cl->cur_arg->name);
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800241 return print_usage_and_die(cl);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800242 }
murgatroid995e71d7a2015-06-19 12:24:44 -0700243 *(int *)cl->cur_arg->value = (int)intval;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800244 break;
245 case ARGTYPE_BOOL:
Craig Tillerb9d35962015-09-11 13:31:16 -0700246 if (0 == strcmp(str, "1") || 0 == strcmp(str, "true")) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800247 *(int *)cl->cur_arg->value = 1;
Craig Tillerb9d35962015-09-11 13:31:16 -0700248 } else if (0 == strcmp(str, "0") || 0 == strcmp(str, "false")) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800249 *(int *)cl->cur_arg->value = 0;
250 } else {
Craig Tillerb9d35962015-09-11 13:31:16 -0700251 fprintf(stderr, "expected boolean, got '%s' for %s\n", str,
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800252 cl->cur_arg->name);
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800253 return print_usage_and_die(cl);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800254 }
255 break;
256 case ARGTYPE_STRING:
Craig Tillerb9d35962015-09-11 13:31:16 -0700257 *(char **)cl->cur_arg->value = str;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800258 break;
259 }
260
261 cl->state = normal_state;
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800262 return 1;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800263}
264
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800265static int normal_state(gpr_cmdline *cl, char *str) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800266 char *eq = NULL;
267 char *tmp = NULL;
268 char *arg_name = NULL;
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800269 int r = 1;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800270
Craig Tillerb9d35962015-09-11 13:31:16 -0700271 if (0 == strcmp(str, "-help") || 0 == strcmp(str, "--help") ||
272 0 == strcmp(str, "-h")) {
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800273 return print_usage_and_die(cl);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800274 }
275
276 cl->cur_arg = NULL;
277
Craig Tillerb9d35962015-09-11 13:31:16 -0700278 if (str[0] == '-') {
279 if (str[1] == '-') {
280 if (str[2] == 0) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800281 /* handle '--' to move to just extra args */
282 cl->state = extra_state;
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800283 return 1;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800284 }
Craig Tillerb9d35962015-09-11 13:31:16 -0700285 str += 2;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800286 } else {
Craig Tillerb9d35962015-09-11 13:31:16 -0700287 str += 1;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800288 }
Craig Tillerb9d35962015-09-11 13:31:16 -0700289 /* first byte of str is now past the leading '-' or '--' */
290 if (str[0] == 'n' && str[1] == 'o' && str[2] == '-') {
291 /* str is of the form '--no-foo' - it's a flag disable */
292 str += 3;
293 cl->cur_arg = find_arg(cl, str);
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800294 if (cl->cur_arg == NULL) {
295 return print_usage_and_die(cl);
296 }
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800297 if (cl->cur_arg->type != ARGTYPE_BOOL) {
Craig Tillerb9d35962015-09-11 13:31:16 -0700298 fprintf(stderr, "%s is not a flag argument\n", str);
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800299 return print_usage_and_die(cl);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800300 }
301 *(int *)cl->cur_arg->value = 0;
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800302 return 1; /* early out */
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800303 }
Craig Tillerb9d35962015-09-11 13:31:16 -0700304 eq = strchr(str, '=');
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800305 if (eq != NULL) {
306 /* copy the string into a temp buffer and extract the name */
Craig Tillerb9d35962015-09-11 13:31:16 -0700307 tmp = arg_name = gpr_malloc((size_t)(eq - str + 1));
308 memcpy(arg_name, str, (size_t)(eq - str));
309 arg_name[eq - str] = 0;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800310 } else {
Craig Tillerb9d35962015-09-11 13:31:16 -0700311 arg_name = str;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800312 }
313 cl->cur_arg = find_arg(cl, arg_name);
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800314 if (cl->cur_arg == NULL) {
315 return print_usage_and_die(cl);
316 }
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800317 if (eq != NULL) {
Craig Tillerb9d35962015-09-11 13:31:16 -0700318 /* str was of the type --foo=value, parse the value */
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800319 r = value_state(cl, eq + 1);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800320 } else if (cl->cur_arg->type != ARGTYPE_BOOL) {
321 /* flag types don't have a '--foo value' variant, other types do */
322 cl->state = value_state;
323 } else {
324 /* flag parameter: just set the value */
325 *(int *)cl->cur_arg->value = 1;
326 }
327 } else {
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800328 r = extra_state(cl, str);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800329 }
330
331 gpr_free(tmp);
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800332 return r;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800333}
334
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800335int gpr_cmdline_parse(gpr_cmdline *cl, int argc, char **argv) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800336 int i;
337
338 GPR_ASSERT(argc >= 1);
339 cl->argv0 = argv[0];
340
341 for (i = 1; i < argc; i++) {
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800342 if (!cl->state(cl, argv[i])) {
343 return 0;
344 }
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800345 }
Craig Tiller1fa1ab82015-12-10 09:22:55 -0800346 return 1;
Craig Tiller190d3602015-02-18 09:23:38 -0800347}