* [PATCH] app/testpmd: allow multiple cmdline-file parameters
@ 2025-07-04 14:05 Bruce Richardson
2025-07-04 18:34 ` [PATCH v2 0/3] improve cmdline file handling in testpmd Bruce Richardson
` (2 more replies)
0 siblings, 3 replies; 16+ messages in thread
From: Bruce Richardson @ 2025-07-04 14:05 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson
While testpmd allows a set of pre-prepared commands to be passed into it
at startup via the "cmdline-file" (and cmdline-file-noecho) parameters,
this is currently limited to a single file. By extending this support
to allow the parameter to be passed multiple (up to 16) times, we enable
users to have a library of pre-canned cmdline files and pass multiple of
these in to the same run, e.g. have one cmdline file per NIC feature and
enable multiple features by passing in multiple filenames.
Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
app/test-pmd/cmdline.c | 11 ++++++++---
app/test-pmd/parameters.c | 15 +++++++++++----
app/test-pmd/testpmd.c | 12 +++++++++---
app/test-pmd/testpmd.h | 8 ++++++--
4 files changed, 34 insertions(+), 12 deletions(-)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index 7b4e27eddf..ad4094fa29 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -11646,7 +11646,9 @@ cmd_load_from_file_parsed(
{
struct cmd_cmdfile_result *res = parsed_result;
- cmdline_read_from_file(res->filename);
+ if (cmdline_read_from_file(res->filename) != 0) {
+ fprintf(stderr, "Failed to load commands from file: %s\n", res->filename);
+ }
}
static cmdline_parse_inst_t cmd_load_from_file = {
@@ -14151,11 +14153,12 @@ init_cmdline(void)
}
/* read cmdline commands from file */
-void
+int
cmdline_read_from_file(const char *filename)
{
struct cmdline *cl;
int fd = -1;
+ int ret = 0;
/* cmdline_file_new does not produce any output
* so when echoing is requested we open filename directly
@@ -14168,7 +14171,7 @@ cmdline_read_from_file(const char *filename)
if (fd < 0) {
fprintf(stderr, "Failed to open file %s: %s\n",
filename, strerror(errno));
- return;
+ return -1;
}
cl = cmdline_new(main_ctx, "testpmd> ", fd, STDOUT_FILENO);
@@ -14177,6 +14180,7 @@ cmdline_read_from_file(const char *filename)
fprintf(stderr,
"Failed to create file based cmdline context: %s\n",
filename);
+ ret = -1;
goto end;
}
@@ -14190,6 +14194,7 @@ cmdline_read_from_file(const char *filename)
end:
if (fd >= 0)
close(fd);
+ return ret;
}
void
diff --git a/app/test-pmd/parameters.c b/app/test-pmd/parameters.c
index 1132972913..64c16b4f36 100644
--- a/app/test-pmd/parameters.c
+++ b/app/test-pmd/parameters.c
@@ -963,10 +963,17 @@ launch_args_parse(int argc, char** argv)
echo_cmdline_file = true;
/* fall-through */
case TESTPMD_OPT_CMDLINE_FILE_NOECHO_NUM:
- printf("CLI commands to be read from %s\n",
- optarg);
- strlcpy(cmdline_filename, optarg,
- sizeof(cmdline_filename));
+ if (cmdline_file_count >= RTE_DIM(cmdline_filenames)) {
+ fprintf(stderr, "Too many cmdline files specified (maximum %zu)\n",
+ RTE_DIM(cmdline_filenames));
+ exit(EXIT_FAILURE);
+ }
+ printf("CLI commands to be read from %s\n", optarg);
+ strlcpy(cmdline_filenames[cmdline_file_count], optarg,
+ sizeof(cmdline_filenames[cmdline_file_count]));
+ cmdline_file_count++;
+ /* reset flag for next time */
+ echo_cmdline_file = false;
break;
case TESTPMD_OPT_TX_FIRST_NUM:
printf("Ports to start sending a burst of "
diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
index bb88555328..3f82c1018d 100644
--- a/app/test-pmd/testpmd.c
+++ b/app/test-pmd/testpmd.c
@@ -105,7 +105,8 @@ int testpmd_logtype; /**< Log type for testpmd logs */
uint8_t interactive = 0;
uint8_t auto_start = 0;
uint8_t tx_first;
-char cmdline_filename[PATH_MAX] = {0};
+char cmdline_filenames[MAX_CMDLINE_FILENAMES][PATH_MAX] = {0};
+unsigned int cmdline_file_count;
bool echo_cmdline_file;
/*
@@ -4508,8 +4509,13 @@ main(int argc, char** argv)
rte_exit(EXIT_FAILURE,
"Could not initialise cmdline context.\n");
- if (strlen(cmdline_filename) != 0)
- cmdline_read_from_file(cmdline_filename);
+ for (unsigned int i = 0; i < cmdline_file_count; i++) {
+ if (cmdline_read_from_file(cmdline_filenames[i]) != 0) {
+ fprintf(stderr, "Failed to process cmdline file: %s\n",
+ cmdline_filenames[i]);
+ break;
+ }
+ }
if (interactive == 1) {
if (auto_start) {
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index e629edaa02..9525a04626 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -36,6 +36,9 @@
extern uint8_t cl_quit;
extern volatile uint8_t f_quit;
+/* Max number of cmdline files we can take on testpmd cmdline */
+#define MAX_CMDLINE_FILENAMES 16
+
/*
* It is used to allocate the memory for hash key.
* The hash key size is NIC dependent.
@@ -509,7 +512,8 @@ extern int testpmd_logtype; /**< Log type for testpmd logs */
extern uint8_t interactive;
extern uint8_t auto_start;
extern uint8_t tx_first;
-extern char cmdline_filename[PATH_MAX]; /**< offline commands file */
+extern char cmdline_filenames[MAX_CMDLINE_FILENAMES][PATH_MAX]; /**< offline commands files */
+extern unsigned int cmdline_file_count; /**< number of cmdline files */
extern bool echo_cmdline_file; /** unset if cmdline-file-noecho is used */
extern uint8_t numa_support; /**< set by "--numa" parameter */
extern uint16_t port_topology; /**< set by "--port-topology" parameter */
@@ -928,7 +932,7 @@ unsigned int parse_hdrs_list(const char *str, const char *item_name,
unsigned int *parsed_items);
void launch_args_parse(int argc, char** argv);
void cmd_reconfig_device_queue(portid_t id, uint8_t dev, uint8_t queue);
-void cmdline_read_from_file(const char *filename);
+int cmdline_read_from_file(const char *filename);
int init_cmdline(void);
void prompt(void);
void prompt_exit(void);
--
2.48.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 0/3] improve cmdline file handling in testpmd
2025-07-04 14:05 [PATCH] app/testpmd: allow multiple cmdline-file parameters Bruce Richardson
@ 2025-07-04 18:34 ` Bruce Richardson
2025-07-04 18:34 ` [PATCH v2 1/3] app/testpmd: explicitly set command echoing on file load Bruce Richardson
` (2 more replies)
2025-07-07 11:17 ` [PATCH v3 0/3] improve cmdline file handling in testpmd Bruce Richardson
2025-07-31 16:00 ` [PATCH v4 0/4] improve cmdline file handling in testpmd Bruce Richardson
2 siblings, 3 replies; 16+ messages in thread
From: Bruce Richardson @ 2025-07-04 18:34 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson
Some small improvements to cmdline file handling testpmd, inspired by the
desire to have support for multiple cmdline files passed on the commandline
of a testpmd run.
The implementation is somewhat complicated by the setting for echo/noecho
of the commands, because the current implementation uses a global flag for
that - shared between cmdline parameters and interactive CLI commands.
V2:
* remove global echo flag, and now support echo/noecho per file loaded
* when echoing, output the file being processed, to clarify things when
loading multiple files.
Bruce Richardson (3):
app/testpmd: explicitly set command echoing on file load
app/testpmd: allow multiple commandline file parameters
app/testpmd: improve output when processing cmdline files
app/test-pmd/cmdline.c | 78 ++++++++++++++++++---
app/test-pmd/parameters.c | 17 +++--
app/test-pmd/testpmd.c | 13 ++--
app/test-pmd/testpmd.h | 15 +++-
doc/guides/testpmd_app_ug/run_app.rst | 3 +-
doc/guides/testpmd_app_ug/testpmd_funcs.rst | 12 ++--
6 files changed, 110 insertions(+), 28 deletions(-)
--
2.48.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 1/3] app/testpmd: explicitly set command echoing on file load
2025-07-04 18:34 ` [PATCH v2 0/3] improve cmdline file handling in testpmd Bruce Richardson
@ 2025-07-04 18:34 ` Bruce Richardson
2025-07-04 18:34 ` [PATCH v2 2/3] app/testpmd: allow multiple commandline file parameters Bruce Richardson
2025-07-04 18:34 ` [PATCH v2 3/3] app/testpmd: improve output when processing cmdline files Bruce Richardson
2 siblings, 0 replies; 16+ messages in thread
From: Bruce Richardson @ 2025-07-04 18:34 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson
The echoing of commands contained in a file loaded via the "load"
command on testpmd CLI is governed by whether or not a cmdline-file or
cmdline-file-noecho had been passed to testpmd on the commandline.
Remove the use of a global setting for this, and explicitly add a
"load_echo" command to match the "load" command.
Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
app/test-pmd/cmdline.c | 51 +++++++++++++++++++--
app/test-pmd/testpmd.c | 2 +-
app/test-pmd/testpmd.h | 2 +-
doc/guides/testpmd_app_ug/testpmd_funcs.rst | 2 +-
4 files changed, 49 insertions(+), 8 deletions(-)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index 7b4e27eddf..5433678b5e 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -11646,7 +11646,9 @@ cmd_load_from_file_parsed(
{
struct cmd_cmdfile_result *res = parsed_result;
- cmdline_read_from_file(res->filename);
+ if (cmdline_read_from_file(res->filename, false) != 0) {
+ fprintf(stderr, "Failed to load commands from file: %s\n", res->filename);
+ }
}
static cmdline_parse_inst_t cmd_load_from_file = {
@@ -11660,6 +11662,41 @@ static cmdline_parse_inst_t cmd_load_from_file = {
},
};
+/* command to load a file with echoing commands */
+struct cmd_load_echo_result {
+ cmdline_fixed_string_t load_echo;
+ cmdline_fixed_string_t filename;
+};
+
+/* CLI fields for file load with echo command */
+static cmdline_parse_token_string_t cmd_load_echo =
+ TOKEN_STRING_INITIALIZER(struct cmd_load_echo_result, load_echo, "load_echo");
+static cmdline_parse_token_string_t cmd_load_echo_filename =
+ TOKEN_STRING_INITIALIZER(struct cmd_load_echo_result, filename, NULL);
+
+static void
+cmd_load_echo_file_parsed(
+ void *parsed_result,
+ __rte_unused struct cmdline *cl,
+ __rte_unused void *data)
+{
+ struct cmd_load_echo_result *res = parsed_result;
+
+ if (cmdline_read_from_file(res->filename, true) != 0)
+ fprintf(stderr, "Failed to load commands from file: %s\n", res->filename);
+}
+
+static cmdline_parse_inst_t cmd_load_echo_file = {
+ .f = cmd_load_echo_file_parsed,
+ .data = NULL,
+ .help_str = "load_echo <filename>",
+ .tokens = {
+ (void *)&cmd_load_echo,
+ (void *)&cmd_load_echo_filename,
+ NULL,
+ },
+};
+
/* Get Rx offloads capabilities */
struct cmd_rx_offload_get_capa_result {
cmdline_fixed_string_t show;
@@ -13865,6 +13902,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
&cmd_help_long,
&cmd_quit,
&cmd_load_from_file,
+ &cmd_load_echo_file,
&cmd_showport,
&cmd_showqueue,
&cmd_showeeprom,
@@ -14151,24 +14189,25 @@ init_cmdline(void)
}
/* read cmdline commands from file */
-void
-cmdline_read_from_file(const char *filename)
+int
+cmdline_read_from_file(const char *filename, bool echo)
{
struct cmdline *cl;
int fd = -1;
+ int ret = 0;
/* cmdline_file_new does not produce any output
* so when echoing is requested we open filename directly
* and then pass that to cmdline_new with stdout as the output path.
*/
- if (!echo_cmdline_file) {
+ if (!echo) {
cl = cmdline_file_new(main_ctx, "testpmd> ", filename);
} else {
fd = open(filename, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Failed to open file %s: %s\n",
filename, strerror(errno));
- return;
+ return -1;
}
cl = cmdline_new(main_ctx, "testpmd> ", fd, STDOUT_FILENO);
@@ -14177,6 +14216,7 @@ cmdline_read_from_file(const char *filename)
fprintf(stderr,
"Failed to create file based cmdline context: %s\n",
filename);
+ ret = -1;
goto end;
}
@@ -14190,6 +14230,7 @@ cmdline_read_from_file(const char *filename)
end:
if (fd >= 0)
close(fd);
+ return ret;
}
void
diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
index bb88555328..b498e6d9fe 100644
--- a/app/test-pmd/testpmd.c
+++ b/app/test-pmd/testpmd.c
@@ -4509,7 +4509,7 @@ main(int argc, char** argv)
"Could not initialise cmdline context.\n");
if (strlen(cmdline_filename) != 0)
- cmdline_read_from_file(cmdline_filename);
+ cmdline_read_from_file(cmdline_filename, echo_cmdline_file);
if (interactive == 1) {
if (auto_start) {
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index e629edaa02..1d34f40deb 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -928,7 +928,7 @@ unsigned int parse_hdrs_list(const char *str, const char *item_name,
unsigned int *parsed_items);
void launch_args_parse(int argc, char** argv);
void cmd_reconfig_device_queue(portid_t id, uint8_t dev, uint8_t queue);
-void cmdline_read_from_file(const char *filename);
+int cmdline_read_from_file(const char *filename, bool echo);
int init_cmdline(void);
void prompt(void);
void prompt_exit(void);
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index 6ad83ae50d..e12585f025 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -95,7 +95,7 @@ practical or possible testpmd supports alternative methods for executing command
* At run-time additional commands can be loaded in bulk by invoking the ``load FILENAME``
- command.
+ or ``load_echo FILENAME`` command.
.. code-block:: console
--
2.48.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 2/3] app/testpmd: allow multiple commandline file parameters
2025-07-04 18:34 ` [PATCH v2 0/3] improve cmdline file handling in testpmd Bruce Richardson
2025-07-04 18:34 ` [PATCH v2 1/3] app/testpmd: explicitly set command echoing on file load Bruce Richardson
@ 2025-07-04 18:34 ` Bruce Richardson
2025-07-04 18:34 ` [PATCH v2 3/3] app/testpmd: improve output when processing cmdline files Bruce Richardson
2 siblings, 0 replies; 16+ messages in thread
From: Bruce Richardson @ 2025-07-04 18:34 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson
While testpmd allows a set of pre-prepared commands to be passed into it
at startup via the "cmdline-file" (and cmdline-file-noecho) parameters,
this is currently limited to a single file. By extending this support
to allow the parameter to be passed multiple (up to 16) times, we enable
users to have a library of pre-canned cmdline files and pass multiple of
these in to the same run, e.g. have one cmdline file per NIC feature and
enable multiple features by passing in multiple filenames.
Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
app/test-pmd/parameters.c | 17 +++++++++++------
app/test-pmd/testpmd.c | 13 +++++++++----
app/test-pmd/testpmd.h | 13 +++++++++++--
doc/guides/testpmd_app_ug/run_app.rst | 3 ++-
doc/guides/testpmd_app_ug/testpmd_funcs.rst | 6 +++---
5 files changed, 36 insertions(+), 16 deletions(-)
diff --git a/app/test-pmd/parameters.c b/app/test-pmd/parameters.c
index 1132972913..ce2ba1c826 100644
--- a/app/test-pmd/parameters.c
+++ b/app/test-pmd/parameters.c
@@ -960,13 +960,18 @@ launch_args_parse(int argc, char** argv)
exit(EXIT_SUCCESS);
break;
case TESTPMD_OPT_CMDLINE_FILE_NUM:
- echo_cmdline_file = true;
- /* fall-through */
case TESTPMD_OPT_CMDLINE_FILE_NOECHO_NUM:
- printf("CLI commands to be read from %s\n",
- optarg);
- strlcpy(cmdline_filename, optarg,
- sizeof(cmdline_filename));
+ if (cmdline_file_count >= RTE_DIM(cmdline_files)) {
+ fprintf(stderr, "Too many cmdline files specified (maximum %zu)\n",
+ RTE_DIM(cmdline_files));
+ exit(EXIT_FAILURE);
+ }
+ printf("CLI commands to be read from %s\n", optarg);
+ strlcpy(cmdline_files[cmdline_file_count].filename, optarg,
+ sizeof(cmdline_files[cmdline_file_count].filename));
+ cmdline_files[cmdline_file_count].echo =
+ (opt == TESTPMD_OPT_CMDLINE_FILE_NUM);
+ cmdline_file_count++;
break;
case TESTPMD_OPT_TX_FIRST_NUM:
printf("Ports to start sending a burst of "
diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
index b498e6d9fe..505e0283fa 100644
--- a/app/test-pmd/testpmd.c
+++ b/app/test-pmd/testpmd.c
@@ -105,8 +105,8 @@ int testpmd_logtype; /**< Log type for testpmd logs */
uint8_t interactive = 0;
uint8_t auto_start = 0;
uint8_t tx_first;
-char cmdline_filename[PATH_MAX] = {0};
-bool echo_cmdline_file;
+struct cmdline_file_info cmdline_files[MAX_CMDLINE_FILENAMES] = {0};
+unsigned int cmdline_file_count;
/*
* NUMA support configuration.
@@ -4508,8 +4508,13 @@ main(int argc, char** argv)
rte_exit(EXIT_FAILURE,
"Could not initialise cmdline context.\n");
- if (strlen(cmdline_filename) != 0)
- cmdline_read_from_file(cmdline_filename, echo_cmdline_file);
+ for (unsigned int i = 0; i < cmdline_file_count; i++) {
+ if (cmdline_read_from_file(cmdline_files[i].filename, cmdline_files[i].echo) != 0) {
+ fprintf(stderr, "Failed to process cmdline file: %s\n",
+ cmdline_files[i].filename);
+ break;
+ }
+ }
if (interactive == 1) {
if (auto_start) {
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 1d34f40deb..6ee229cb5c 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -36,6 +36,15 @@
extern uint8_t cl_quit;
extern volatile uint8_t f_quit;
+/* Max number of cmdline files we can take on testpmd cmdline */
+#define MAX_CMDLINE_FILENAMES 16
+
+/* Structure to track cmdline files and their echo settings */
+struct cmdline_file_info {
+ char filename[PATH_MAX]; /**< Path to the cmdline file */
+ bool echo; /**< Whether to echo commands from this file */
+};
+
/*
* It is used to allocate the memory for hash key.
* The hash key size is NIC dependent.
@@ -509,8 +518,8 @@ extern int testpmd_logtype; /**< Log type for testpmd logs */
extern uint8_t interactive;
extern uint8_t auto_start;
extern uint8_t tx_first;
-extern char cmdline_filename[PATH_MAX]; /**< offline commands file */
-extern bool echo_cmdline_file; /** unset if cmdline-file-noecho is used */
+extern struct cmdline_file_info cmdline_files[MAX_CMDLINE_FILENAMES]; /**< offline commands files */
+extern unsigned int cmdline_file_count; /**< number of cmdline files */
extern uint8_t numa_support; /**< set by "--numa" parameter */
extern uint16_t port_topology; /**< set by "--port-topology" parameter */
extern uint8_t no_flush_rx; /**<set by "--no-flush-rx" parameter */
diff --git a/doc/guides/testpmd_app_ug/run_app.rst b/doc/guides/testpmd_app_ug/run_app.rst
index 330c37f2d9..680ecefac2 100644
--- a/doc/guides/testpmd_app_ug/run_app.rst
+++ b/doc/guides/testpmd_app_ug/run_app.rst
@@ -41,8 +41,9 @@ The command line options are:
* ``--cmdline-file=filename, --cmdline-file-noecho=filename``
- Read and execute commands from a file.
+ At startup, read and execute commands from a file.
The file should contain the same commands that can be entered interactively.
+ This option can be specified multiple times to process several files in sequence.
When using ``cmdline-file``, each command is printed as it is executed.
When using ``cmdline-file-noecho``, the commands are executed silently.
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index e12585f025..b8a401fa6f 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -67,13 +67,13 @@ Command File Functions
To facilitate loading large number of commands or to avoid cutting and pasting where not
practical or possible testpmd supports alternative methods for executing commands.
-* If started with the ``--cmdline-file=FILENAME`` command line argument testpmd
- will execute all CLI commands contained within the file immediately before
+* If started with the ``--cmdline-file=FILENAME`` or ``--cmdline-file-noecho=FILENAME`` command line argument,
+ testpmd will execute all CLI commands contained within the file immediately before
starting packet forwarding or entering interactive mode.
.. code-block:: console
- ./dpdk-testpmd -n4 -r2 ... -- -i --cmdline-file=/home/ubuntu/flow-create-commands.txt
+ ./dpdk-testpmd ... -- -i --cmdline-file-noecho=/home/ubuntu/flow-create-commands.txt
Interactive-mode selected
CLI commands to be read from /home/ubuntu/flow-create-commands.txt
Configuring Port 0 (socket 0)
--
2.48.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 3/3] app/testpmd: improve output when processing cmdline files
2025-07-04 18:34 ` [PATCH v2 0/3] improve cmdline file handling in testpmd Bruce Richardson
2025-07-04 18:34 ` [PATCH v2 1/3] app/testpmd: explicitly set command echoing on file load Bruce Richardson
2025-07-04 18:34 ` [PATCH v2 2/3] app/testpmd: allow multiple commandline file parameters Bruce Richardson
@ 2025-07-04 18:34 ` Bruce Richardson
2 siblings, 0 replies; 16+ messages in thread
From: Bruce Richardson @ 2025-07-04 18:34 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson
Two small improvements for the cmdline file processing in testpmd.
* Now that we support multiple files, change the prompt to indicate what
file is currently being processed, and just print an EOF message when
done.
* When not echoing, the "Read" verb in the message "Read CLI commands..."
is a little ambiguous, as it could mean "I have read", or "Go and read",
i.e. job done or job about to start. Tweak the text to "Finished reading"
which is unambiguous.
Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
app/test-pmd/cmdline.c | 27 ++++++++++++++++++---
doc/guides/testpmd_app_ug/testpmd_funcs.rst | 4 +--
2 files changed, 26 insertions(+), 5 deletions(-)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index 5433678b5e..b2a7aa8afd 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -10,6 +10,7 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
+#include <libgen.h>
#include <string.h>
#include <unistd.h>
#include <inttypes.h>
@@ -14195,6 +14196,7 @@ cmdline_read_from_file(const char *filename, bool echo)
struct cmdline *cl;
int fd = -1;
int ret = 0;
+ char *prompt = NULL;
/* cmdline_file_new does not produce any output
* so when echoing is requested we open filename directly
@@ -14203,6 +14205,18 @@ cmdline_read_from_file(const char *filename, bool echo)
if (!echo) {
cl = cmdline_file_new(main_ctx, "testpmd> ", filename);
} else {
+ char *filename_copy = strdup(filename);
+
+ if (filename_copy == NULL) {
+ fprintf(stderr, "Failed to allocate memory for filename\n");
+ return -1;
+ }
+ if (asprintf(&prompt, "[%s] ", basename(filename_copy)) < 0) {
+ fprintf(stderr, "Failed to allocate prompt string\n");
+ return -1;
+ }
+ free(filename_copy);
+
fd = open(filename, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Failed to open file %s: %s\n",
@@ -14210,7 +14224,7 @@ cmdline_read_from_file(const char *filename, bool echo)
return -1;
}
- cl = cmdline_new(main_ctx, "testpmd> ", fd, STDOUT_FILENO);
+ cl = cmdline_new(main_ctx, prompt, fd, STDOUT_FILENO);
}
if (cl == NULL) {
fprintf(stderr,
@@ -14221,15 +14235,22 @@ cmdline_read_from_file(const char *filename, bool echo)
}
cmdline_interact(cl);
- cmdline_quit(cl);
+ /* when done, if we have echo, we only need to print end of file,
+ * but if no echo, we need to use printf and include the filename.
+ */
+ if (echo)
+ cmdline_printf(cl, "<End-Of-File>\n");
+ else
+ printf("Finished reading CLI commands from %s\n", filename);
+ cmdline_quit(cl);
cmdline_free(cl);
- printf("Read CLI commands from %s\n", filename);
end:
if (fd >= 0)
close(fd);
+ free(prompt);
return ret;
}
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index b8a401fa6f..2b0c4897ba 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -90,7 +90,7 @@ practical or possible testpmd supports alternative methods for executing command
...
Flow rule #498 created
Flow rule #499 created
- Read all CLI commands from /home/ubuntu/flow-create-commands.txt
+ Finished reading all CLI commands from /home/ubuntu/flow-create-commands.txt
testpmd>
@@ -106,7 +106,7 @@ practical or possible testpmd supports alternative methods for executing command
...
Flow rule #498 created
Flow rule #499 created
- Read all CLI commands from /home/ubuntu/flow-create-commands.txt
+ Finished reading all CLI commands from /home/ubuntu/flow-create-commands.txt
testpmd>
--
2.48.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 0/3] improve cmdline file handling in testpmd
2025-07-04 14:05 [PATCH] app/testpmd: allow multiple cmdline-file parameters Bruce Richardson
2025-07-04 18:34 ` [PATCH v2 0/3] improve cmdline file handling in testpmd Bruce Richardson
@ 2025-07-07 11:17 ` Bruce Richardson
2025-07-07 11:17 ` [PATCH v3 1/3] app/testpmd: explicitly set command echoing on file load Bruce Richardson
` (2 more replies)
2025-07-31 16:00 ` [PATCH v4 0/4] improve cmdline file handling in testpmd Bruce Richardson
2 siblings, 3 replies; 16+ messages in thread
From: Bruce Richardson @ 2025-07-07 11:17 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson
Some small improvements to cmdline file handling testpmd, inspired by the
desire to have support for multiple cmdline files passed on the commandline
of a testpmd run.
The implementation is somewhat complicated by the setting for echo/noecho
of the commands, because the current implementation uses a global flag for
that - shared between cmdline parameters and interactive CLI commands.
V3:
* Fix windows support, no libgen or basename, no asprintf...
V2:
* remove global echo flag, and now support echo/noecho per file loaded
* when echoing, output the file being processed, to clarify things when
loading multiple files.
Bruce Richardson (3):
app/testpmd: explicitly set command echoing on file load
app/testpmd: allow multiple commandline file parameters
app/testpmd: improve output when processing cmdline files
app/test-pmd/cmdline.c | 100 ++++++++++++++++++--
app/test-pmd/parameters.c | 17 ++--
app/test-pmd/testpmd.c | 13 ++-
app/test-pmd/testpmd.h | 15 ++-
doc/guides/testpmd_app_ug/run_app.rst | 3 +-
doc/guides/testpmd_app_ug/testpmd_funcs.rst | 12 +--
6 files changed, 132 insertions(+), 28 deletions(-)
--
2.48.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 1/3] app/testpmd: explicitly set command echoing on file load
2025-07-07 11:17 ` [PATCH v3 0/3] improve cmdline file handling in testpmd Bruce Richardson
@ 2025-07-07 11:17 ` Bruce Richardson
2025-07-07 11:17 ` [PATCH v3 2/3] app/testpmd: allow multiple commandline file parameters Bruce Richardson
2025-07-07 11:17 ` [PATCH v3 3/3] app/testpmd: improve output when processing cmdline files Bruce Richardson
2 siblings, 0 replies; 16+ messages in thread
From: Bruce Richardson @ 2025-07-07 11:17 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson
The echoing of commands contained in a file loaded via the "load"
command on testpmd CLI is governed by whether or not a cmdline-file or
cmdline-file-noecho had been passed to testpmd on the commandline.
Remove the use of a global setting for this, and explicitly add a
"load_echo" command to match the "load" command.
Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
app/test-pmd/cmdline.c | 51 +++++++++++++++++++--
app/test-pmd/testpmd.c | 2 +-
app/test-pmd/testpmd.h | 2 +-
doc/guides/testpmd_app_ug/testpmd_funcs.rst | 2 +-
4 files changed, 49 insertions(+), 8 deletions(-)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index 7b4e27eddf..5433678b5e 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -11646,7 +11646,9 @@ cmd_load_from_file_parsed(
{
struct cmd_cmdfile_result *res = parsed_result;
- cmdline_read_from_file(res->filename);
+ if (cmdline_read_from_file(res->filename, false) != 0) {
+ fprintf(stderr, "Failed to load commands from file: %s\n", res->filename);
+ }
}
static cmdline_parse_inst_t cmd_load_from_file = {
@@ -11660,6 +11662,41 @@ static cmdline_parse_inst_t cmd_load_from_file = {
},
};
+/* command to load a file with echoing commands */
+struct cmd_load_echo_result {
+ cmdline_fixed_string_t load_echo;
+ cmdline_fixed_string_t filename;
+};
+
+/* CLI fields for file load with echo command */
+static cmdline_parse_token_string_t cmd_load_echo =
+ TOKEN_STRING_INITIALIZER(struct cmd_load_echo_result, load_echo, "load_echo");
+static cmdline_parse_token_string_t cmd_load_echo_filename =
+ TOKEN_STRING_INITIALIZER(struct cmd_load_echo_result, filename, NULL);
+
+static void
+cmd_load_echo_file_parsed(
+ void *parsed_result,
+ __rte_unused struct cmdline *cl,
+ __rte_unused void *data)
+{
+ struct cmd_load_echo_result *res = parsed_result;
+
+ if (cmdline_read_from_file(res->filename, true) != 0)
+ fprintf(stderr, "Failed to load commands from file: %s\n", res->filename);
+}
+
+static cmdline_parse_inst_t cmd_load_echo_file = {
+ .f = cmd_load_echo_file_parsed,
+ .data = NULL,
+ .help_str = "load_echo <filename>",
+ .tokens = {
+ (void *)&cmd_load_echo,
+ (void *)&cmd_load_echo_filename,
+ NULL,
+ },
+};
+
/* Get Rx offloads capabilities */
struct cmd_rx_offload_get_capa_result {
cmdline_fixed_string_t show;
@@ -13865,6 +13902,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
&cmd_help_long,
&cmd_quit,
&cmd_load_from_file,
+ &cmd_load_echo_file,
&cmd_showport,
&cmd_showqueue,
&cmd_showeeprom,
@@ -14151,24 +14189,25 @@ init_cmdline(void)
}
/* read cmdline commands from file */
-void
-cmdline_read_from_file(const char *filename)
+int
+cmdline_read_from_file(const char *filename, bool echo)
{
struct cmdline *cl;
int fd = -1;
+ int ret = 0;
/* cmdline_file_new does not produce any output
* so when echoing is requested we open filename directly
* and then pass that to cmdline_new with stdout as the output path.
*/
- if (!echo_cmdline_file) {
+ if (!echo) {
cl = cmdline_file_new(main_ctx, "testpmd> ", filename);
} else {
fd = open(filename, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Failed to open file %s: %s\n",
filename, strerror(errno));
- return;
+ return -1;
}
cl = cmdline_new(main_ctx, "testpmd> ", fd, STDOUT_FILENO);
@@ -14177,6 +14216,7 @@ cmdline_read_from_file(const char *filename)
fprintf(stderr,
"Failed to create file based cmdline context: %s\n",
filename);
+ ret = -1;
goto end;
}
@@ -14190,6 +14230,7 @@ cmdline_read_from_file(const char *filename)
end:
if (fd >= 0)
close(fd);
+ return ret;
}
void
diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
index bb88555328..b498e6d9fe 100644
--- a/app/test-pmd/testpmd.c
+++ b/app/test-pmd/testpmd.c
@@ -4509,7 +4509,7 @@ main(int argc, char** argv)
"Could not initialise cmdline context.\n");
if (strlen(cmdline_filename) != 0)
- cmdline_read_from_file(cmdline_filename);
+ cmdline_read_from_file(cmdline_filename, echo_cmdline_file);
if (interactive == 1) {
if (auto_start) {
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index e629edaa02..1d34f40deb 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -928,7 +928,7 @@ unsigned int parse_hdrs_list(const char *str, const char *item_name,
unsigned int *parsed_items);
void launch_args_parse(int argc, char** argv);
void cmd_reconfig_device_queue(portid_t id, uint8_t dev, uint8_t queue);
-void cmdline_read_from_file(const char *filename);
+int cmdline_read_from_file(const char *filename, bool echo);
int init_cmdline(void);
void prompt(void);
void prompt_exit(void);
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index 6ad83ae50d..e12585f025 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -95,7 +95,7 @@ practical or possible testpmd supports alternative methods for executing command
* At run-time additional commands can be loaded in bulk by invoking the ``load FILENAME``
- command.
+ or ``load_echo FILENAME`` command.
.. code-block:: console
--
2.48.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 2/3] app/testpmd: allow multiple commandline file parameters
2025-07-07 11:17 ` [PATCH v3 0/3] improve cmdline file handling in testpmd Bruce Richardson
2025-07-07 11:17 ` [PATCH v3 1/3] app/testpmd: explicitly set command echoing on file load Bruce Richardson
@ 2025-07-07 11:17 ` Bruce Richardson
2025-07-07 11:17 ` [PATCH v3 3/3] app/testpmd: improve output when processing cmdline files Bruce Richardson
2 siblings, 0 replies; 16+ messages in thread
From: Bruce Richardson @ 2025-07-07 11:17 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson
While testpmd allows a set of pre-prepared commands to be passed into it
at startup via the "cmdline-file" (and cmdline-file-noecho) parameters,
this is currently limited to a single file. By extending this support
to allow the parameter to be passed multiple (up to 16) times, we enable
users to have a library of pre-canned cmdline files and pass multiple of
these in to the same run, e.g. have one cmdline file per NIC feature and
enable multiple features by passing in multiple filenames.
Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
app/test-pmd/parameters.c | 17 +++++++++++------
app/test-pmd/testpmd.c | 13 +++++++++----
app/test-pmd/testpmd.h | 13 +++++++++++--
doc/guides/testpmd_app_ug/run_app.rst | 3 ++-
doc/guides/testpmd_app_ug/testpmd_funcs.rst | 6 +++---
5 files changed, 36 insertions(+), 16 deletions(-)
diff --git a/app/test-pmd/parameters.c b/app/test-pmd/parameters.c
index 1132972913..ce2ba1c826 100644
--- a/app/test-pmd/parameters.c
+++ b/app/test-pmd/parameters.c
@@ -960,13 +960,18 @@ launch_args_parse(int argc, char** argv)
exit(EXIT_SUCCESS);
break;
case TESTPMD_OPT_CMDLINE_FILE_NUM:
- echo_cmdline_file = true;
- /* fall-through */
case TESTPMD_OPT_CMDLINE_FILE_NOECHO_NUM:
- printf("CLI commands to be read from %s\n",
- optarg);
- strlcpy(cmdline_filename, optarg,
- sizeof(cmdline_filename));
+ if (cmdline_file_count >= RTE_DIM(cmdline_files)) {
+ fprintf(stderr, "Too many cmdline files specified (maximum %zu)\n",
+ RTE_DIM(cmdline_files));
+ exit(EXIT_FAILURE);
+ }
+ printf("CLI commands to be read from %s\n", optarg);
+ strlcpy(cmdline_files[cmdline_file_count].filename, optarg,
+ sizeof(cmdline_files[cmdline_file_count].filename));
+ cmdline_files[cmdline_file_count].echo =
+ (opt == TESTPMD_OPT_CMDLINE_FILE_NUM);
+ cmdline_file_count++;
break;
case TESTPMD_OPT_TX_FIRST_NUM:
printf("Ports to start sending a burst of "
diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
index b498e6d9fe..505e0283fa 100644
--- a/app/test-pmd/testpmd.c
+++ b/app/test-pmd/testpmd.c
@@ -105,8 +105,8 @@ int testpmd_logtype; /**< Log type for testpmd logs */
uint8_t interactive = 0;
uint8_t auto_start = 0;
uint8_t tx_first;
-char cmdline_filename[PATH_MAX] = {0};
-bool echo_cmdline_file;
+struct cmdline_file_info cmdline_files[MAX_CMDLINE_FILENAMES] = {0};
+unsigned int cmdline_file_count;
/*
* NUMA support configuration.
@@ -4508,8 +4508,13 @@ main(int argc, char** argv)
rte_exit(EXIT_FAILURE,
"Could not initialise cmdline context.\n");
- if (strlen(cmdline_filename) != 0)
- cmdline_read_from_file(cmdline_filename, echo_cmdline_file);
+ for (unsigned int i = 0; i < cmdline_file_count; i++) {
+ if (cmdline_read_from_file(cmdline_files[i].filename, cmdline_files[i].echo) != 0) {
+ fprintf(stderr, "Failed to process cmdline file: %s\n",
+ cmdline_files[i].filename);
+ break;
+ }
+ }
if (interactive == 1) {
if (auto_start) {
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 1d34f40deb..6ee229cb5c 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -36,6 +36,15 @@
extern uint8_t cl_quit;
extern volatile uint8_t f_quit;
+/* Max number of cmdline files we can take on testpmd cmdline */
+#define MAX_CMDLINE_FILENAMES 16
+
+/* Structure to track cmdline files and their echo settings */
+struct cmdline_file_info {
+ char filename[PATH_MAX]; /**< Path to the cmdline file */
+ bool echo; /**< Whether to echo commands from this file */
+};
+
/*
* It is used to allocate the memory for hash key.
* The hash key size is NIC dependent.
@@ -509,8 +518,8 @@ extern int testpmd_logtype; /**< Log type for testpmd logs */
extern uint8_t interactive;
extern uint8_t auto_start;
extern uint8_t tx_first;
-extern char cmdline_filename[PATH_MAX]; /**< offline commands file */
-extern bool echo_cmdline_file; /** unset if cmdline-file-noecho is used */
+extern struct cmdline_file_info cmdline_files[MAX_CMDLINE_FILENAMES]; /**< offline commands files */
+extern unsigned int cmdline_file_count; /**< number of cmdline files */
extern uint8_t numa_support; /**< set by "--numa" parameter */
extern uint16_t port_topology; /**< set by "--port-topology" parameter */
extern uint8_t no_flush_rx; /**<set by "--no-flush-rx" parameter */
diff --git a/doc/guides/testpmd_app_ug/run_app.rst b/doc/guides/testpmd_app_ug/run_app.rst
index 330c37f2d9..680ecefac2 100644
--- a/doc/guides/testpmd_app_ug/run_app.rst
+++ b/doc/guides/testpmd_app_ug/run_app.rst
@@ -41,8 +41,9 @@ The command line options are:
* ``--cmdline-file=filename, --cmdline-file-noecho=filename``
- Read and execute commands from a file.
+ At startup, read and execute commands from a file.
The file should contain the same commands that can be entered interactively.
+ This option can be specified multiple times to process several files in sequence.
When using ``cmdline-file``, each command is printed as it is executed.
When using ``cmdline-file-noecho``, the commands are executed silently.
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index e12585f025..b8a401fa6f 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -67,13 +67,13 @@ Command File Functions
To facilitate loading large number of commands or to avoid cutting and pasting where not
practical or possible testpmd supports alternative methods for executing commands.
-* If started with the ``--cmdline-file=FILENAME`` command line argument testpmd
- will execute all CLI commands contained within the file immediately before
+* If started with the ``--cmdline-file=FILENAME`` or ``--cmdline-file-noecho=FILENAME`` command line argument,
+ testpmd will execute all CLI commands contained within the file immediately before
starting packet forwarding or entering interactive mode.
.. code-block:: console
- ./dpdk-testpmd -n4 -r2 ... -- -i --cmdline-file=/home/ubuntu/flow-create-commands.txt
+ ./dpdk-testpmd ... -- -i --cmdline-file-noecho=/home/ubuntu/flow-create-commands.txt
Interactive-mode selected
CLI commands to be read from /home/ubuntu/flow-create-commands.txt
Configuring Port 0 (socket 0)
--
2.48.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 3/3] app/testpmd: improve output when processing cmdline files
2025-07-07 11:17 ` [PATCH v3 0/3] improve cmdline file handling in testpmd Bruce Richardson
2025-07-07 11:17 ` [PATCH v3 1/3] app/testpmd: explicitly set command echoing on file load Bruce Richardson
2025-07-07 11:17 ` [PATCH v3 2/3] app/testpmd: allow multiple commandline file parameters Bruce Richardson
@ 2025-07-07 11:17 ` Bruce Richardson
2025-07-29 4:24 ` Stephen Hemminger
2 siblings, 1 reply; 16+ messages in thread
From: Bruce Richardson @ 2025-07-07 11:17 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson
Two small improvements for the cmdline file processing in testpmd.
* Now that we support multiple files, change the prompt to indicate what
file is currently being processed, and just print an EOF message when
done.
* When not echoing, the "Read" verb in the message "Read CLI commands..."
is a little ambiguous, as it could mean "I have read", or "Go and read",
i.e. job done or job about to start. Tweak the text to "Finished reading"
which is unambiguous.
Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
Owing to limitations of whats available on windows, e.g. no basename
function, the windows implementation prints N characters from the end of
the filename, if its too long. While I don't think this is as good as the
basename version used on Linux/Unix, if we want to have common code, we can
just use that as a common version everywhere and drop the basename version.
---
app/test-pmd/cmdline.c | 49 +++++++++++++++++++--
doc/guides/testpmd_app_ug/testpmd_funcs.rst | 4 +-
2 files changed, 48 insertions(+), 5 deletions(-)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index 5433678b5e..0c58abe1e4 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -14,6 +14,9 @@
#include <unistd.h>
#include <inttypes.h>
#include <sys/queue.h>
+#ifndef RTE_EXEC_ENV_WINDOWS
+#include <libgen.h>
+#endif
#include <rte_common.h>
#include <rte_byteorder.h>
@@ -14195,6 +14198,7 @@ cmdline_read_from_file(const char *filename, bool echo)
struct cmdline *cl;
int fd = -1;
int ret = 0;
+ char *prompt = NULL;
/* cmdline_file_new does not produce any output
* so when echoing is requested we open filename directly
@@ -14203,6 +14207,38 @@ cmdline_read_from_file(const char *filename, bool echo)
if (!echo) {
cl = cmdline_file_new(main_ctx, "testpmd> ", filename);
} else {
+#ifndef RTE_EXEC_ENV_WINDOWS
+ /* use basename(filename) as prompt */
+ char *filename_copy = strdup(filename);
+
+ if (filename_copy == NULL) {
+ fprintf(stderr, "Failed to allocate memory for filename\n");
+ return -1;
+ }
+ if (asprintf(&prompt, "[%s] ", basename(filename_copy)) < 0) {
+ fprintf(stderr, "Failed to allocate prompt string\n");
+ return -1;
+ }
+ free(filename_copy);
+#else /* !WINDOWS */
+ /* No libgen and basename on windows, so just use short filename as prompt.
+ * if filename is longer than N chars, copy to prompt only the
+ * last N - 3 (since we add "...") chars, otherwise copy the whole filename.
+ */
+#define FILE_STR_LEN 18
+ const unsigned int len = strlen(filename);
+ prompt = malloc(FILE_STR_LEN + 4); /* 4 for "[] " + '\0' */
+
+ if (prompt == NULL) {
+ fprintf(stderr, "Failed to allocate memory for prompt\n");
+ return -1;
+ }
+ if (len > FILE_STR_LEN)
+ sprintf(prompt, "[...%s] ", &filename[len - (FILE_STR_LEN - 3)]);
+ else
+ sprintf(prompt, "[%s] ", filename);
+#endif /* !WINDOWS */
+
fd = open(filename, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Failed to open file %s: %s\n",
@@ -14210,7 +14246,7 @@ cmdline_read_from_file(const char *filename, bool echo)
return -1;
}
- cl = cmdline_new(main_ctx, "testpmd> ", fd, STDOUT_FILENO);
+ cl = cmdline_new(main_ctx, prompt, fd, STDOUT_FILENO);
}
if (cl == NULL) {
fprintf(stderr,
@@ -14221,15 +14257,22 @@ cmdline_read_from_file(const char *filename, bool echo)
}
cmdline_interact(cl);
- cmdline_quit(cl);
+ /* when done, if we have echo, we only need to print end of file,
+ * but if no echo, we need to use printf and include the filename.
+ */
+ if (echo)
+ cmdline_printf(cl, "<End-Of-File>\n");
+ else
+ printf("Finished reading CLI commands from %s\n", filename);
+ cmdline_quit(cl);
cmdline_free(cl);
- printf("Read CLI commands from %s\n", filename);
end:
if (fd >= 0)
close(fd);
+ free(prompt);
return ret;
}
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index b8a401fa6f..2b0c4897ba 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -90,7 +90,7 @@ practical or possible testpmd supports alternative methods for executing command
...
Flow rule #498 created
Flow rule #499 created
- Read all CLI commands from /home/ubuntu/flow-create-commands.txt
+ Finished reading all CLI commands from /home/ubuntu/flow-create-commands.txt
testpmd>
@@ -106,7 +106,7 @@ practical or possible testpmd supports alternative methods for executing command
...
Flow rule #498 created
Flow rule #499 created
- Read all CLI commands from /home/ubuntu/flow-create-commands.txt
+ Finished reading all CLI commands from /home/ubuntu/flow-create-commands.txt
testpmd>
--
2.48.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 3/3] app/testpmd: improve output when processing cmdline files
2025-07-07 11:17 ` [PATCH v3 3/3] app/testpmd: improve output when processing cmdline files Bruce Richardson
@ 2025-07-29 4:24 ` Stephen Hemminger
0 siblings, 0 replies; 16+ messages in thread
From: Stephen Hemminger @ 2025-07-29 4:24 UTC (permalink / raw)
To: Bruce Richardson; +Cc: dev
On Mon, 7 Jul 2025 12:17:51 +0100
Bruce Richardson <bruce.richardson@intel.com> wrote:
> Two small improvements for the cmdline file processing in testpmd.
>
> * Now that we support multiple files, change the prompt to indicate what
> file is currently being processed, and just print an EOF message when
> done.
> * When not echoing, the "Read" verb in the message "Read CLI commands..."
> is a little ambiguous, as it could mean "I have read", or "Go and read",
> i.e. job done or job about to start. Tweak the text to "Finished reading"
> which is unambiguous.
>
> Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
> ---
>
> Owing to limitations of whats available on windows, e.g. no basename
> function, the windows implementation prints N characters from the end of
> the filename, if its too long. While I don't think this is as good as the
> basename version used on Linux/Unix, if we want to have common code, we can
> just use that as a common version everywhere and drop the basename version.
I would prefer just having basename() wrapper like other helpers
we have already for Windows. Get from FreeBSD or other BSD licensed source
(or write your own).
Having two #ifdefs creates needless platform difference here.
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v4 0/4] improve cmdline file handling in testpmd
2025-07-04 14:05 [PATCH] app/testpmd: allow multiple cmdline-file parameters Bruce Richardson
2025-07-04 18:34 ` [PATCH v2 0/3] improve cmdline file handling in testpmd Bruce Richardson
2025-07-07 11:17 ` [PATCH v3 0/3] improve cmdline file handling in testpmd Bruce Richardson
@ 2025-07-31 16:00 ` Bruce Richardson
2025-07-31 16:00 ` [PATCH v4 1/4] eal: add basename function for common path manipulation Bruce Richardson
` (3 more replies)
2 siblings, 4 replies; 16+ messages in thread
From: Bruce Richardson @ 2025-07-31 16:00 UTC (permalink / raw)
To: dev; +Cc: stephen, andremue, Bruce Richardson
Some small improvements to cmdline file handling testpmd, inspired by the
desire to have support for multiple cmdline files passed on the commandline
of a testpmd run.
The implementation is somewhat complicated by the setting for echo/noecho
of the commands, because the current implementation uses a global flag for
that - shared between cmdline parameters and interactive CLI commands.
The final complication/addition, is the need for a common basename function
across our supported OS's. This is provided by the rte_basename function in
patch 1.
V4:
* remove ifdefs in testpmd code, by providing common rte_basename fn
V3:
* Fix windows support, no libgen or basename, no asprintf...
V2:
* remove global echo flag, and now support echo/noecho per file loaded
* when echoing, output the file being processed, to clarify things when
loading multiple files.
Bruce Richardson (4):
eal: add basename function for common path manipulation
app/testpmd: explicitly set command echoing on file load
app/testpmd: allow multiple commandline file parameters
app/testpmd: improve output when processing cmdline files
app/test-pmd/cmdline.c | 69 ++++++++++--
app/test-pmd/parameters.c | 17 +--
app/test-pmd/testpmd.c | 13 ++-
app/test-pmd/testpmd.h | 15 ++-
app/test/test_string_fns.c | 111 ++++++++++++++++++++
doc/guides/testpmd_app_ug/run_app.rst | 3 +-
doc/guides/testpmd_app_ug/testpmd_funcs.rst | 12 +--
lib/eal/include/rte_string_fns.h | 32 ++++++
lib/eal/unix/meson.build | 1 +
lib/eal/unix/rte_basename.c | 37 +++++++
lib/eal/windows/meson.build | 1 +
lib/eal/windows/rte_basename.c | 53 ++++++++++
12 files changed, 336 insertions(+), 28 deletions(-)
create mode 100644 lib/eal/unix/rte_basename.c
create mode 100644 lib/eal/windows/rte_basename.c
--
2.48.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v4 1/4] eal: add basename function for common path manipulation
2025-07-31 16:00 ` [PATCH v4 0/4] improve cmdline file handling in testpmd Bruce Richardson
@ 2025-07-31 16:00 ` Bruce Richardson
2025-08-01 14:25 ` Andre Muezerie
2025-07-31 16:00 ` [PATCH v4 2/4] app/testpmd: explicitly set command echoing on file load Bruce Richardson
` (2 subsequent siblings)
3 siblings, 1 reply; 16+ messages in thread
From: Bruce Richardson @ 2025-07-31 16:00 UTC (permalink / raw)
To: dev; +Cc: stephen, andremue, Bruce Richardson, Tyler Retzlaff, Dmitry Kozlyuk
There is no standard, cross-platform function to get the basename of a
file path across all the supported DPDK platforms, Linux, BSD and
Windows. Both Linux and BSD have a "basename" function in standard
library, except:
* Linux has two different basename functions, a POSIX version (which may
or may not modify args), and a GNU one which is guaranteed *not* to
modify the input arg and returns pointer to internal storage.
* FreeBSD has just the one basename function, but, to be different, it is
guaranteed *always* to modify the argument and re-use it for output.
* Windows just doesn't have a basename function, but provides _split_path
as a similar function, but with many differences over basename, e.g.
splitting off extension, returning empty basename if path ends in "/"
etc. etc.
Therefore, rather than just trying to implement basename for windows,
which opens the question as to whether to emulate GNU and *never* modify
arg, or emulate BSD and *always* modify arg, this patchset introduces
"rte_basename" which should have defined behaviour on all platforms. The
patch also introduces a set of test cases to confirm consistent behaviour
on all platforms too.
The behaviour is as in doxygen docs. Essentially:
- does not modify input path buffer
- returns output in a separate output buffer
- uses snprintf and strlcpy style return value to indicate truncation
Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
app/test/test_string_fns.c | 111 +++++++++++++++++++++++++++++++
lib/eal/include/rte_string_fns.h | 32 +++++++++
lib/eal/unix/meson.build | 1 +
lib/eal/unix/rte_basename.c | 37 +++++++++++
lib/eal/windows/meson.build | 1 +
lib/eal/windows/rte_basename.c | 53 +++++++++++++++
6 files changed, 235 insertions(+)
create mode 100644 lib/eal/unix/rte_basename.c
create mode 100644 lib/eal/windows/rte_basename.c
diff --git a/app/test/test_string_fns.c b/app/test/test_string_fns.c
index 3b311325dc..1a2830575e 100644
--- a/app/test/test_string_fns.c
+++ b/app/test/test_string_fns.c
@@ -205,6 +205,115 @@ test_rte_str_skip_leading_spaces(void)
return 0;
}
+static int
+test_rte_basename(void)
+{
+ /* Test case structure for positive cases */
+ struct {
+ const char *input_path; /* Input path string */
+ const char *expected; /* Expected result */
+ } test_cases[] = {
+ /* Test cases from man 3 basename */
+ {"/usr/lib", "lib"},
+ {"/usr/", "usr"},
+ {"usr", "usr"},
+ {"/", "/"},
+ {".", "."},
+ {"..", ".."},
+
+ /* Additional requested test cases */
+ {"/////", "/"},
+ {"/path/to/file.txt", "file.txt"},
+
+ /* Additional edge cases with trailing slashes */
+ {"///usr///", "usr"},
+ {"/a/b/c/", "c"},
+
+ /* Empty string case */
+ {"", "."},
+ {NULL, "."} /* NULL path should return "." */
+ };
+
+ char buf[256];
+ size_t result;
+
+ /* Run positive test cases from table */
+ for (size_t i = 0; i < RTE_DIM(test_cases); i++) {
+ result = rte_basename(test_cases[i].input_path, buf, sizeof(buf));
+
+ if (strcmp(buf, test_cases[i].expected) != 0) {
+ LOG("FAIL [%zu]: '%s' - buf contains '%s', expected '%s'\n",
+ i, test_cases[i].input_path, buf, test_cases[i].expected);
+ return -1;
+ }
+
+ /* Check that the return value matches the expected string length */
+ if (result != strlen(test_cases[i].expected)) {
+ LOG("FAIL [%zu]: '%s' - returned length %zu, expected %zu\n",
+ i, test_cases[i].input_path, result, strlen(test_cases[i].expected));
+ return -1;
+ }
+
+ LOG("PASS [%zu]: '%s' -> '%s' (len=%zu)\n",
+ i, test_cases[i].input_path, buf, result);
+ }
+
+ /* re-run the table above verifying that for a NULL buffer, or zero length, we get
+ * correct length returned.
+ */
+ for (size_t i = 0; i < RTE_DIM(test_cases); i++) {
+ result = rte_basename(test_cases[i].input_path, NULL, 0);
+ if (result != strlen(test_cases[i].expected)) {
+ LOG("FAIL [%zu]: '%s' - returned length %zu, expected %zu\n",
+ i, test_cases[i].input_path, result, strlen(test_cases[i].expected));
+ return -1;
+ }
+ LOG("PASS [%zu]: '%s' -> length %zu (NULL buffer case)\n",
+ i, test_cases[i].input_path, result);
+ }
+
+ /* Test case: buffer too small for result should truncate and return full length */
+ const size_t small_size = 5;
+ result = rte_basename("/path/to/very_long_filename.txt", buf, small_size);
+ /* Should be truncated to fit in 5 bytes (4 chars + null terminator) */
+ if (strlen(buf) >= small_size) {
+ LOG("FAIL: small buffer test - result '%s' not properly truncated (len=%zu, buflen=%zu)\n",
+ buf, strlen(buf), small_size);
+ return -1;
+ }
+ /* Return value should indicate truncation occurred (>= buflen) */
+ if (result != strlen("very_long_filename.txt")) {
+ LOG("FAIL: small buffer test - return value %zu doesn't indicate truncation (buflen=%zu)\n",
+ result, small_size);
+ return -1;
+ }
+ LOG("PASS: small buffer truncation -> '%s' (returned len=%zu, actual len=%zu)\n",
+ buf, result, strlen(buf));
+
+ /* extreme length test case - check that even with paths longer than PATH_MAX we still
+ * return the last component correctly. Use "/zzz...zzz/abc.txt" and check we get "abc.txt"
+ */
+ char basename_val[] = "abc.txt";
+ char long_path[PATH_MAX + 50];
+ for (int i = 0; i < PATH_MAX + 20; i++)
+ long_path[i] = (i == 0) ? '/' : 'z';
+ sprintf(long_path + PATH_MAX + 20, "/%s", basename_val);
+
+ result = rte_basename(long_path, buf, sizeof(buf));
+ if (strcmp(buf, basename_val) != 0) {
+ LOG("FAIL: long path test - expected '%s', got '%s'\n",
+ basename_val, buf);
+ return -1;
+ }
+ if (result != strlen(basename_val)) {
+ LOG("FAIL: long path test - expected length %zu, got %zu\n",
+ strlen(basename_val), result);
+ return -1;
+ }
+ LOG("PASS: long path test -> '%s' (len=%zu)\n", buf, result);
+ return 0;
+}
+
static int
test_string_fns(void)
{
@@ -214,6 +323,8 @@ test_string_fns(void)
return -1;
if (test_rte_str_skip_leading_spaces() < 0)
return -1;
+ if (test_rte_basename() < 0)
+ return -1;
return 0;
}
diff --git a/lib/eal/include/rte_string_fns.h b/lib/eal/include/rte_string_fns.h
index 702bd81251..3713c94acb 100644
--- a/lib/eal/include/rte_string_fns.h
+++ b/lib/eal/include/rte_string_fns.h
@@ -149,6 +149,38 @@ rte_str_skip_leading_spaces(const char *src)
return p;
}
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Provides the final component of a path, similar to POSIX basename function.
+ *
+ * This API provides the similar behaviour on all platforms, Linux, BSD, Windows,
+ * hiding the implementation differences.
+ * - It does not modify the input path.
+ * - The output buffer is passed as an argument, and the result is copied into it.
+ * - Expected output is the last component of the path, or the path itself if
+ * it does not contain a directory separator.
+ * - If the final component is too long to fit in the output buffer, it will be truncated.
+ * - For empty or NULL input paths, output buffer will contain the string ".".
+ * - Supports up to PATH_MAX (BSD/Linux) or _MAX_PATH (Windows) characters in the input path.
+ *
+ * @param path
+ * The input path string. Not modified by this function.
+ * @param buf
+ * The buffer to hold the resultant basename.
+ * Must be large enough to hold the result, otherwise basename will be truncated.
+ * @param buflen
+ * The size of the buffer in bytes.
+ * @return
+ * The number of bytes that were written to buf (excluding the terminating '\0').
+ * If the return value is >= buflen, truncation occurred.
+ * Return (size_t)-1 on error (Windows only)
+ */
+__rte_experimental
+size_t
+rte_basename(const char *path, char *buf, size_t buflen);
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/eal/unix/meson.build b/lib/eal/unix/meson.build
index f1eb82e16a..70af352dab 100644
--- a/lib/eal/unix/meson.build
+++ b/lib/eal/unix/meson.build
@@ -9,6 +9,7 @@ sources += files(
'eal_unix_memory.c',
'eal_unix_thread.c',
'eal_unix_timer.c',
+ 'rte_basename.c',
'rte_thread.c',
)
diff --git a/lib/eal/unix/rte_basename.c b/lib/eal/unix/rte_basename.c
new file mode 100644
index 0000000000..a72d6bb3c9
--- /dev/null
+++ b/lib/eal/unix/rte_basename.c
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Intel Corporation
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include <limits.h>
+
+#include <eal_export.h>
+#include <rte_string_fns.h>
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_basename, 25.11)
+size_t
+rte_basename(const char *path, char *buf, size_t buflen)
+{
+ char copy[PATH_MAX + 1];
+ size_t retval = 0;
+
+ if (path == NULL)
+ return (buf == NULL) ? strlen(".") : strlcpy(buf, ".", buflen);
+
+ /* basename is on the end, so if path is too long, use only last PATH_MAX bytes */
+ const size_t pathlen = strlen(path);
+ if (pathlen > PATH_MAX)
+ path = &path[pathlen - PATH_MAX];
+
+ /* make a copy of buffer since basename may modify it */
+ strlcpy(copy, path, sizeof(copy));
+
+ /* if passed a null buffer, just return length of basename, otherwise strlcpy it */
+ retval = (buf == NULL) ?
+ strlen(basename(copy)) :
+ strlcpy(buf, basename(copy), buflen);
+
+ return retval;
+}
diff --git a/lib/eal/windows/meson.build b/lib/eal/windows/meson.build
index c526ede405..e7fad1f010 100644
--- a/lib/eal/windows/meson.build
+++ b/lib/eal/windows/meson.build
@@ -19,6 +19,7 @@ sources += files(
'eal_timer.c',
'getline.c',
'getopt.c',
+ 'rte_basename.c',
'rte_thread.c',
)
diff --git a/lib/eal/windows/rte_basename.c b/lib/eal/windows/rte_basename.c
new file mode 100644
index 0000000000..f4dfc08a0a
--- /dev/null
+++ b/lib/eal/windows/rte_basename.c
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Intel Corporation
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <rte_string_fns.h>
+
+size_t
+rte_basename(const char *path, char *buf, size_t buflen)
+{
+ char fname[_MAX_FNAME + 1];
+ char ext[_MAX_EXT + 1];
+ char dir[_MAX_DIR + 1];
+
+ if (path == NULL || path[0] == '\0')
+ return (buf == NULL) ? strlen(".") : strlcpy(buf, ".", buflen);
+
+ /* basename is on the end, so if path is too long, use only last PATH_MAX bytes */
+ const size_t pathlen = strlen(path);
+ if (pathlen > _MAX_PATH)
+ path = &path[pathlen - _MAX_PATH];
+
+
+ /* Use _splitpath_s to separate the path into components */
+ int ret = _splitpath_s(path, NULL, 0, dir, sizeof(dir),
+ fname, sizeof(fname), ext, sizeof(ext));
+ if (ret != 0)
+ return (size_t)-1;
+
+ /* if there is a trailing slash, then split_path returns no basename, but
+ * we want to return the last component of the path in all cases.
+ * Therefore re-run removing trailing slash from path.
+ */
+ if (fname[0] == '\0' && ext[0] == '\0') {
+ size_t dirlen = strlen(dir);
+ while (dirlen > 0 && (dir[dirlen - 1] == '\\' || dir[dirlen - 1] == '/')) {
+ /* special case for "/" to keep *nix compatibility */
+ if (strcmp(dir, "/") == 0)
+ return (buf == NULL) ? strlen(dir) : strlcpy(buf, dir, buflen);
+
+ /* Remove trailing backslash */
+ dir[--dirlen] = '\0';
+ }
+ _splitpath_s(dir, NULL, 0, NULL, 0, fname, sizeof(fname), ext, sizeof(ext));
+ }
+
+ if (buf == NULL)
+ return strlen(fname) + strlen(ext);
+
+ /* Combine the filename and extension into output */
+ return snprintf(buf, buflen, "%s%s", fname, ext);
+}
--
2.48.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v4 2/4] app/testpmd: explicitly set command echoing on file load
2025-07-31 16:00 ` [PATCH v4 0/4] improve cmdline file handling in testpmd Bruce Richardson
2025-07-31 16:00 ` [PATCH v4 1/4] eal: add basename function for common path manipulation Bruce Richardson
@ 2025-07-31 16:00 ` Bruce Richardson
2025-07-31 16:00 ` [PATCH v4 3/4] app/testpmd: allow multiple commandline file parameters Bruce Richardson
2025-07-31 16:00 ` [PATCH v4 4/4] app/testpmd: improve output when processing cmdline files Bruce Richardson
3 siblings, 0 replies; 16+ messages in thread
From: Bruce Richardson @ 2025-07-31 16:00 UTC (permalink / raw)
To: dev; +Cc: stephen, andremue, Bruce Richardson, Aman Singh
The echoing of commands contained in a file loaded via the "load"
command on testpmd CLI is governed by whether or not a cmdline-file or
cmdline-file-noecho had been passed to testpmd on the commandline.
Remove the use of a global setting for this, and explicitly add a
"load_echo" command to match the "load" command.
Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
app/test-pmd/cmdline.c | 51 +++++++++++++++++++--
app/test-pmd/testpmd.c | 2 +-
app/test-pmd/testpmd.h | 2 +-
doc/guides/testpmd_app_ug/testpmd_funcs.rst | 2 +-
4 files changed, 49 insertions(+), 8 deletions(-)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index 7b4e27eddf..5433678b5e 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -11646,7 +11646,9 @@ cmd_load_from_file_parsed(
{
struct cmd_cmdfile_result *res = parsed_result;
- cmdline_read_from_file(res->filename);
+ if (cmdline_read_from_file(res->filename, false) != 0) {
+ fprintf(stderr, "Failed to load commands from file: %s\n", res->filename);
+ }
}
static cmdline_parse_inst_t cmd_load_from_file = {
@@ -11660,6 +11662,41 @@ static cmdline_parse_inst_t cmd_load_from_file = {
},
};
+/* command to load a file with echoing commands */
+struct cmd_load_echo_result {
+ cmdline_fixed_string_t load_echo;
+ cmdline_fixed_string_t filename;
+};
+
+/* CLI fields for file load with echo command */
+static cmdline_parse_token_string_t cmd_load_echo =
+ TOKEN_STRING_INITIALIZER(struct cmd_load_echo_result, load_echo, "load_echo");
+static cmdline_parse_token_string_t cmd_load_echo_filename =
+ TOKEN_STRING_INITIALIZER(struct cmd_load_echo_result, filename, NULL);
+
+static void
+cmd_load_echo_file_parsed(
+ void *parsed_result,
+ __rte_unused struct cmdline *cl,
+ __rte_unused void *data)
+{
+ struct cmd_load_echo_result *res = parsed_result;
+
+ if (cmdline_read_from_file(res->filename, true) != 0)
+ fprintf(stderr, "Failed to load commands from file: %s\n", res->filename);
+}
+
+static cmdline_parse_inst_t cmd_load_echo_file = {
+ .f = cmd_load_echo_file_parsed,
+ .data = NULL,
+ .help_str = "load_echo <filename>",
+ .tokens = {
+ (void *)&cmd_load_echo,
+ (void *)&cmd_load_echo_filename,
+ NULL,
+ },
+};
+
/* Get Rx offloads capabilities */
struct cmd_rx_offload_get_capa_result {
cmdline_fixed_string_t show;
@@ -13865,6 +13902,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
&cmd_help_long,
&cmd_quit,
&cmd_load_from_file,
+ &cmd_load_echo_file,
&cmd_showport,
&cmd_showqueue,
&cmd_showeeprom,
@@ -14151,24 +14189,25 @@ init_cmdline(void)
}
/* read cmdline commands from file */
-void
-cmdline_read_from_file(const char *filename)
+int
+cmdline_read_from_file(const char *filename, bool echo)
{
struct cmdline *cl;
int fd = -1;
+ int ret = 0;
/* cmdline_file_new does not produce any output
* so when echoing is requested we open filename directly
* and then pass that to cmdline_new with stdout as the output path.
*/
- if (!echo_cmdline_file) {
+ if (!echo) {
cl = cmdline_file_new(main_ctx, "testpmd> ", filename);
} else {
fd = open(filename, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Failed to open file %s: %s\n",
filename, strerror(errno));
- return;
+ return -1;
}
cl = cmdline_new(main_ctx, "testpmd> ", fd, STDOUT_FILENO);
@@ -14177,6 +14216,7 @@ cmdline_read_from_file(const char *filename)
fprintf(stderr,
"Failed to create file based cmdline context: %s\n",
filename);
+ ret = -1;
goto end;
}
@@ -14190,6 +14230,7 @@ cmdline_read_from_file(const char *filename)
end:
if (fd >= 0)
close(fd);
+ return ret;
}
void
diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
index bb88555328..b498e6d9fe 100644
--- a/app/test-pmd/testpmd.c
+++ b/app/test-pmd/testpmd.c
@@ -4509,7 +4509,7 @@ main(int argc, char** argv)
"Could not initialise cmdline context.\n");
if (strlen(cmdline_filename) != 0)
- cmdline_read_from_file(cmdline_filename);
+ cmdline_read_from_file(cmdline_filename, echo_cmdline_file);
if (interactive == 1) {
if (auto_start) {
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index e629edaa02..1d34f40deb 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -928,7 +928,7 @@ unsigned int parse_hdrs_list(const char *str, const char *item_name,
unsigned int *parsed_items);
void launch_args_parse(int argc, char** argv);
void cmd_reconfig_device_queue(portid_t id, uint8_t dev, uint8_t queue);
-void cmdline_read_from_file(const char *filename);
+int cmdline_read_from_file(const char *filename, bool echo);
int init_cmdline(void);
void prompt(void);
void prompt_exit(void);
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index 6ad83ae50d..e12585f025 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -95,7 +95,7 @@ practical or possible testpmd supports alternative methods for executing command
* At run-time additional commands can be loaded in bulk by invoking the ``load FILENAME``
- command.
+ or ``load_echo FILENAME`` command.
.. code-block:: console
--
2.48.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v4 3/4] app/testpmd: allow multiple commandline file parameters
2025-07-31 16:00 ` [PATCH v4 0/4] improve cmdline file handling in testpmd Bruce Richardson
2025-07-31 16:00 ` [PATCH v4 1/4] eal: add basename function for common path manipulation Bruce Richardson
2025-07-31 16:00 ` [PATCH v4 2/4] app/testpmd: explicitly set command echoing on file load Bruce Richardson
@ 2025-07-31 16:00 ` Bruce Richardson
2025-07-31 16:00 ` [PATCH v4 4/4] app/testpmd: improve output when processing cmdline files Bruce Richardson
3 siblings, 0 replies; 16+ messages in thread
From: Bruce Richardson @ 2025-07-31 16:00 UTC (permalink / raw)
To: dev; +Cc: stephen, andremue, Bruce Richardson, Aman Singh
While testpmd allows a set of pre-prepared commands to be passed into it
at startup via the "cmdline-file" (and cmdline-file-noecho) parameters,
this is currently limited to a single file. By extending this support
to allow the parameter to be passed multiple (up to 16) times, we enable
users to have a library of pre-canned cmdline files and pass multiple of
these in to the same run, e.g. have one cmdline file per NIC feature and
enable multiple features by passing in multiple filenames.
Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
app/test-pmd/parameters.c | 17 +++++++++++------
app/test-pmd/testpmd.c | 13 +++++++++----
app/test-pmd/testpmd.h | 13 +++++++++++--
doc/guides/testpmd_app_ug/run_app.rst | 3 ++-
doc/guides/testpmd_app_ug/testpmd_funcs.rst | 6 +++---
5 files changed, 36 insertions(+), 16 deletions(-)
diff --git a/app/test-pmd/parameters.c b/app/test-pmd/parameters.c
index 1132972913..ce2ba1c826 100644
--- a/app/test-pmd/parameters.c
+++ b/app/test-pmd/parameters.c
@@ -960,13 +960,18 @@ launch_args_parse(int argc, char** argv)
exit(EXIT_SUCCESS);
break;
case TESTPMD_OPT_CMDLINE_FILE_NUM:
- echo_cmdline_file = true;
- /* fall-through */
case TESTPMD_OPT_CMDLINE_FILE_NOECHO_NUM:
- printf("CLI commands to be read from %s\n",
- optarg);
- strlcpy(cmdline_filename, optarg,
- sizeof(cmdline_filename));
+ if (cmdline_file_count >= RTE_DIM(cmdline_files)) {
+ fprintf(stderr, "Too many cmdline files specified (maximum %zu)\n",
+ RTE_DIM(cmdline_files));
+ exit(EXIT_FAILURE);
+ }
+ printf("CLI commands to be read from %s\n", optarg);
+ strlcpy(cmdline_files[cmdline_file_count].filename, optarg,
+ sizeof(cmdline_files[cmdline_file_count].filename));
+ cmdline_files[cmdline_file_count].echo =
+ (opt == TESTPMD_OPT_CMDLINE_FILE_NUM);
+ cmdline_file_count++;
break;
case TESTPMD_OPT_TX_FIRST_NUM:
printf("Ports to start sending a burst of "
diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
index b498e6d9fe..505e0283fa 100644
--- a/app/test-pmd/testpmd.c
+++ b/app/test-pmd/testpmd.c
@@ -105,8 +105,8 @@ int testpmd_logtype; /**< Log type for testpmd logs */
uint8_t interactive = 0;
uint8_t auto_start = 0;
uint8_t tx_first;
-char cmdline_filename[PATH_MAX] = {0};
-bool echo_cmdline_file;
+struct cmdline_file_info cmdline_files[MAX_CMDLINE_FILENAMES] = {0};
+unsigned int cmdline_file_count;
/*
* NUMA support configuration.
@@ -4508,8 +4508,13 @@ main(int argc, char** argv)
rte_exit(EXIT_FAILURE,
"Could not initialise cmdline context.\n");
- if (strlen(cmdline_filename) != 0)
- cmdline_read_from_file(cmdline_filename, echo_cmdline_file);
+ for (unsigned int i = 0; i < cmdline_file_count; i++) {
+ if (cmdline_read_from_file(cmdline_files[i].filename, cmdline_files[i].echo) != 0) {
+ fprintf(stderr, "Failed to process cmdline file: %s\n",
+ cmdline_files[i].filename);
+ break;
+ }
+ }
if (interactive == 1) {
if (auto_start) {
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 1d34f40deb..6ee229cb5c 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -36,6 +36,15 @@
extern uint8_t cl_quit;
extern volatile uint8_t f_quit;
+/* Max number of cmdline files we can take on testpmd cmdline */
+#define MAX_CMDLINE_FILENAMES 16
+
+/* Structure to track cmdline files and their echo settings */
+struct cmdline_file_info {
+ char filename[PATH_MAX]; /**< Path to the cmdline file */
+ bool echo; /**< Whether to echo commands from this file */
+};
+
/*
* It is used to allocate the memory for hash key.
* The hash key size is NIC dependent.
@@ -509,8 +518,8 @@ extern int testpmd_logtype; /**< Log type for testpmd logs */
extern uint8_t interactive;
extern uint8_t auto_start;
extern uint8_t tx_first;
-extern char cmdline_filename[PATH_MAX]; /**< offline commands file */
-extern bool echo_cmdline_file; /** unset if cmdline-file-noecho is used */
+extern struct cmdline_file_info cmdline_files[MAX_CMDLINE_FILENAMES]; /**< offline commands files */
+extern unsigned int cmdline_file_count; /**< number of cmdline files */
extern uint8_t numa_support; /**< set by "--numa" parameter */
extern uint16_t port_topology; /**< set by "--port-topology" parameter */
extern uint8_t no_flush_rx; /**<set by "--no-flush-rx" parameter */
diff --git a/doc/guides/testpmd_app_ug/run_app.rst b/doc/guides/testpmd_app_ug/run_app.rst
index 82f61b133c..efefad8738 100644
--- a/doc/guides/testpmd_app_ug/run_app.rst
+++ b/doc/guides/testpmd_app_ug/run_app.rst
@@ -41,8 +41,9 @@ The command line options are:
* ``--cmdline-file=filename, --cmdline-file-noecho=filename``
- Read and execute commands from a file.
+ At startup, read and execute commands from a file.
The file should contain the same commands that can be entered interactively.
+ This option can be specified multiple times to process several files in sequence.
When using ``cmdline-file``, each command is printed as it is executed.
When using ``cmdline-file-noecho``, the commands are executed silently.
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index e12585f025..b8a401fa6f 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -67,13 +67,13 @@ Command File Functions
To facilitate loading large number of commands or to avoid cutting and pasting where not
practical or possible testpmd supports alternative methods for executing commands.
-* If started with the ``--cmdline-file=FILENAME`` command line argument testpmd
- will execute all CLI commands contained within the file immediately before
+* If started with the ``--cmdline-file=FILENAME`` or ``--cmdline-file-noecho=FILENAME`` command line argument,
+ testpmd will execute all CLI commands contained within the file immediately before
starting packet forwarding or entering interactive mode.
.. code-block:: console
- ./dpdk-testpmd -n4 -r2 ... -- -i --cmdline-file=/home/ubuntu/flow-create-commands.txt
+ ./dpdk-testpmd ... -- -i --cmdline-file-noecho=/home/ubuntu/flow-create-commands.txt
Interactive-mode selected
CLI commands to be read from /home/ubuntu/flow-create-commands.txt
Configuring Port 0 (socket 0)
--
2.48.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v4 4/4] app/testpmd: improve output when processing cmdline files
2025-07-31 16:00 ` [PATCH v4 0/4] improve cmdline file handling in testpmd Bruce Richardson
` (2 preceding siblings ...)
2025-07-31 16:00 ` [PATCH v4 3/4] app/testpmd: allow multiple commandline file parameters Bruce Richardson
@ 2025-07-31 16:00 ` Bruce Richardson
3 siblings, 0 replies; 16+ messages in thread
From: Bruce Richardson @ 2025-07-31 16:00 UTC (permalink / raw)
To: dev; +Cc: stephen, andremue, Bruce Richardson, Aman Singh
Two small improvements for the cmdline file processing in testpmd.
* Now that we support multiple files, change the prompt to indicate what
file is currently being processed, and just print an EOF message when
done.
* When not echoing, the "Read" verb in the message "Read CLI commands..."
is a little ambiguous, as it could mean "I have read", or "Go and read",
i.e. job done or job about to start. Tweak the text to "Finished reading"
which is unambiguous.
Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
app/test-pmd/cmdline.c | 18 +++++++++++++++---
doc/guides/testpmd_app_ug/testpmd_funcs.rst | 4 ++--
2 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index 5433678b5e..c436a2fa21 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -14203,6 +14203,12 @@ cmdline_read_from_file(const char *filename, bool echo)
if (!echo) {
cl = cmdline_file_new(main_ctx, "testpmd> ", filename);
} else {
+ /* use basename(filename) as prompt */
+ char prompt[32] = "[] ";
+
+ rte_basename(filename, &prompt[1], sizeof(prompt) - strlen(prompt));
+ strlcat(prompt, "] ", sizeof(prompt));
+
fd = open(filename, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Failed to open file %s: %s\n",
@@ -14210,7 +14216,7 @@ cmdline_read_from_file(const char *filename, bool echo)
return -1;
}
- cl = cmdline_new(main_ctx, "testpmd> ", fd, STDOUT_FILENO);
+ cl = cmdline_new(main_ctx, prompt, fd, STDOUT_FILENO);
}
if (cl == NULL) {
fprintf(stderr,
@@ -14221,11 +14227,17 @@ cmdline_read_from_file(const char *filename, bool echo)
}
cmdline_interact(cl);
- cmdline_quit(cl);
+ /* when done, if we have echo, we only need to print end of file,
+ * but if no echo, we need to use printf and include the filename.
+ */
+ if (echo)
+ cmdline_printf(cl, "<End-Of-File>\n");
+ else
+ printf("Finished reading CLI commands from %s\n", filename);
+ cmdline_quit(cl);
cmdline_free(cl);
- printf("Read CLI commands from %s\n", filename);
end:
if (fd >= 0)
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index b8a401fa6f..2b0c4897ba 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -90,7 +90,7 @@ practical or possible testpmd supports alternative methods for executing command
...
Flow rule #498 created
Flow rule #499 created
- Read all CLI commands from /home/ubuntu/flow-create-commands.txt
+ Finished reading all CLI commands from /home/ubuntu/flow-create-commands.txt
testpmd>
@@ -106,7 +106,7 @@ practical or possible testpmd supports alternative methods for executing command
...
Flow rule #498 created
Flow rule #499 created
- Read all CLI commands from /home/ubuntu/flow-create-commands.txt
+ Finished reading all CLI commands from /home/ubuntu/flow-create-commands.txt
testpmd>
--
2.48.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v4 1/4] eal: add basename function for common path manipulation
2025-07-31 16:00 ` [PATCH v4 1/4] eal: add basename function for common path manipulation Bruce Richardson
@ 2025-08-01 14:25 ` Andre Muezerie
0 siblings, 0 replies; 16+ messages in thread
From: Andre Muezerie @ 2025-08-01 14:25 UTC (permalink / raw)
To: Bruce Richardson; +Cc: dev, stephen, Tyler Retzlaff, Dmitry Kozlyuk
On Thu, Jul 31, 2025 at 04:00:38PM +0000, Bruce Richardson wrote:
> There is no standard, cross-platform function to get the basename of a
> file path across all the supported DPDK platforms, Linux, BSD and
> Windows. Both Linux and BSD have a "basename" function in standard
> library, except:
> * Linux has two different basename functions, a POSIX version (which may
> or may not modify args), and a GNU one which is guaranteed *not* to
> modify the input arg and returns pointer to internal storage.
> * FreeBSD has just the one basename function, but, to be different, it is
> guaranteed *always* to modify the argument and re-use it for output.
> * Windows just doesn't have a basename function, but provides _split_path
> as a similar function, but with many differences over basename, e.g.
> splitting off extension, returning empty basename if path ends in "/"
> etc. etc.
>
> Therefore, rather than just trying to implement basename for windows,
> which opens the question as to whether to emulate GNU and *never* modify
> arg, or emulate BSD and *always* modify arg, this patchset introduces
> "rte_basename" which should have defined behaviour on all platforms. The
> patch also introduces a set of test cases to confirm consistent behaviour
> on all platforms too.
>
> The behaviour is as in doxygen docs. Essentially:
> - does not modify input path buffer
> - returns output in a separate output buffer
> - uses snprintf and strlcpy style return value to indicate truncation
>
> Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
> ---
> app/test/test_string_fns.c | 111 +++++++++++++++++++++++++++++++
> lib/eal/include/rte_string_fns.h | 32 +++++++++
> lib/eal/unix/meson.build | 1 +
> lib/eal/unix/rte_basename.c | 37 +++++++++++
> lib/eal/windows/meson.build | 1 +
> lib/eal/windows/rte_basename.c | 53 +++++++++++++++
> 6 files changed, 235 insertions(+)
> create mode 100644 lib/eal/unix/rte_basename.c
> create mode 100644 lib/eal/windows/rte_basename.c
>
> diff --git a/app/test/test_string_fns.c b/app/test/test_string_fns.c
> index 3b311325dc..1a2830575e 100644
> --- a/app/test/test_string_fns.c
> +++ b/app/test/test_string_fns.c
> @@ -205,6 +205,115 @@ test_rte_str_skip_leading_spaces(void)
> return 0;
> }
>
> +static int
> +test_rte_basename(void)
> +{
> + /* Test case structure for positive cases */
> + struct {
> + const char *input_path; /* Input path string */
> + const char *expected; /* Expected result */
> + } test_cases[] = {
> + /* Test cases from man 3 basename */
> + {"/usr/lib", "lib"},
> + {"/usr/", "usr"},
> + {"usr", "usr"},
> + {"/", "/"},
> + {".", "."},
> + {"..", ".."},
> +
> + /* Additional requested test cases */
> + {"/////", "/"},
> + {"/path/to/file.txt", "file.txt"},
> +
> + /* Additional edge cases with trailing slashes */
> + {"///usr///", "usr"},
> + {"/a/b/c/", "c"},
> +
> + /* Empty string case */
> + {"", "."},
> + {NULL, "."} /* NULL path should return "." */
> + };
> +
> + char buf[256];
> + size_t result;
> +
> + /* Run positive test cases from table */
> + for (size_t i = 0; i < RTE_DIM(test_cases); i++) {
> + result = rte_basename(test_cases[i].input_path, buf, sizeof(buf));
> +
> + if (strcmp(buf, test_cases[i].expected) != 0) {
> + LOG("FAIL [%zu]: '%s' - buf contains '%s', expected '%s'\n",
> + i, test_cases[i].input_path, buf, test_cases[i].expected);
> + return -1;
> + }
> +
> + /* Check that the return value matches the expected string length */
> + if (result != strlen(test_cases[i].expected)) {
> + LOG("FAIL [%zu]: '%s' - returned length %zu, expected %zu\n",
> + i, test_cases[i].input_path, result, strlen(test_cases[i].expected));
> + return -1;
> + }
> +
> + LOG("PASS [%zu]: '%s' -> '%s' (len=%zu)\n",
> + i, test_cases[i].input_path, buf, result);
> + }
> +
> + /* re-run the table above verifying that for a NULL buffer, or zero length, we get
> + * correct length returned.
> + */
> + for (size_t i = 0; i < RTE_DIM(test_cases); i++) {
> + result = rte_basename(test_cases[i].input_path, NULL, 0);
> + if (result != strlen(test_cases[i].expected)) {
> + LOG("FAIL [%zu]: '%s' - returned length %zu, expected %zu\n",
> + i, test_cases[i].input_path, result, strlen(test_cases[i].expected));
> + return -1;
> + }
> + LOG("PASS [%zu]: '%s' -> length %zu (NULL buffer case)\n",
> + i, test_cases[i].input_path, result);
> + }
> +
> + /* Test case: buffer too small for result should truncate and return full length */
> + const size_t small_size = 5;
> + result = rte_basename("/path/to/very_long_filename.txt", buf, small_size);
> + /* Should be truncated to fit in 5 bytes (4 chars + null terminator) */
> + if (strlen(buf) >= small_size) {
> + LOG("FAIL: small buffer test - result '%s' not properly truncated (len=%zu, buflen=%zu)\n",
> + buf, strlen(buf), small_size);
> + return -1;
> + }
> + /* Return value should indicate truncation occurred (>= buflen) */
> + if (result != strlen("very_long_filename.txt")) {
> + LOG("FAIL: small buffer test - return value %zu doesn't indicate truncation (buflen=%zu)\n",
> + result, small_size);
> + return -1;
> + }
> + LOG("PASS: small buffer truncation -> '%s' (returned len=%zu, actual len=%zu)\n",
> + buf, result, strlen(buf));
> +
> + /* extreme length test case - check that even with paths longer than PATH_MAX we still
> + * return the last component correctly. Use "/zzz...zzz/abc.txt" and check we get "abc.txt"
> + */
> + char basename_val[] = "abc.txt";
> + char long_path[PATH_MAX + 50];
> + for (int i = 0; i < PATH_MAX + 20; i++)
> + long_path[i] = (i == 0) ? '/' : 'z';
> + sprintf(long_path + PATH_MAX + 20, "/%s", basename_val);
> +
> + result = rte_basename(long_path, buf, sizeof(buf));
> + if (strcmp(buf, basename_val) != 0) {
> + LOG("FAIL: long path test - expected '%s', got '%s'\n",
> + basename_val, buf);
> + return -1;
> + }
> + if (result != strlen(basename_val)) {
> + LOG("FAIL: long path test - expected length %zu, got %zu\n",
> + strlen(basename_val), result);
> + return -1;
> + }
> + LOG("PASS: long path test -> '%s' (len=%zu)\n", buf, result);
> + return 0;
> +}
> +
> static int
> test_string_fns(void)
> {
> @@ -214,6 +323,8 @@ test_string_fns(void)
> return -1;
> if (test_rte_str_skip_leading_spaces() < 0)
> return -1;
> + if (test_rte_basename() < 0)
> + return -1;
> return 0;
> }
>
> diff --git a/lib/eal/include/rte_string_fns.h b/lib/eal/include/rte_string_fns.h
> index 702bd81251..3713c94acb 100644
> --- a/lib/eal/include/rte_string_fns.h
> +++ b/lib/eal/include/rte_string_fns.h
> @@ -149,6 +149,38 @@ rte_str_skip_leading_spaces(const char *src)
> return p;
> }
>
> +/**
> + * @warning
> + * @b EXPERIMENTAL: this API may change without prior notice.
> + *
> + * Provides the final component of a path, similar to POSIX basename function.
> + *
> + * This API provides the similar behaviour on all platforms, Linux, BSD, Windows,
> + * hiding the implementation differences.
> + * - It does not modify the input path.
> + * - The output buffer is passed as an argument, and the result is copied into it.
> + * - Expected output is the last component of the path, or the path itself if
> + * it does not contain a directory separator.
> + * - If the final component is too long to fit in the output buffer, it will be truncated.
> + * - For empty or NULL input paths, output buffer will contain the string ".".
> + * - Supports up to PATH_MAX (BSD/Linux) or _MAX_PATH (Windows) characters in the input path.
> + *
> + * @param path
> + * The input path string. Not modified by this function.
> + * @param buf
> + * The buffer to hold the resultant basename.
> + * Must be large enough to hold the result, otherwise basename will be truncated.
> + * @param buflen
> + * The size of the buffer in bytes.
> + * @return
> + * The number of bytes that were written to buf (excluding the terminating '\0').
> + * If the return value is >= buflen, truncation occurred.
> + * Return (size_t)-1 on error (Windows only)
> + */
> +__rte_experimental
> +size_t
> +rte_basename(const char *path, char *buf, size_t buflen);
> +
> #ifdef __cplusplus
> }
> #endif
> diff --git a/lib/eal/unix/meson.build b/lib/eal/unix/meson.build
> index f1eb82e16a..70af352dab 100644
> --- a/lib/eal/unix/meson.build
> +++ b/lib/eal/unix/meson.build
> @@ -9,6 +9,7 @@ sources += files(
> 'eal_unix_memory.c',
> 'eal_unix_thread.c',
> 'eal_unix_timer.c',
> + 'rte_basename.c',
> 'rte_thread.c',
> )
>
> diff --git a/lib/eal/unix/rte_basename.c b/lib/eal/unix/rte_basename.c
> new file mode 100644
> index 0000000000..a72d6bb3c9
> --- /dev/null
> +++ b/lib/eal/unix/rte_basename.c
> @@ -0,0 +1,37 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Intel Corporation
> + */
> +
> +#include <string.h>
> +#include <stdlib.h>
> +#include <libgen.h>
> +#include <limits.h>
> +
> +#include <eal_export.h>
> +#include <rte_string_fns.h>
> +
> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_basename, 25.11)
> +size_t
> +rte_basename(const char *path, char *buf, size_t buflen)
> +{
> + char copy[PATH_MAX + 1];
> + size_t retval = 0;
> +
> + if (path == NULL)
> + return (buf == NULL) ? strlen(".") : strlcpy(buf, ".", buflen);
> +
> + /* basename is on the end, so if path is too long, use only last PATH_MAX bytes */
> + const size_t pathlen = strlen(path);
> + if (pathlen > PATH_MAX)
> + path = &path[pathlen - PATH_MAX];
> +
> + /* make a copy of buffer since basename may modify it */
> + strlcpy(copy, path, sizeof(copy));
> +
> + /* if passed a null buffer, just return length of basename, otherwise strlcpy it */
> + retval = (buf == NULL) ?
> + strlen(basename(copy)) :
> + strlcpy(buf, basename(copy), buflen);
> +
> + return retval;
> +}
> diff --git a/lib/eal/windows/meson.build b/lib/eal/windows/meson.build
> index c526ede405..e7fad1f010 100644
> --- a/lib/eal/windows/meson.build
> +++ b/lib/eal/windows/meson.build
> @@ -19,6 +19,7 @@ sources += files(
> 'eal_timer.c',
> 'getline.c',
> 'getopt.c',
> + 'rte_basename.c',
> 'rte_thread.c',
> )
>
> diff --git a/lib/eal/windows/rte_basename.c b/lib/eal/windows/rte_basename.c
> new file mode 100644
> index 0000000000..f4dfc08a0a
> --- /dev/null
> +++ b/lib/eal/windows/rte_basename.c
> @@ -0,0 +1,53 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Intel Corporation
> + */
> +
> +#include <string.h>
> +#include <stdlib.h>
> +#include <rte_string_fns.h>
> +
> +size_t
> +rte_basename(const char *path, char *buf, size_t buflen)
> +{
> + char fname[_MAX_FNAME + 1];
> + char ext[_MAX_EXT + 1];
> + char dir[_MAX_DIR + 1];
> +
> + if (path == NULL || path[0] == '\0')
> + return (buf == NULL) ? strlen(".") : strlcpy(buf, ".", buflen);
> +
> + /* basename is on the end, so if path is too long, use only last PATH_MAX bytes */
> + const size_t pathlen = strlen(path);
> + if (pathlen > _MAX_PATH)
> + path = &path[pathlen - _MAX_PATH];
> +
> +
> + /* Use _splitpath_s to separate the path into components */
> + int ret = _splitpath_s(path, NULL, 0, dir, sizeof(dir),
> + fname, sizeof(fname), ext, sizeof(ext));
> + if (ret != 0)
> + return (size_t)-1;
> +
> + /* if there is a trailing slash, then split_path returns no basename, but
> + * we want to return the last component of the path in all cases.
> + * Therefore re-run removing trailing slash from path.
> + */
> + if (fname[0] == '\0' && ext[0] == '\0') {
> + size_t dirlen = strlen(dir);
> + while (dirlen > 0 && (dir[dirlen - 1] == '\\' || dir[dirlen - 1] == '/')) {
> + /* special case for "/" to keep *nix compatibility */
> + if (strcmp(dir, "/") == 0)
> + return (buf == NULL) ? strlen(dir) : strlcpy(buf, dir, buflen);
> +
> + /* Remove trailing backslash */
> + dir[--dirlen] = '\0';
> + }
> + _splitpath_s(dir, NULL, 0, NULL, 0, fname, sizeof(fname), ext, sizeof(ext));
> + }
> +
> + if (buf == NULL)
> + return strlen(fname) + strlen(ext);
> +
> + /* Combine the filename and extension into output */
> + return snprintf(buf, buflen, "%s%s", fname, ext);
> +}
> --
> 2.48.1
Acked-by: Andre Muezerie <andremue@linux.microsoft.com>
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2025-08-01 14:25 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-07-04 14:05 [PATCH] app/testpmd: allow multiple cmdline-file parameters Bruce Richardson
2025-07-04 18:34 ` [PATCH v2 0/3] improve cmdline file handling in testpmd Bruce Richardson
2025-07-04 18:34 ` [PATCH v2 1/3] app/testpmd: explicitly set command echoing on file load Bruce Richardson
2025-07-04 18:34 ` [PATCH v2 2/3] app/testpmd: allow multiple commandline file parameters Bruce Richardson
2025-07-04 18:34 ` [PATCH v2 3/3] app/testpmd: improve output when processing cmdline files Bruce Richardson
2025-07-07 11:17 ` [PATCH v3 0/3] improve cmdline file handling in testpmd Bruce Richardson
2025-07-07 11:17 ` [PATCH v3 1/3] app/testpmd: explicitly set command echoing on file load Bruce Richardson
2025-07-07 11:17 ` [PATCH v3 2/3] app/testpmd: allow multiple commandline file parameters Bruce Richardson
2025-07-07 11:17 ` [PATCH v3 3/3] app/testpmd: improve output when processing cmdline files Bruce Richardson
2025-07-29 4:24 ` Stephen Hemminger
2025-07-31 16:00 ` [PATCH v4 0/4] improve cmdline file handling in testpmd Bruce Richardson
2025-07-31 16:00 ` [PATCH v4 1/4] eal: add basename function for common path manipulation Bruce Richardson
2025-08-01 14:25 ` Andre Muezerie
2025-07-31 16:00 ` [PATCH v4 2/4] app/testpmd: explicitly set command echoing on file load Bruce Richardson
2025-07-31 16:00 ` [PATCH v4 3/4] app/testpmd: allow multiple commandline file parameters Bruce Richardson
2025-07-31 16:00 ` [PATCH v4 4/4] app/testpmd: improve output when processing cmdline files Bruce Richardson
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).