From 6d9413c09b5198223fe62403bd95599fe6ef1fa6 Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Mon, 27 Apr 2026 09:08:42 +0200 Subject: [PATCH] feat(console): Add max_cmdline_args to esp_console_repl_config_t --- components/console/esp_console.h | 4 +- components/console/esp_console_common.c | 8 ++- components/console/esp_console_repl_chip.c | 8 ++- components/console/esp_console_repl_linux.c | 4 +- .../console/private_include/console_private.h | 3 +- .../test_apps/console/main/test_app_main.c | 2 +- .../test_apps/console/main/test_console.c | 65 ++++++++++++++++++- 7 files changed, 84 insertions(+), 10 deletions(-) diff --git a/components/console/esp_console.h b/components/console/esp_console.h index 236d6949dc3..74c70736942 100644 --- a/components/console/esp_console.h +++ b/components/console/esp_console.h @@ -54,6 +54,7 @@ typedef struct { BaseType_t task_core_id; //!< repl task affinity, i.e. which core the task is pinned to const char *prompt; //!< prompt (NULL represents default: "esp> ") size_t max_cmdline_length; //!< maximum length of a command line. If 0, default value will be used + size_t max_cmdline_args; //!< maximum number of command line arguments to parse. If 0, default value will be used } esp_console_repl_config_t; /** @@ -69,6 +70,7 @@ typedef struct { .task_core_id = tskNO_AFFINITY, \ .prompt = NULL, \ .max_cmdline_length = 0, \ + .max_cmdline_args = 0, \ } #if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM @@ -78,7 +80,7 @@ typedef struct { */ typedef struct { int channel; //!< UART channel number (count from zero) - int baud_rate; //!< Comunication baud rate + int baud_rate; //!< Communication baud rate int tx_gpio_num; //!< GPIO number for TX path, -1 means using default one int rx_gpio_num; //!< GPIO number for RX path, -1 means using default one } esp_console_dev_uart_config_t; diff --git a/components/console/esp_console_common.c b/components/console/esp_console_common.c index 416976dccbb..63553f6b7be 100644 --- a/components/console/esp_console_common.c +++ b/components/console/esp_console_common.c @@ -59,17 +59,23 @@ _exit: return ret; } -esp_err_t esp_console_common_init(size_t max_cmdline_length, esp_console_repl_com_t *repl_com) +esp_err_t esp_console_common_init(size_t max_cmdline_length, size_t max_cmdline_args, esp_console_repl_com_t *repl_com) { esp_err_t ret = ESP_OK; /* Initialize the console */ esp_console_config_t console_config = ESP_CONSOLE_CONFIG_DEFAULT(); repl_com->max_cmdline_length = console_config.max_cmdline_length; + repl_com->max_cmdline_args = console_config.max_cmdline_args; /* Replace the default command line length if passed as a parameter */ if (max_cmdline_length != 0) { console_config.max_cmdline_length = max_cmdline_length; repl_com->max_cmdline_length = max_cmdline_length; } + /* Replace the default command line args if passed as a parameter */ + if (max_cmdline_args != 0) { + console_config.max_cmdline_args = max_cmdline_args; + repl_com->max_cmdline_args = max_cmdline_args; + } #if CONFIG_LOG_COLORS console_config.hint_color = atoi(LOG_COLOR_CYAN); diff --git a/components/console/esp_console_repl_chip.c b/components/console/esp_console_repl_chip.c index 2bc79bbf543..879c72ff013 100644 --- a/components/console/esp_console_repl_chip.c +++ b/components/console/esp_console_repl_chip.c @@ -62,7 +62,7 @@ esp_err_t esp_console_new_repl_usb_cdc(const esp_console_dev_usb_cdc_config_t *d fcntl(fileno(stdin), F_SETFL, 0); // initialize console, common part - ret = esp_console_common_init(repl_config->max_cmdline_length, &cdc_repl->repl_com); + ret = esp_console_common_init(repl_config->max_cmdline_length, repl_config->max_cmdline_args, &cdc_repl->repl_com); if (ret != ESP_OK) { goto _exit; } @@ -136,7 +136,7 @@ esp_err_t esp_console_new_repl_usb_serial_jtag(const esp_console_dev_usb_serial_ } // initialize console, common part - ret = esp_console_common_init(repl_config->max_cmdline_length, &usb_serial_jtag_repl->repl_com); + ret = esp_console_common_init(repl_config->max_cmdline_length, repl_config->max_cmdline_args, &usb_serial_jtag_repl->repl_com); if (ret != ESP_OK) { goto _exit; } @@ -240,7 +240,9 @@ esp_err_t esp_console_new_repl_uart(const esp_console_dev_uart_config_t *dev_con uart_vfs_dev_use_driver(dev_config->channel); // initialize console, common part - ret = esp_console_common_init(repl_config->max_cmdline_length, &uart_repl->repl_com); + ret = esp_console_common_init(repl_config->max_cmdline_length, + repl_config->max_cmdline_args, + &uart_repl->repl_com); if (ret != ESP_OK) { goto _exit; } diff --git a/components/console/esp_console_repl_linux.c b/components/console/esp_console_repl_linux.c index a01d2f8134f..f3130ba5648 100644 --- a/components/console/esp_console_repl_linux.c +++ b/components/console/esp_console_repl_linux.c @@ -51,7 +51,7 @@ static void prepare_input_stream(void) assert(tcgetattr(stdin_fileno, &s_orig_termios) == 0); struct termios raw = s_orig_termios; raw.c_iflag |= ICRNL; // we translate to NL because linenoise expects NL - raw.c_lflag &= ~(ECHO | ICANON); // turn off echo and cononical mode + raw.c_lflag &= ~(ECHO | ICANON); // turn off echo and canonical mode assert(tcsetattr(stdin_fileno, TCSAFLUSH, &raw) == 0); // Make sure user does not end up with a broken terminal @@ -101,7 +101,7 @@ static esp_err_t esp_console_new_repl_linux(const esp_console_repl_config_t *rep fcntl(fileno(stdin), F_SETFL, 0); // initialize console , common part - ret = esp_console_common_init(repl_config->max_cmdline_length, &linux_repl->repl_com); + ret = esp_console_common_init(repl_config->max_cmdline_length, repl_config->max_cmdline_args, &linux_repl->repl_com); if (ret != ESP_OK) { goto _exit; } diff --git a/components/console/private_include/console_private.h b/components/console/private_include/console_private.h index 6f9f8361afc..b1fe9f0933b 100644 --- a/components/console/private_include/console_private.h +++ b/components/console/private_include/console_private.h @@ -35,6 +35,7 @@ typedef struct { const char *history_save_path; TaskHandle_t task_hdl; // REPL task handle size_t max_cmdline_length; // Maximum length of a command line. If 0, default value will be used. + size_t max_cmdline_args; // Maximum number of command line arguments to parse. If 0, default value will be used. } esp_console_repl_com_t; typedef struct { @@ -44,7 +45,7 @@ typedef struct { void esp_console_repl_task(void *args); -esp_err_t esp_console_common_init(size_t max_cmdline_length, esp_console_repl_com_t *repl_com); +esp_err_t esp_console_common_init(size_t max_cmdline_length, size_t max_cmdline_args, esp_console_repl_com_t *repl_com); esp_err_t esp_console_setup_prompt(const char *prompt, esp_console_repl_com_t *repl_com); esp_err_t esp_console_setup_history(const char *history_path, uint32_t max_history_len, diff --git a/components/console/test_apps/console/main/test_app_main.c b/components/console/test_apps/console/main/test_app_main.c index 258953bd479..59d770a655d 100644 --- a/components/console/test_apps/console/main/test_app_main.c +++ b/components/console/test_apps/console/main/test_app_main.c @@ -10,7 +10,7 @@ #include // Some resources are lazy allocated (newlib locks) in the console code, the threshold is left for that case -#define TEST_MEMORY_LEAK_THRESHOLD (150) +#define TEST_MEMORY_LEAK_THRESHOLD (250) void setUp(void) { diff --git a/components/console/test_apps/console/main/test_console.c b/components/console/test_apps/console/main/test_console.c index 0b6a7d1b583..e70d5d45b29 100644 --- a/components/console/test_apps/console/main/test_console.c +++ b/components/console/test_apps/console/main/test_console.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -31,6 +31,26 @@ typedef struct { static esp_console_repl_t *s_repl = NULL; +/* Shared state for the repl config limits test */ +static int s_cmd_captured_argc; +static char s_cmd_captured_last_arg[32]; + +static int do_capture_args_cmd(int argc, char **argv) +{ + s_cmd_captured_argc = argc; + if (argc > 1) { + strncpy(s_cmd_captured_last_arg, argv[argc - 1], sizeof(s_cmd_captured_last_arg) - 1); + s_cmd_captured_last_arg[sizeof(s_cmd_captured_last_arg) - 1] = '\0'; + } + return 0; +} + +static const esp_console_cmd_t s_capture_args_cmd = { + .command = "capture", + .help = "Capture argc and last arg for testing", + .func = do_capture_args_cmd, +}; + static int do_hello_cmd_with_context(void *context, int argc, char **argv) { cmd_context_t *cmd_context = (cmd_context_t *)context; @@ -273,3 +293,46 @@ TEST_CASE("esp console test with context", "[console]") TEST_ESP_OK(esp_console_deinit()); } + +TEST_CASE("esp console repl config respects max_cmdline_args and max_cmdline_length", "[console][ignore]") +{ + /* + * Use small limits to make truncation observable: + * - max_cmdline_args = 4: split_argv stops when argc reaches argv_size-1 = 3, + * so "capture arg1 arg2 arg3" drops "arg3" and the command receives argc == 3. + * - max_cmdline_length = 20: strlcpy copies at most 19 chars, so the 20-char + * input "capture longarg12345" is truncated to "capture longarg1234" and the + * last argument received by the command is "longarg1234". + */ + esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); + repl_config.max_cmdline_args = 4; + repl_config.max_cmdline_length = 20; + esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); + TEST_ASSERT_EQUAL(ESP_OK, esp_console_new_repl_uart(&uart_config, &repl_config, &s_repl)); + + TEST_ESP_OK(esp_console_cmd_register(&s_capture_args_cmd)); + + /* --- Verify max_cmdline_args --- + * Input has 4 tokens (command + 3 args). The limit allows at most + * argv_size-1 = 3 tokens, so the last token ("arg3") is dropped. + */ + s_cmd_captured_argc = -1; + int ret; + TEST_ESP_OK(esp_console_run("capture arg1 arg2 arg3", &ret)); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(3, s_cmd_captured_argc); + + /* --- Verify max_cmdline_length --- + * "capture longarg12345" is exactly 20 chars. strlcpy(buf, input, 20) + * retains at most 19 chars, yielding "capture longarg1234". + * The command therefore receives "longarg1234" as its only argument. + */ + s_cmd_captured_argc = -1; + memset(s_cmd_captured_last_arg, 0, sizeof(s_cmd_captured_last_arg)); + TEST_ESP_OK(esp_console_run("capture longarg12345", &ret)); + TEST_ASSERT_EQUAL(0, ret); + TEST_ASSERT_EQUAL(2, s_cmd_captured_argc); + TEST_ASSERT_EQUAL_STRING("longarg1234", s_cmd_captured_last_arg); + + TEST_ESP_OK(esp_console_deinit()); +}