]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
moving forward... many cleanups
authorAlan T. DeKok <aland@freeradius.org>
Thu, 28 Jun 2018 21:14:41 +0000 (17:14 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Sat, 30 Jun 2018 13:07:44 +0000 (09:07 -0400)
* entering "foo bar baz" as a partial command now lets you do
  "exit" once to get back to the main prompt.  i.e. you DON'T
  have to type "exit" 3 times.

* somewhat working fr_command_str_to_argv() function
  which now replaces fr_command_runnable().

As the str_to_argv() function should also be aware of data types
for command syntax (e.g. FR_TYPE_STRING), and parse those correctly.
i.e. with strings that contain spaces.

Sadly, it doesn't seem to quite work yet...

src/main/command.c
src/main/radmin.c

index b2d0820d822bed53f608e19daae7c67234796ec8..dcff00732759a3cf4e39c1277f14f197fc6a7859 100644 (file)
@@ -1095,6 +1095,66 @@ void fr_command_debug(FILE *fp, fr_cmd_t *head)
        fr_command_debug_internal(fp, head, 0);
 }
 
+static char *split(char **input)
+{
+       char *str = *input;
+       char *word;
+
+       /*
+        *      String is empty, we're done.
+        */
+       if (!*str) return NULL;
+
+       /*
+        *      Skip leading whitespace.
+        */
+       while ((*str == ' ') ||
+              (*str == '\t') ||
+              (*str == '\r') ||
+              (*str == '\n'))
+               *(str++) = '\0';
+
+       /*
+        *      String is empty, we're done.
+        */
+       if (!*str) return NULL;
+
+       /*
+        *      String is only comments, we're done.
+        */
+       if (*str == '#') {
+               *str = '\0';
+               return NULL;
+       }
+
+       /*
+        *      Remember the start of the word.
+        */
+       word = str;
+
+       /*
+        *      Skip the next non-space characters.
+        */
+       while (*str &&
+              (*str != ' ') &&
+              (*str != '#') &&
+              (*str != '\t') &&
+              (*str != '\r') &&
+              (*str != '\n'))
+               str++;
+
+       /*
+        *      One of the above characters is after the word.
+        *      Over-write it with NUL.  If *str==0, then we leave it
+        *      alone, so that the next call to split() discovers it,
+        *      and returns NULL.
+        */
+       if (*str) *(str++) = '\0';
+
+       *input = str;
+       return word;
+}
+
 /** Split a string in-place, updating argv[]
  *
  *  This function also respects the various data types (mostly).
@@ -1107,45 +1167,168 @@ void fr_command_debug(FILE *fp, fr_cmd_t *head)
  * @param argv the commands leading up to this string
  * @param max_argc the maximum number of entries in the argv array
  * @param str the string to split
+ * @param[out] runnable whether or not the command is runnable.
  * @return
- *     - <0 on error
+ *     - <0 on error.
  *     - total number of arguments in the argv[] array.  Always >= argc.
  */
-int fr_command_str_to_argv(UNUSED fr_cmd_t *head, int argc, char *argv[], int max_argc, char *str)
+int fr_command_str_to_argv(fr_cmd_t *head, int argc, char *argv[], int max_argc, char *str, bool *runnable)
 {
-       int my_argc = argc;
+       int i, offset;
+       char *p, *word;
+       fr_cmd_t *cmd, *start;
+
+       if ((argc < 0) || (max_argc == 0) || !str) {
+               fr_strerror_printf("Invalid arguments passed to parse routine.");
+               return -1;
+       }
+
+       /*
+        *      Must have something to check.
+        */
+       if (!head) {
+               fr_strerror_printf("No commands to parse.");
+               return -1;
+       }
+
+       start = head;
+       cmd = NULL;
+       p = str;
+       *runnable = false;
+       offset = 0;
+
+       /*
+        *      Either walk down the input argc, or keep splitting
+        *      'str' until we reach a command with a callback
+        *      function.
+        */
+       for (i = 0; i < max_argc; i++) {
+               if (i < argc) {
+                       word = argv[i];
+               } else {
+                       word = split(&p);
+
+                       /*
+                        *      No more strings to parse, we're done.
+                        */
+                       if (!word) {
+                               rad_assert(cmd->func == NULL);
+//                             DEBUG("RETURN %d - %d !runnable", __LINE__, i);
+                               return i;
+                       }
 
-       if ((argc < 0) || (max_argc == 0) || !str) return -1;
+                       /*
+                        *      Save the parsed word.  Note that it
+                        *      MUST be a command name.
+                        */
+                       argv[i] = word;
+               }
 
-       while (*str) {
-               if (my_argc >= max_argc) break;
+               /*
+                *      We have more arguments, but we've run out of
+                *      commands to run.  That's an error.
+                */
+               if (!start) {
+               too_many:
+                       fr_strerror_printf("Input has too many parameters for command.");
+                       return -1;
+               }
 
                /*
-                *      Chop out comments early.
+                *      Search the current list for the given name.
                 */
-               if (*str == '#') {
-                       *str = '\0';
-                       break;
+               cmd = fr_command_find(&start, word, NULL);
+               if (!cmd) {
+                       if (i == 1) {
+                               fr_strerror_printf("No such command '%s'", word);
+                       } else {
+                               fr_strerror_printf("No such command '... %s'", word);
+                       }
+                       return -1;
                }
 
-               while ((*str == ' ') ||
-                      (*str == '\t') ||
-                      (*str == '\r') ||
-                      (*str == '\n'))
-                       *(str++) = '\0';
+               /*
+                *      This node has a child.  Continue parsing with
+                *      that one.
+                */
+               if (cmd->child) {
+                       rad_assert(cmd->func == NULL);
+                       start = cmd->child;
+                       continue;
+               }
+
+               /*
+                *      This node has no children.  It MUST be a leaf
+                *      node, so we stop.
+                *
+                *      If it isn't a leaf node, then someone managed
+                *      to add a node where func==NULL, which means
+                *      that fr_command_add() has failed to do it's
+                *      job.
+                */
+               rad_assert(cmd->func != NULL);
+               offset = i;
+               break;
+       }
+
+       /*
+        *      We've run out of space to store the arguments, return that.
+        */
+       if (i == max_argc) {
+               return max_argc;
+       }
+
+       rad_assert(cmd != NULL);
+       rad_assert(cmd->func != NULL);
 
-               if (!*str) break;
+       /*
+        *      This command doesn't take arguments (that's a good
+        *      attitude!).  See if there is any text left in the
+        *      input string.
+        */
+       if (!cmd->syntax) {
+               word = split(&p);
 
-               argv[my_argc] = str;
-               my_argc++;
+               /*
+                *      Oops... there's text after what should be the
+                *      last argument.  That's bad.
+                */
+               if (word) goto too_many;
 
-               while (*str &&
-                      (*str != ' ') &&
-                      (*str != '\t') &&
-                      (*str != '\r') &&
-                      (*str != '\n'))
-                       str++;
+               *runnable = (cmd->func != NULL);
+//             DEBUG("RETURN %d - %d runnable=%d", __LINE__, i + 1, *runnable);
+               return i + 1;
        }
 
-       return my_argc;
+       /*
+        *      We now keep splitting the input, but this time we
+        *      check it against syntax_types[] and syntax_argc.
+        */
+       for (i = 0; i < cmd->syntax_argc; i++) {
+               if ((offset + i) >= max_argc) return max_argc;
+
+               word = split(&p);
+
+               /*
+                *      If there's no more text, return whatever the
+                *      caller sent us.
+                */
+               if (!word) {
+//                     DEBUG("RETURN %d - %d !runnable", __LINE__, offset + i + 1);
+                       return (offset + i + 1);
+               }
+
+               argv[offset + i] = word;
+       }
+
+       /*
+        *      One last check for "too much" data.
+        */
+       word = split(&p);
+       if (word) goto too_many;
+
+       *runnable = true;
+
+//     DEBUG("RETURN %d - %d runnable", __LINE__, offset + cmd->syntax_argc);
+       return offset + cmd->syntax_argc;
 }
index e20ed4f398c49576b01e9ae31c54986716a95cbe..2587889ee73b3655aaca639c71abe3f35a30623c 100644 (file)
@@ -133,10 +133,12 @@ static int cmd_help(FILE *fp, UNUSED void *ctx, int argc, char const *argv[]);
 static void *fr_radmin(UNUSED void *input_ctx)
 {
        int argc, context;
+       bool runnable;
        char **argv;
        char const **const_argv;
        char *argv_buffer;
-       char *current;
+       char *current_argv;
+       int *context_exit;
        char const *prompt;
        size_t size, room;
        TALLOC_CTX *ctx;
@@ -148,16 +150,17 @@ static void *fr_radmin(UNUSED void *input_ctx)
 
        size = room = 8192;
        argv_buffer = talloc_array(ctx, char, size);
-       current = argv_buffer;
+       current_argv = argv_buffer;
 
        /* -Wincompatible-pointer-types-discards-qualifiers */
-       argv = talloc_zero_array(ctx, char *, 32);
+       argv = talloc_zero_array(ctx, char *, MAX_ARGV);
        memcpy(&const_argv, &argv, sizeof(argv));
 
+       context_exit = talloc_zero_array(ctx, int, MAX_ARGV + 1);
+
        fflush(stdout);
 
        while (true) {
-               int rcode;
                char *line;
 
                line = readline(prompt);
@@ -188,11 +191,11 @@ static void *fr_radmin(UNUSED void *input_ctx)
                         *      Allow exiting from the current context.
                         */
                        if (strcmp(line, "exit") == 0) {
-                               context--;
+                               talloc_const_free(prompt);
+                               context = context_exit[context];
                                if (context == 0) {
                                        prompt = "radmin> ";
                                } else {
-                                       talloc_const_free(prompt);
                                        prompt = talloc_asprintf(ctx, "... %s> ", argv[context - 1]);
                                }
                                goto next;
@@ -200,21 +203,27 @@ static void *fr_radmin(UNUSED void *input_ctx)
                }
 
                /*
-                *      Splitting the line into words mangles it
-                *      in-place.  So we need to copy the line to an
-                *      intermediate buffer.
+                *      "line" is dynamically allocated and we don't
+                *      want argv[] pointing to it.  Also, splitting
+                *      the line mangles it in-place.  So we need to
+                *      copy the line to "current_argv" for splitting.
+                *      We also copy it to "current_line" for adding
+                *      to the history.
                 *
-                *      @todo - add the *full* line to the history,
-                *      not the partial line.
+                *      @todo - we need a smart history which adds the
+                *      FULL line to the history, and then on
+                *      up-arrow, only produces the RELEVANT line from
+                *      the current context.
                 */
-               strlcpy(current, line, room);
-               argc = fr_command_str_to_argv(radmin_cmd, context, argv, MAX_ARGV, current);
+               strlcpy(current_argv, line, room);
+               argc = fr_command_str_to_argv(radmin_cmd, context, argv, MAX_ARGV, current_argv, &runnable);
 
                /*
-                *      @todo - show parse error
+                *      Parse error!  Oops..
                 */
                if (argc < 0) {
-                       fprintf(stderr, "Failed parsing line\n");
+                       fprintf(stderr, "Failed parsing line: %s\n", fr_strerror());
+                       add_history(line); /* let them up-arrow and retype it */
                        continue;
                }
 
@@ -223,26 +232,15 @@ static void *fr_radmin(UNUSED void *input_ctx)
                 */
                if (argc == context) continue;
 
-               /*
-                *      Having split the RHS of the arguments, we now
-                *      pass the whole argument list to see if it's
-                *      runnable.
-                */
-               rcode = fr_command_runnable(radmin_cmd, argc, const_argv);
-               if (rcode < 0) {
-                       fprintf(stderr, "Unknown command: %s\n", fr_strerror());
-                       add_history(line); /* let them up-arrow and retype it */
-                       goto next;
-               }
 
                /*
                 *      It's a partial command.  Add it to the context
                 *      and continue.
                 *
-                *      Note that we have to update `current`, because
+                *      Note that we have to update `current_argv`, because
                 *      argv[context] currently points there...
                 */
-               if (rcode == 0) {
+               if (!runnable) {
                        size_t len;
 
                        len = strlen(argv[argc - 1]) + 1;
@@ -259,13 +257,25 @@ static void *fr_radmin(UNUSED void *input_ctx)
                         *      Move the pointer down the buffer and
                         *      keep reading more.
                         */
-                       current = current + len;
-                       room -= len;
+                       current_argv = current_argv + len + 1;
+                       room -= (len + 1);
 
                        if (context > 0) {
                                talloc_const_free(prompt);
                        }
 
+                       /*
+                        *      Remember how many arguments we
+                        *      added in this context, and go back up
+                        *      that number of arguments when entering
+                        *      'exit'.
+                        *
+                        *      Otherwise, entering a partial command
+                        *      "foo bar baz" would require you to
+                        *      type "exit" 3 times in order to get
+                        *      back to the root.
+                        */
+                       context_exit[argc] = context;
                        context = argc;
                        prompt = talloc_asprintf(ctx, "... %s> ", argv[context - 1]);
                        goto next;
@@ -365,9 +375,9 @@ static fr_cmd_table_t cmd_table[] = {
        },
 
        {
-               .syntax = "foo",
+               .syntax = "foo IPADDR bar INTEGER",
                .func = cmd_test,
-               .help = "test foo.",
+               .help = "test foo IPADDR bar INTEGER",
                .read_only = false,
                .parents = parents,
        },