/* * (C) Copyright 2014, 2016 Leo C. * * (C) Copyright 2000-2009 * Wolfgang Denk, DENX Software Engineering, wd@denx.de. * * SPDX-License-Identifier: GPL-2.0 */ /* * Command Processor Table */ #include "command.h" #include "common.h" #include #include #include #include #include "config.h" #include "print-utils.h" #include "con-utils.h" #include "env.h" #include "debug.h" jmp_buf cmd_jbuf; static void print_usage_line(const FLASH char *name, int width, const FLASH char *usage) { width -= strlen_P(name); if (width < 0) width = 0; my_puts_P(name); print_blanks(width); my_puts_P(PSTR(" - ")); my_puts_P(usage); my_puts_P(PSTR("\n")); } int strcmp_PP(const FLASH char *s1, const FLASH char *s2) { unsigned char c1, c2; while ((c1 = *(const FLASH unsigned char *)s1++) == (c2 = *(const FLASH unsigned char *)s2++)) if (c1 == 0) return 0; return c1 - c2; } int cmpstring_PP(const void *p1, const void *p2) { return strcmp_PP((*(const FLASH cmd_tbl_t **) p1)->name, (*(const FLASH cmd_tbl_t **) p2)->name); } int cmd_tbl_item_count(void) { cmd_tbl_t * p = cmd_tbl; int count = 0; while (p->name != NULL) { p++; count++; } return count; } /* * Use puts() instead of printf() to avoid printf buffer overflow * for long help messages */ command_ret_t _do_help(cmd_tbl_t *cmd_start, int cmd_items, cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[]) { uint_fast8_t i, max_len = 0; command_ret_t rcode = CMD_RET_SUCCESS; (void) flag; char *optenv = getenv_str(PSTR("cmd")); bool opt_debug = optenv && strstr_P(optenv, PSTR("debug")) != NULL; if (argc == 1) { /*show list of commands */ cmd_tbl_t *cmd_array[cmd_items]; int i; /* Make array of commands from .uboot_cmd section */ cmdtp = cmd_start; for (i = 0; i < cmd_items; i++) { cmd_array[i] = cmdtp++; uint_fast8_t l = strlen_P(cmd_array[i]->name); if (l > max_len) max_len = l; } /* Sort command list */ qsort(cmd_array, cmd_items, sizeof (cmd_tbl_t *), cmpstring_PP); /* print short help (usage) */ for (i = 0; i < cmd_items; i++) { if (opt_debug || cmd_array[i]->name[0] != '!') { const FLASH char *usage = cmd_array[i]->usage; /* allow user abort */ if (ctrlc ()) return CMD_RET_FAILURE; if (usage == NULL) continue; #ifdef GCC_BUG_61443 print_usage_line(cmd_array[i]->name, max_len, usage); #else printf_P(PSTR("%-" stringify(8) /*FIXME*/ "S - %S\n"), cmd_array[i]->name, usage); #endif } } return CMD_RET_SUCCESS; } /* * command help (long version) */ for (i = 1; i < argc; ++i) { if ((cmdtp = find_cmd_tbl (argv[i], cmd_start, cmd_items )) != NULL && (opt_debug || cmdtp->name[0] != '!')) { cmd_usage(cmdtp); } else { printf_P(PSTR("Unknown command '%s' - try 'help'" " without arguments.\n\n"), argv[i] ); rcode = CMD_RET_FAILURE; } } return rcode; } /*************************************************************************** * find command table entry for a command */ cmd_tbl_t *find_cmd_tbl (const char *cmd, cmd_tbl_t *table, int table_len) { cmd_tbl_t *cmdtp; cmd_tbl_t *cmdtp_temp = table; /*Init value */ size_t len; uint_fast8_t n_found = 0; char *optenv = getenv_str(PSTR("cmd")); bool opt_debug = optenv && strstr_P(optenv, PSTR("debug")) != NULL; if (!cmd) return NULL; len = strlen(cmd); for (cmdtp = table; cmdtp != table + table_len; cmdtp++) { if (strncmp_P(cmd, cmdtp->name, len) == 0 && (opt_debug || cmdtp->name[0] != '!')) { if (len == strlen_P(cmdtp->name)) return cmdtp; /* full match */ cmdtp_temp = cmdtp; /* abbreviated command ? */ n_found++; } } if (n_found == 1) /* exactly one match */ return cmdtp_temp; return NULL; /* not found or ambiguous command */ } cmd_tbl_t *find_cmd (const char *cmd) { return find_cmd_tbl(cmd, cmd_tbl, cmd_tbl_item_count()); } command_ret_t cmd_usage(const FLASH cmd_tbl_t *cmdtp) { // printf("%s - %s\n\n", cmdtp->name, cmdtp->usage); print_usage_line(cmdtp->name, 0, cmdtp->usage); #if 0 my_puts_P(cmdtp->name); print_blanks(/*FIXME*/ 8 - strlen_P(cmdtp->name)); my_puts_P(PSTR(" - ")); my_puts_P(cmdtp->usage); my_puts_P(PSTR("\n\n")); #endif #ifdef CONFIG_SYS_LONGHELP // printf("Usage:\n%s ", cmdtp->name); my_puts_P(PSTR("Usage:\n")); my_puts_P(cmdtp->name); my_puts_P(PSTR(" ")); if (!cmdtp->help) { my_puts_P(PSTR(" - No additional help available.\n")); return CMD_RET_FAILURE; } my_puts_P(cmdtp->help); my_puts_P(PSTR("\n")); #endif /* CONFIG_SYS_LONGHELP */ return CMD_RET_FAILURE; } #ifdef CONFIG_AUTO_COMPLETE int var_complete(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]) { static char tmp_buf[CONFIG_SYS_CBSIZE]; int space; space = last_char == '\0' || isblank(last_char); if (space && argc == 1) return env_complete("", maxv, cmdv, sizeof(tmp_buf), tmp_buf); if (!space && argc == 2) return env_complete(argv[1], maxv, cmdv, sizeof(tmp_buf), tmp_buf); return 0; } /*************************************************************************************/ /* TODO: cmdtp points to FLASH */ static int complete_cmdv(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]) { cmd_tbl_t *cmdtp = cmd_tbl; // const int count = ARRAY_SIZE(cmd_tbl); // const cmd_tbl_t *cmdend = cmdtp + count; // const char *p; int len, clen; int n_found = 0; const char *cmd; /* sanity? */ if (maxv < 2) return -2; cmdv[0] = NULL; if (argc == 0) { /* output full list of commands */ for (; cmdtp->name[0] != '\0'; cmdtp++) { if (n_found >= maxv - 2) { cmdv[n_found++] = "..."; break; } cmdv[n_found++] = cmdtp->name; } cmdv[n_found] = NULL; return n_found; } /* more than one arg or one but the start of the next */ if (argc > 1 || (last_char == '\0' || isblank(last_char))) { cmdtp = find_cmd(argv[0]); if (cmdtp == NULL || cmdtp->complete == NULL) { cmdv[0] = NULL; return 0; } return (*cmdtp->complete)(argc, argv, last_char, maxv, cmdv); } cmd = argv[0]; len = strlen(cmd); /* return the partial matches */ for (; cmdtp->name[0] != '\0'; cmdtp++) { clen = strlen(cmdtp->name); if (clen < len) continue; if (memcmp(cmd, cmdtp->name, len) != 0) continue; /* too many! */ if (n_found >= maxv - 2) { cmdv[n_found++] = "..."; break; } cmdv[n_found++] = cmdtp->name; } cmdv[n_found] = NULL; return n_found; } static int make_argv(char *s, int argvsz, char *argv[]) { int argc = 0; /* split into argv */ while (argc < argvsz - 1) { /* skip any white space */ while (isblank(*s)) ++s; if (*s == '\0') /* end of s, no more args */ break; argv[argc++] = s; /* begin of argument string */ /* find end of string */ while (*s && !isblank(*s)) ++s; if (*s == '\0') /* end of s, no more args */ break; *s++ = '\0'; /* terminate current arg */ } argv[argc] = NULL; return argc; } static void print_argv(const char *banner, const char *leader, const char *sep, int linemax, char * const argv[]) { int ll = leader != NULL ? strlen(leader) : 0; int sl = sep != NULL ? strlen(sep) : 0; int len, i; if (banner) { my_puts_P(PSTR("\n")); my_puts(banner); } i = linemax; /* force leader and newline */ while (*argv != NULL) { len = strlen(*argv) + sl; if (i + len >= linemax) { my_puts_P(PSTR("\n")); if (leader) my_puts(leader); i = ll - sl; } else if (sep) my_puts(sep); my_puts(*argv++); i += len; } my_puts_P(PSTR("\n")); } static int find_common_prefix(char * const argv[]) { int i, len; char *anchor, *s, *t; if (*argv == NULL) return 0; /* begin with max */ anchor = *argv++; len = strlen(anchor); while ((t = *argv++) != NULL) { s = anchor; for (i = 0; i < len; i++, t++, s++) { if (*t != *s) break; } len = s - anchor; } return len; } static char tmp_buf[CONFIG_SYS_CBSIZE]; /* copy of console I/O buffer */ int cmd_auto_complete(const FLASH char *const prompt, char *buf, int *np, int *colp) { int n = *np, col = *colp; char *argv[CONFIG_SYS_MAXARGS + 1]; /* NULL terminated */ char *cmdv[20]; char *s, *t; const char *sep; int i, j, k, len, seplen, argc; int cnt; char last_char; if (strcmp_PP(prompt, CONFIG_SYS_PROMPT) != 0) return 0; /* not in normal console */ cnt = strlen(buf); if (cnt >= 1) last_char = buf[cnt - 1]; else last_char = '\0'; /* copy to secondary buffer which will be affected */ strcpy(tmp_buf, buf); /* separate into argv */ argc = make_argv(tmp_buf, sizeof(argv)/sizeof(argv[0]), argv); /* do the completion and return the possible completions */ i = complete_cmdv(argc, argv, last_char, sizeof(cmdv)/sizeof(cmdv[0]), cmdv); /* no match; bell and out */ if (i == 0) { if (argc > 1) /* allow tab for non command */ return 0; putchar('\a'); return 1; } s = NULL; len = 0; sep = NULL; seplen = 0; if (i == 1) { /* one match; perfect */ k = strlen(argv[argc - 1]); s = cmdv[0] + k; len = strlen(s); sep = " "; seplen = 1; } else if (i > 1 && (j = find_common_prefix(cmdv)) != 0) { /* more */ k = strlen(argv[argc - 1]); j -= k; if (j > 0) { s = cmdv[0] + k; len = j; } } if (s != NULL) { k = len + seplen; /* make sure it fits */ if (n + k >= CONFIG_SYS_CBSIZE - 2) { putchar('\a'); return 1; } t = buf + cnt; for (i = 0; i < len; i++) *t++ = *s++; if (sep != NULL) for (i = 0; i < seplen; i++) *t++ = sep[i]; *t = '\0'; n += k; col += k; my_puts(t - k); if (sep == NULL) putchar('\a'); *np = n; *colp = col; } else { print_argv(NULL, " ", " ", 78, cmdv); my_puts_P(prompt); my_puts(buf); } return 1; } #endif /* CONFIG_AUTO_COMPLETE */ /** * Call a command function. This should be the only route in U-Boot to call * a command, so that we can track whether we are waiting for input or * executing a command. * * @param cmdtp Pointer to the command to execute * @param flag Some flags normally 0 (see CMD_FLAG_.. above) * @param argc Number of arguments (arg 0 must be the command text) * @param argv Arguments * @return 0 if command succeeded, else non-zero (CMD_RET_...) */ command_ret_t cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { command_ret_t result; result = (cmdtp->cmd)(cmdtp, flag, argc, argv); // if (result != CMD_RET_SUCCESS) // debug("Command failed, result=%d\n", result); return result; } command_ret_t cmd_process(int flag, int argc, char * const argv[], uint_fast8_t *repeatable) { command_ret_t rc = CMD_RET_SUCCESS; cmd_tbl_t *cmdtp; /* Look up command in command table */ cmdtp = find_cmd(argv[0]); if (cmdtp == NULL) { printf_P(PSTR("Unknown command '%s' - try 'help'\n"), argv[0]); return CMD_RET_FAILURE; } if (!cmdtp->cmd) { debug("### Command '%s' found, but ->cmd == NULL \n", argv[0]); return CMD_RET_FAILURE; } /* found - check max args */ if (argc > cmdtp->maxargs) rc = CMD_RET_USAGE; #if defined(CONFIG_CMD_BOOTD) /* avoid "bootd" recursion */ else if (cmdtp->cmd == do_bootd) { if (flag & CMD_FLAG_BOOTD) { my_puts_P(PSTR("'bootd' recursion detected\n")); rc = CMD_RET_FAILURE; } else { flag |= CMD_FLAG_BOOTD; } } #endif if (setjmp(cmd_jbuf) != 0) return CMD_RET_FAILURE; /* If OK so far, then do the command */ if (!rc) { rc = cmd_call(cmdtp, flag, argc, argv); *repeatable &= cmdtp->repeatable; } if (rc == CMD_RET_USAGE) rc = cmd_usage(cmdtp); return rc; } int cmd_process_error(cmd_tbl_t *cmdtp, int err) { char buf[strlen_P(cmdtp->name) + 1]; strcpy_P(buf, cmdtp->name); if (err) { printf_P(PSTR("Command '%s' failed: Error %d\n"), buf, err); return 1; } return 0; }