ldb-samba: ldif_read_objectSid avoids VLA
[samba.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_section {
33         const char *name;
34         struct cmdline_command *commands;
35 };
36
37 struct cmdline_context {
38         const char *prog;
39         struct poptOption *options;
40         struct cmdline_section *section;
41         int num_sections;
42         size_t max_len;
43         poptContext pc;
44         int argc, arg0;
45         const char **argv;
46         struct cmdline_command *match_cmd;
47 };
48
49 static bool cmdline_show_help = false;
50
51 static void cmdline_popt_help(poptContext pc,
52                               enum poptCallbackReason reason,
53                               struct poptOption *key,
54                               const char *arg,
55                               void *data)
56 {
57         if (key->shortName == 'h') {
58                 cmdline_show_help = true;
59         }
60 }
61
62 struct poptOption cmdline_help_options[] = {
63         { NULL, '\0', POPT_ARG_CALLBACK, cmdline_popt_help, 0, NULL, NULL },
64         { "help", 'h', 0, NULL, 'h', "Show this help message", NULL },
65         POPT_TABLEEND
66 };
67
68 #define CMDLINE_HELP_OPTIONS \
69         { NULL, '\0', POPT_ARG_INCLUDE_TABLE, cmdline_help_options, \
70           0, "Help Options:", NULL }
71
72 static bool cmdline_option_check(struct poptOption *option)
73 {
74         if (option->longName == NULL) {
75                 D_ERR("Option has no long name\n");
76                 return false;
77         }
78
79         if (option->argInfo != POPT_ARG_STRING &&
80             option->argInfo != POPT_ARG_INT &&
81             option->argInfo != POPT_ARG_LONG &&
82             option->argInfo != POPT_ARG_VAL &&
83             option->argInfo != POPT_ARG_FLOAT &&
84             option->argInfo != POPT_ARG_DOUBLE) {
85                 D_ERR("Option '%s' has unsupported type\n", option->longName);
86                 return false;
87         }
88
89         if (option->arg == NULL) {
90                 D_ERR("Option '%s' has invalid arg\n", option->longName);
91                 return false;
92         }
93
94         if (option->descrip == NULL) {
95                 D_ERR("Option '%s' has no help msg\n", option->longName);
96                 return false;
97         }
98
99         return true;
100 }
101
102 static bool cmdline_options_check(struct poptOption *options)
103 {
104         int i;
105         bool ok;
106
107         if (options == NULL) {
108                 return true;
109         }
110
111         i = 0;
112         while (options[i].longName != NULL || options[i].shortName != '\0') {
113                 ok = cmdline_option_check(&options[i]);
114                 if (!ok) {
115                         return false;
116                 }
117                 i++;
118         }
119
120         return true;
121 }
122
123 static int cmdline_options_define(TALLOC_CTX *mem_ctx,
124                                   struct poptOption *user_options,
125                                   struct poptOption **result)
126 {
127         struct poptOption *options;
128         int count, i;
129
130         count = (user_options == NULL ? 2 : 3);
131
132         options = talloc_array(mem_ctx, struct poptOption, count);
133         if (options == NULL) {
134                 return ENOMEM;
135         }
136
137         i = 0;
138         options[i++] = (struct poptOption) CMDLINE_HELP_OPTIONS;
139         if (user_options != NULL) {
140                 options[i++] = (struct poptOption) {
141                         .argInfo = POPT_ARG_INCLUDE_TABLE,
142                         .arg = user_options,
143                         .descrip = "Options:",
144                 };
145         }
146         options[i++] = (struct poptOption) POPT_TABLEEND;
147
148         *result = options;
149         return 0;
150 }
151
152 static bool cmdline_command_check(struct cmdline_command *cmd, size_t *max_len)
153 {
154         size_t len;
155
156         if (cmd->name == NULL) {
157                 return false;
158         }
159
160         if (cmd->fn == NULL) {
161                 D_ERR("Command '%s' has no implementation function\n",
162                       cmd->name);
163                 return false;
164         }
165
166         if (cmd->msg_help == NULL) {
167                 D_ERR("Command '%s' has no help msg\n", cmd->name);
168                 return false;
169         }
170
171         len = strlen(cmd->name);
172         if (cmd->msg_args != NULL) {
173                 len += strlen(cmd->msg_args);
174         }
175         if (len > CMDLINE_MAX_LEN) {
176                 D_ERR("Command '%s' is too long (%zu)\n", cmd->name, len);
177                 return false;
178         }
179
180         if (len > *max_len) {
181                 *max_len = len;
182         }
183
184         len = strlen(cmd->msg_help);
185         if (len > CMDLINE_MAX_LEN) {
186                 D_ERR("Command '%s' help too long (%zu)\n", cmd->name, len);
187                 return false;
188         }
189
190         return true;
191 }
192
193 static bool cmdline_commands_check(struct cmdline_command *commands,
194                                    size_t *max_len)
195 {
196         int i;
197         bool ok;
198
199         if (commands == NULL) {
200                 return false;
201         }
202
203         for (i=0; commands[i].name != NULL; i++) {
204                 ok = cmdline_command_check(&commands[i], max_len);
205                 if (!ok) {
206                         return false;
207                 }
208         }
209
210         return true;
211 }
212
213 static int cmdline_context_destructor(struct cmdline_context *cmdline);
214
215 static int cmdline_section_add(struct cmdline_context *cmdline,
216                                const char *name,
217                                struct cmdline_command *commands)
218 {
219         struct cmdline_section *section;
220         size_t max_len = 0;
221         bool ok;
222
223         ok = cmdline_commands_check(commands, &max_len);
224         if (!ok) {
225                 return EINVAL;
226         }
227
228         section = talloc_realloc(cmdline,
229                                  cmdline->section,
230                                  struct cmdline_section,
231                                  cmdline->num_sections + 1);
232         if (section == NULL) {
233                 return ENOMEM;
234         }
235
236         section[cmdline->num_sections] = (struct cmdline_section) {
237                 .name = name,
238                 .commands = commands,
239         };
240
241         if (max_len > cmdline->max_len) {
242                 cmdline->max_len = max_len;
243         }
244
245         cmdline->section = section;
246         cmdline->num_sections += 1;
247
248         return 0;
249 }
250
251 int cmdline_init(TALLOC_CTX *mem_ctx,
252                  const char *prog,
253                  struct poptOption *options,
254                  const char *name,
255                  struct cmdline_command *commands,
256                  struct cmdline_context **result)
257 {
258         struct cmdline_context *cmdline;
259         int ret;
260         bool ok;
261
262         if (prog == NULL) {
263                 return EINVAL;
264         }
265
266         ok = cmdline_options_check(options);
267         if (!ok) {
268                 return EINVAL;
269         }
270
271         cmdline = talloc_zero(mem_ctx, struct  cmdline_context);
272         if (cmdline == NULL) {
273                 return ENOMEM;
274         }
275
276         cmdline->prog = talloc_strdup(cmdline, prog);
277         if (cmdline->prog == NULL) {
278                 talloc_free(cmdline);
279                 return ENOMEM;
280         }
281
282         ret = cmdline_options_define(cmdline, options, &cmdline->options);
283         if (ret != 0) {
284                 talloc_free(cmdline);
285                 return ret;
286         }
287
288         ret = cmdline_section_add(cmdline, name, commands);
289         if (ret != 0) {
290                 talloc_free(cmdline);
291                 return ret;
292         }
293
294         cmdline->argc = 1;
295         cmdline->argv = talloc_array(cmdline, const char *, 2);
296         if (cmdline->argv == NULL) {
297                 talloc_free(cmdline);
298                 return ENOMEM;
299         }
300         cmdline->argv[0] = cmdline->prog;
301         cmdline->argv[1] = NULL;
302
303         /* Dummy popt context for generating help */
304         cmdline->pc = poptGetContext(cmdline->prog,
305                                      cmdline->argc,
306                                      cmdline->argv,
307                                      cmdline->options,
308                                      0);
309         if (cmdline->pc == NULL) {
310                 talloc_free(cmdline);
311                 return ENOMEM;
312         }
313
314         talloc_set_destructor(cmdline, cmdline_context_destructor);
315
316         *result = cmdline;
317         return 0;
318 }
319
320 static int cmdline_context_destructor(struct cmdline_context *cmdline)
321 {
322         if (cmdline->pc != NULL) {
323                 poptFreeContext(cmdline->pc);
324         }
325
326         return 0;
327 }
328
329 int cmdline_add(struct cmdline_context *cmdline,
330                 const char *name,
331                 struct cmdline_command *commands)
332 {
333         return cmdline_section_add(cmdline, name, commands);
334 }
335
336 static int cmdline_parse_options(struct cmdline_context *cmdline,
337                                  int argc,
338                                  const char **argv)
339 {
340         int opt;
341
342         if (cmdline->pc != NULL) {
343                 poptFreeContext(cmdline->pc);
344         }
345
346         cmdline->pc = poptGetContext(cmdline->prog,
347                                      argc,
348                                      argv,
349                                      cmdline->options,
350                                      0);
351         if (cmdline->pc == NULL) {
352                 return ENOMEM;
353         }
354
355         while ((opt = poptGetNextOpt(cmdline->pc)) != -1) {
356                 D_ERR("Invalid option %s: %s\n",
357                       poptBadOption(cmdline->pc, 0),
358                       poptStrerror(opt));
359                 return EINVAL;
360         }
361
362         /* Set up remaining arguments for commands */
363         cmdline->argc = 0;
364         cmdline->argv = poptGetArgs(cmdline->pc);
365         if (cmdline->argv != NULL) {
366                 while (cmdline->argv[cmdline->argc] != NULL) {
367                         cmdline->argc++;
368                 }
369         }
370
371         return 0;
372 }
373
374 static int cmdline_match_section(struct cmdline_context *cmdline,
375                                  struct cmdline_section *section)
376 {
377         int i;
378
379         for (i=0; section->commands[i].name != NULL; i++) {
380                 struct cmdline_command *cmd;
381                 char name[CMDLINE_MAX_LEN+1];
382                 size_t len;
383                 char *t, *str;
384                 int n = 0;
385                 bool match = false;
386
387                 cmd = &section->commands[i];
388                 len = strlcpy(name, cmd->name, sizeof(name));
389                 if (len >= sizeof(name)) {
390                         D_ERR("Skipping long command '%s'\n", cmd->name);
391                         continue;
392                 }
393
394                 str = name;
395                 while ((t = strtok(str, " ")) != NULL) {
396                         if (n >= cmdline->argc) {
397                                 match = false;
398                                 break;
399                         }
400                         if (cmdline->argv[n] == NULL) {
401                                 match = false;
402                                 break;
403                         }
404                         if (strcmp(cmdline->argv[n], t) == 0) {
405                                 match = true;
406                                 cmdline->arg0 = n+1;
407                         } else {
408                                 match = false;
409                                 break;
410                         }
411
412                         n += 1;
413                         str = NULL;
414                 }
415
416                 if (match) {
417                         cmdline->match_cmd = cmd;
418                         return 0;
419                 }
420         }
421
422         cmdline->match_cmd = NULL;
423         return ENOENT;
424 }
425
426 static int cmdline_match(struct cmdline_context *cmdline)
427 {
428         int i, ret = ENOENT;
429
430         if (cmdline->argc == 0 || cmdline->argv == NULL) {
431                 cmdline->match_cmd = NULL;
432                 return EINVAL;
433         }
434
435         for (i=0; i<cmdline->num_sections; i++) {
436                 ret = cmdline_match_section(cmdline, &cmdline->section[i]);
437                 if (ret == 0) {
438                         break;
439                 }
440         }
441
442         return ret;
443 }
444
445 int cmdline_parse(struct cmdline_context *cmdline,
446                   int argc,
447                   const char **argv,
448                   bool parse_options)
449 {
450         int ret;
451
452         if (argc < 2) {
453                 cmdline_usage(cmdline, NULL);
454                 return EINVAL;
455         }
456
457         cmdline_show_help = false;
458
459         if (parse_options) {
460                 ret = cmdline_parse_options(cmdline, argc, argv);
461                 if (ret != 0) {
462                         cmdline_usage(cmdline, NULL);
463                         return ret;
464                 }
465         } else {
466                 cmdline->argc = argc;
467                 cmdline->argv = argv;
468         }
469
470         ret = cmdline_match(cmdline);
471
472         if (ret != 0 || cmdline_show_help) {
473                 const char *name = NULL;
474
475                 if (cmdline->match_cmd != NULL) {
476                         name = cmdline->match_cmd->name;
477                 }
478
479                 cmdline_usage(cmdline, name);
480
481                 if (cmdline_show_help) {
482                         ret = EAGAIN;
483                 }
484         }
485
486         return ret;
487 }
488
489 static void cmdline_usage_command(struct cmdline_context *cmdline,
490                                   struct cmdline_command *cmd,
491                                   bool print_all)
492 {
493         size_t len;
494
495         len = strlen(cmd->name);
496
497         printf("  %s ", cmd->name);
498         if (print_all) {
499                 printf("%-*s",
500                        (int)(cmdline->max_len-len),
501                        cmd->msg_args == NULL ? "" : cmd->msg_args);
502         } else {
503                 printf("%s", cmd->msg_args == NULL ? "" : cmd->msg_args);
504         }
505         printf("     %s\n", cmd->msg_help);
506 }
507
508 static void cmdline_usage_section(struct cmdline_context *cmdline,
509                                   struct cmdline_section *section)
510 {
511         int i;
512
513         printf("\n");
514
515         if (section->name != NULL) {
516                 printf("%s ", section->name);
517         }
518         printf("Commands:\n");
519         for (i=0; section->commands[i].name != NULL; i++) {
520                 cmdline_usage_command(cmdline, &section->commands[i], true);
521
522         }
523 }
524
525 static void cmdline_usage_full(struct cmdline_context *cmdline)
526 {
527         int i;
528
529         poptSetOtherOptionHelp(cmdline->pc, "[<options>] <command> [<args>]");
530         poptPrintHelp(cmdline->pc, stdout, 0);
531
532         for (i=0; i<cmdline->num_sections; i++) {
533                 cmdline_usage_section(cmdline, &cmdline->section[i]);
534         }
535 }
536
537 void cmdline_usage(struct cmdline_context *cmdline, const char *cmd_name)
538 {
539         struct cmdline_command *cmd = NULL;
540         int i, j;
541
542         if (cmd_name == NULL) {
543                 cmdline_usage_full(cmdline);
544                 return;
545         }
546
547         for (j=0; j<cmdline->num_sections; j++) {
548                 struct cmdline_section *section = &cmdline->section[j];
549
550                 for (i=0; section->commands[i].name != NULL; i++) {
551                         if (strcmp(section->commands[i].name, cmd_name) == 0) {
552                                 cmd = &section->commands[i];
553                                 break;
554                         }
555                 }
556         }
557
558         if (cmd == NULL) {
559                 cmdline_usage_full(cmdline);
560                 return;
561         }
562
563         poptSetOtherOptionHelp(cmdline->pc, "<command> [<args>]");
564         poptPrintUsage(cmdline->pc, stdout, 0);
565
566         printf("\n");
567         cmdline_usage_command(cmdline, cmd, false);
568 }
569
570 int cmdline_run(struct cmdline_context *cmdline,
571                 void *private_data,
572                 int *result)
573 {
574         struct cmdline_command *cmd = cmdline->match_cmd;
575         TALLOC_CTX *tmp_ctx;
576         int ret;
577
578         if (cmd == NULL) {
579                 return ENOENT;
580         }
581
582         tmp_ctx = talloc_new(cmdline);
583         if (tmp_ctx == NULL) {
584                 return ENOMEM;
585         }
586
587         ret = cmd->fn(tmp_ctx,
588                       cmdline->argc - cmdline->arg0,
589                       &cmdline->argv[cmdline->arg0],
590                       private_data);
591
592         talloc_free(tmp_ctx);
593
594         if (result != NULL) {
595                 *result = ret;
596         }
597         return 0;
598 }