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).
* @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;
}
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;
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);
* 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;
}
/*
- * 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;
}
*/
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;
* 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;
},
{
- .syntax = "foo",
+ .syntax = "foo IPADDR bar INTEGER",
.func = cmd_test,
- .help = "test foo.",
+ .help = "test foo IPADDR bar INTEGER",
.read_only = false,
.parents = parents,
},