From: Alan T. DeKok Date: Thu, 28 Jun 2018 21:14:41 +0000 (-0400) Subject: moving forward... many cleanups X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=eb080696a6c9b31fb54dbe34189c6ef849b1d565;p=thirdparty%2Ffreeradius-server.git moving forward... many cleanups * 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... --- diff --git a/src/main/command.c b/src/main/command.c index b2d0820d822..dcff0073275 100644 --- a/src/main/command.c +++ b/src/main/command.c @@ -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; } diff --git a/src/main/radmin.c b/src/main/radmin.c index e20ed4f398c..2587889ee73 100644 --- a/src/main/radmin.c +++ b/src/main/radmin.c @@ -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, },