ctdb-tools: Add event daemon config options to config tool
[vlendec/samba-autobuild/.git] / ctdb / common / cmdline.c
1 /*
2    Command line processing
3
4    Copyright (C) Amitay Isaacs  2018
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "replace.h"
21
22 #include <popt.h>
23 #include <talloc.h>
24 #include <tevent.h>
25
26 #include "lib/util/debug.h"
27
28 #include "common/cmdline.h"
29
30 #define CMDLINE_MAX_LEN         80
31
32 struct cmdline_context {
33         const char *prog;
34         struct poptOption *options;
35         struct cmdline_command *commands;
36         int max_len;
37         poptContext pc;
38         int argc, arg0;
39         const char **argv;
40         struct cmdline_command *match_cmd;
41 };
42
43 static bool cmdline_show_help = false;
44
45 static void cmdline_popt_help(poptContext pc,
46                               enum poptCallbackReason reason,
47                               struct poptOption *key,
48                               const char *arg,
49                               void *data)
50 {
51         if (key->shortName == 'h') {
52                 cmdline_show_help = true;
53         }
54 }
55
56 struct poptOption cmdline_help_options[] = {
57         { NULL, '\0', POPT_ARG_CALLBACK, cmdline_popt_help, 0, NULL, NULL },
58         { "help", 'h', 0, NULL, 'h', "Show this help message", NULL },
59         POPT_TABLEEND
60 };
61
62 #define CMDLINE_HELP_OPTIONS \
63         { NULL, '\0', POPT_ARG_INCLUDE_TABLE, cmdline_help_options, \
64           0, "Help Options:", NULL }
65
66 static bool cmdline_option_check(struct poptOption *option)
67 {
68         if (option->longName == NULL) {
69                 D_ERR("Option has no long name\n");
70                 return false;
71         }
72
73         if (option->argInfo != POPT_ARG_STRING &&
74             option->argInfo != POPT_ARG_INT &&
75             option->argInfo != POPT_ARG_LONG &&
76             option->argInfo != POPT_ARG_VAL &&
77             option->argInfo != POPT_ARG_FLOAT &&
78             option->argInfo != POPT_ARG_DOUBLE) {
79                 D_ERR("Option '%s' has unsupported type\n", option->longName);
80                 return false;
81         }
82
83         if (option->arg == NULL) {
84                 D_ERR("Option '%s' has invalid arg\n", option->longName);
85                 return false;
86         }
87
88         if (option->descrip == NULL) {
89                 D_ERR("Option '%s' has no help msg\n", option->longName);
90                 return false;
91         }
92
93         return true;
94 }
95
96 static bool cmdline_options_check(struct poptOption *options)
97 {
98         int i;
99         bool ok;
100
101         if (options == NULL) {
102                 return true;
103         }
104
105         i = 0;
106         while (options[i].longName != NULL || options[i].shortName != '\0') {
107                 ok = cmdline_option_check(&options[i]);
108                 if (!ok) {
109                         return false;
110                 }
111                 i++;
112         }
113
114         return true;
115 }
116
117 static int cmdline_options_define(TALLOC_CTX *mem_ctx,
118                                   struct poptOption *user_options,
119                                   struct poptOption **result)
120 {
121         struct poptOption *options;
122         int count, i;
123
124         count = (user_options == NULL ? 2 : 3);
125
126         options = talloc_array(mem_ctx, struct poptOption, count);
127         if (options == NULL) {
128                 return ENOMEM;
129         }
130
131         i = 0;
132         options[i++] = (struct poptOption) CMDLINE_HELP_OPTIONS;
133         if (user_options != NULL) {
134                 options[i++] = (struct poptOption) {
135                         .argInfo = POPT_ARG_INCLUDE_TABLE,
136                         .arg = user_options,
137                         .descrip = "Options:",
138                 };
139         }
140         options[i++] = (struct poptOption) POPT_TABLEEND;
141
142         *result = options;
143         return 0;
144 }
145
146 static bool cmdline_command_check(struct cmdline_command *cmd, int *max_len)
147 {
148         size_t len;
149
150         if (cmd->name == NULL) {
151                 return false;
152         }
153
154         if (cmd->fn == NULL) {
155                 D_ERR("Command '%s' has no implementation function\n",
156                       cmd->name);
157                 return false;
158         }
159
160         if (cmd->msg_help == NULL) {
161                 D_ERR("Command '%s' has no help msg\n", cmd->name);
162                 return false;
163         }
164
165         len = strlen(cmd->name);
166         if (cmd->msg_args != NULL) {
167                 len += strlen(cmd->msg_args);
168         }
169         if (len > CMDLINE_MAX_LEN) {
170                 D_ERR("Command '%s' is too long (%zu)\n", cmd->name, len);
171                 return false;
172         }
173
174         if (len > *max_len) {
175                 *max_len = (int)len;
176         }
177
178         len = strlen(cmd->msg_help);
179         if (len > CMDLINE_MAX_LEN) {
180                 D_ERR("Command '%s' help too long (%zu)\n", cmd->name, len);
181                 return false;
182         }
183
184         return true;
185 }
186
187 static bool cmdline_commands_check(struct cmdline_command *commands,
188                                    int *max_len)
189 {
190         int i;
191         bool ok;
192
193         if (commands == NULL) {
194                 return false;
195         }
196
197         for (i=0; commands[i].name != NULL; i++) {
198                 ok = cmdline_command_check(&commands[i], max_len);
199                 if (!ok) {
200                         return false;
201                 }
202         }
203
204         return true;
205 }
206
207 static int cmdline_context_destructor(struct cmdline_context *cmdline);
208
209 int cmdline_init(TALLOC_CTX *mem_ctx,
210                  const char *prog,
211                  struct poptOption *options,
212                  struct cmdline_command *commands,
213                  struct cmdline_context **result)
214 {
215         struct cmdline_context *cmdline;
216         int ret, max_len = 0;
217         bool ok;
218
219         if (prog == NULL) {
220                 return EINVAL;
221         }
222
223         ok = cmdline_options_check(options);
224         if (!ok) {
225                 return EINVAL;
226         }
227
228         ok = cmdline_commands_check(commands, &max_len);
229         if (!ok) {
230                 return EINVAL;
231         }
232
233         cmdline = talloc_zero(mem_ctx, struct  cmdline_context);
234         if (cmdline == NULL) {
235                 return ENOMEM;
236         }
237
238         cmdline->prog = talloc_strdup(cmdline, prog);
239         if (cmdline->prog == NULL) {
240                 talloc_free(cmdline);
241                 return ENOMEM;
242         }
243
244         ret = cmdline_options_define(cmdline, options, &cmdline->options);
245         if (ret != 0) {
246                 talloc_free(cmdline);
247                 return ret;
248         }
249         cmdline->commands = commands;
250         cmdline->max_len = max_len;
251
252         cmdline->argc = 1;
253         cmdline->argv = talloc_array(cmdline, const char *, 2);
254         if (cmdline->argv == NULL) {
255                 talloc_free(cmdline);
256                 return ENOMEM;
257         }
258         cmdline->argv[0] = cmdline->prog;
259         cmdline->argv[1] = NULL;
260
261         /* Dummy popt context for generating help */
262         cmdline->pc = poptGetContext(cmdline->prog,
263                                      cmdline->argc,
264                                      cmdline->argv,
265                                      cmdline->options,
266                                      0);
267         if (cmdline->pc == NULL) {
268                 talloc_free(cmdline);
269                 return ENOMEM;
270         }
271
272         talloc_set_destructor(cmdline, cmdline_context_destructor);
273
274         *result = cmdline;
275         return 0;
276 }
277
278 static int cmdline_context_destructor(struct cmdline_context *cmdline)
279 {
280         if (cmdline->pc != NULL) {
281                 poptFreeContext(cmdline->pc);
282         }
283
284         return 0;
285 }
286
287 static int cmdline_parse_options(struct cmdline_context *cmdline,
288                                  int argc,
289                                  const char **argv)
290 {
291         int opt;
292
293         if (cmdline->pc != NULL) {
294                 poptFreeContext(cmdline->pc);
295         }
296
297         cmdline->pc = poptGetContext(cmdline->prog,
298                                      argc,
299                                      argv,
300                                      cmdline->options,
301                                      0);
302         if (cmdline->pc == NULL) {
303                 return ENOMEM;
304         }
305
306         while ((opt = poptGetNextOpt(cmdline->pc)) != -1) {
307                 D_ERR("Invalid option %s: %s\n",
308                       poptBadOption(cmdline->pc, 0),
309                       poptStrerror(opt));
310                 return EINVAL;
311         }
312
313         /* Set up remaining arguments for commands */
314         cmdline->argc = 0;
315         cmdline->argv = poptGetArgs(cmdline->pc);
316         if (cmdline->argv != NULL) {
317                 while (cmdline->argv[cmdline->argc] != NULL) {
318                         cmdline->argc++;
319                 }
320         }
321
322         return 0;
323 }
324
325 static int cmdline_match(struct cmdline_context *cmdline)
326 {
327         int i;
328
329         if (cmdline->argc == 0 || cmdline->argv == NULL) {
330                 cmdline->match_cmd = NULL;
331                 return EINVAL;
332         }
333
334         for (i=0; cmdline->commands[i].name != NULL; i++) {
335                 struct cmdline_command *cmd;
336                 char name[CMDLINE_MAX_LEN+1];
337                 size_t len;
338                 char *t, *str;
339                 int n = 0;
340                 bool match = false;
341
342                 cmd = &cmdline->commands[i];
343                 len = strlcpy(name, cmd->name, sizeof(name));
344                 if (len >= sizeof(name)) {
345                         D_ERR("Skipping long command '%s'\n", cmd->name);
346                         continue;
347                 }
348
349                 str = name;
350                 while ((t = strtok(str, " ")) != NULL) {
351                         if (n >= cmdline->argc) {
352                                 match = false;
353                                 break;
354                         }
355                         if (cmdline->argv[n] == NULL) {
356                                 match = false;
357                                 break;
358                         }
359                         if (strcmp(cmdline->argv[n], t) == 0) {
360                                 match = true;
361                                 cmdline->arg0 = n+1;
362                         } else {
363                                 match = false;
364                                 break;
365                         }
366
367                         n += 1;
368                         str = NULL;
369                 }
370
371                 if (match) {
372                         cmdline->match_cmd = cmd;
373                         return 0;
374                 }
375         }
376
377         cmdline->match_cmd = NULL;
378         return ENOENT;
379 }
380
381 int cmdline_parse(struct cmdline_context *cmdline,
382                   int argc,
383                   const char **argv,
384                   bool parse_options)
385 {
386         int ret;
387
388         if (argc < 2) {
389                 return EINVAL;
390         }
391
392         cmdline_show_help = false;
393
394         if (parse_options) {
395                 ret = cmdline_parse_options(cmdline, argc, argv);
396                 if (ret != 0) {
397                         return ret;
398                 }
399         } else {
400                 cmdline->argc = argc;
401                 cmdline->argv = argv;
402         }
403
404         ret = cmdline_match(cmdline);
405         if (!cmdline_show_help && ret != 0) {
406                 return ret;
407         }
408
409         return 0;
410 }
411
412 static void cmdline_usage_command(struct cmdline_context *cmdline,
413                                   struct cmdline_command *cmd,
414                                   bool print_all)
415 {
416         int len;
417
418         len = (int)strlen(cmd->name);
419
420         printf("  %s ", cmd->name);
421         if (print_all) {
422                 printf("%-*s",
423                        cmdline->max_len-len,
424                        cmd->msg_args == NULL ? "" : cmd->msg_args);
425         } else {
426                 printf("%s", cmd->msg_args == NULL ? "" : cmd->msg_args);
427         }
428         printf("     %s\n", cmd->msg_help);
429 }
430
431 static void cmdline_usage_full(struct cmdline_context *cmdline)
432 {
433         int i;
434
435         poptSetOtherOptionHelp(cmdline->pc, "[<options>] <command> [<args>]");
436         poptPrintHelp(cmdline->pc, stdout, 0);
437
438         printf("\nCommands:\n");
439         for (i=0; cmdline->commands[i].name != NULL; i++) {
440                 cmdline_usage_command(cmdline, &cmdline->commands[i], true);
441
442         }
443 }
444
445 void cmdline_usage(struct cmdline_context *cmdline, const char *cmd_name)
446 {
447         struct cmdline_command *cmd = NULL;
448         int i;
449
450         if (cmd_name == NULL) {
451                 cmdline_usage_full(cmdline);
452                 return;
453         }
454
455         for (i=0; cmdline->commands[i].name != NULL; i++) {
456                 if (strcmp(cmdline->commands[i].name, cmd_name) == 0) {
457                         cmd = &cmdline->commands[i];
458                         break;
459                 }
460         }
461
462         if (cmd == NULL) {
463                 cmdline_usage_full(cmdline);
464                 return;
465         }
466
467         poptSetOtherOptionHelp(cmdline->pc, "<command> [<args>]");
468         poptPrintUsage(cmdline->pc, stdout, 0);
469
470         printf("\n");
471         cmdline_usage_command(cmdline, cmd, false);
472 }
473
474 int cmdline_run(struct cmdline_context *cmdline,
475                 void *private_data,
476                 int *result)
477 {
478         struct cmdline_command *cmd = cmdline->match_cmd;
479         TALLOC_CTX *tmp_ctx;
480         int ret;
481
482         if (cmdline_show_help) {
483                 const char *name = NULL;
484
485                 if (cmd != NULL) {
486                         name = cmdline->match_cmd->name;
487                 }
488
489                 cmdline_usage(cmdline, name);
490
491                 if (result != NULL) {
492                         *result = 0;
493                 }
494                 return EAGAIN;
495         }
496
497         if (cmd == NULL) {
498                 return ENOENT;
499         }
500
501         tmp_ctx = talloc_new(cmdline);
502         if (tmp_ctx == NULL) {
503                 return ENOMEM;
504         }
505
506         ret = cmd->fn(tmp_ctx,
507                       cmdline->argc - cmdline->arg0,
508                       &cmdline->argv[cmdline->arg0],
509                       private_data);
510
511         talloc_free(tmp_ctx);
512
513         if (result != NULL) {
514                 *result = ret;
515         }
516         return 0;
517 }