From 35f3690a4c46ed8e81312d7211652bd5358a57c9 Mon Sep 17 00:00:00 2001 From: fangpeina Date: Tue, 16 Dec 2025 23:09:50 +0800 Subject: [PATCH] nshlib: Add stderr redirection support This commit implements stderr redirection in HSN, support Bash-like syntax for redirecting standard error output. Support both foreground and background commands. example: - nsh> ls noexists 2> err.log - nsh> ls noexists 2> err.log & - nsh> ls noexists 2>> err.log - nsh> sh < /dev/ttyS0 > /dev/ttyS0 2> /dev/ttyS1 & - nsh> sh < /dev/ttyS0 > /dev/ttyS0 2>&1 & Signed-off-by: fangpeina --- include/nshlib/nshlib.h | 11 ++-- nshlib/nsh.h | 1 + nshlib/nsh_console.c | 5 +- nshlib/nsh_console.h | 20 +++---- nshlib/nsh_fileapps.c | 33 +++++++++++ nshlib/nsh_parse.c | 119 ++++++++++++++++++++++++++++++++++++++-- nshlib/nsh_script.c | 2 +- 7 files changed, 168 insertions(+), 23 deletions(-) diff --git a/include/nshlib/nshlib.h b/include/nshlib/nshlib.h index f9565894a..f2153fed0 100644 --- a/include/nshlib/nshlib.h +++ b/include/nshlib/nshlib.h @@ -69,21 +69,24 @@ struct nsh_param_s { - /* Redirect input/output through `fd` OR `path_name` + /* Redirect input/output/error through `fd` OR `path_name` * * Select one: - * 1. Using fd_in/fd_out as oldfd for dup2() if greater than -1. - * 2. Using file_in/file_out as full path to the file if it is - * not NULL, and oflags_in/oflags_out as flags for open(). + * 1. Using fd_in/fd_out/fd_err as oldfd for dup2() if greater than -1. + * 2. Using file_in/file_out/file_err as full path to the file if it is + * not NULL, and oflags_in/oflags_out/oflags_err as flags for open(). */ int fd_in; int fd_out; + int fd_err; int oflags_in; int oflags_out; + int oflags_err; FAR const char *file_in; FAR const char *file_out; + FAR const char *file_err; }; /**************************************************************************** diff --git a/nshlib/nsh.h b/nshlib/nsh.h index 74f4032cc..8ea334d07 100644 --- a/nshlib/nsh.h +++ b/nshlib/nsh.h @@ -655,6 +655,7 @@ struct nsh_parser_s #endif bool np_redir_out; /* true: Output from the last command was re-directed */ bool np_redir_in; /* true: Input from the last command was re-directed */ + bool np_redir_err; /* true: Error from the last command was re-directed */ bool np_fail; /* true: The last command failed */ pid_t np_lastpid; /* Pid of the last command executed */ #ifdef NSH_HAVE_VARS diff --git a/nshlib/nsh_console.c b/nshlib/nsh_console.c index 2d8c315ad..cb95e0342 100644 --- a/nshlib/nsh_console.c +++ b/nshlib/nsh_console.c @@ -76,7 +76,7 @@ static int nsh_erroroutput(FAR struct nsh_vtbl_s *vtbl, #endif static FAR char *nsh_consolelinebuffer(FAR struct nsh_vtbl_s *vtbl); static void nsh_consoleredirect(FAR struct nsh_vtbl_s *vtbl, int fd_in, - int fd_out, FAR uint8_t *save); + int fd_out, int fd_err, FAR uint8_t *save); static void nsh_consoleundirect(FAR struct nsh_vtbl_s *vtbl, FAR uint8_t *save); static void nsh_consoleexit(FAR struct nsh_vtbl_s *vtbl, @@ -332,7 +332,7 @@ static void nsh_consolerelease(FAR struct nsh_vtbl_s *vtbl) ****************************************************************************/ static void nsh_consoleredirect(FAR struct nsh_vtbl_s *vtbl, int fd_in, - int fd_out, FAR uint8_t *save) + int fd_out, int fd_err, FAR uint8_t *save) { FAR struct console_stdio_s *pstate = (FAR struct console_stdio_s *)vtbl; FAR struct serialsave_s *ssave = (FAR struct serialsave_s *)save; @@ -354,6 +354,7 @@ static void nsh_consoleredirect(FAR struct nsh_vtbl_s *vtbl, int fd_in, OUTFD(pstate) = fd_out; INFD(pstate) = fd_in; + ERRFD(pstate) = fd_err; } /**************************************************************************** diff --git a/nshlib/nsh_console.h b/nshlib/nsh_console.h index 0b94a2b6b..268d03379 100644 --- a/nshlib/nsh_console.h +++ b/nshlib/nsh_console.h @@ -43,15 +43,15 @@ /* Method access macros */ -#define nsh_clone(v) (v)->clone(v) -#define nsh_release(v) (v)->release(v) -#define nsh_write(v,b,n) (v)->write(v,b,n) -#define nsh_read(v,b,n) (v)->read(v,b,n) -#define nsh_ioctl(v,c,a) (v)->ioctl(v,c,a) -#define nsh_linebuffer(v) (v)->linebuffer(v) -#define nsh_redirect(v,fi,fo,s) (v)->redirect(v,fi,fo,s) -#define nsh_undirect(v,s) (v)->undirect(v,s) -#define nsh_exit(v,s) (v)->exit(v,s) +#define nsh_clone(v) (v)->clone(v) +#define nsh_release(v) (v)->release(v) +#define nsh_write(v,b,n) (v)->write(v,b,n) +#define nsh_read(v,b,n) (v)->read(v,b,n) +#define nsh_ioctl(v,c,a) (v)->ioctl(v,c,a) +#define nsh_linebuffer(v) (v)->linebuffer(v) +#define nsh_redirect(v,fi,fo,fe,s) (v)->redirect(v,fi,fo,fe,s) +#define nsh_undirect(v,s) (v)->undirect(v,s) +#define nsh_exit(v,s) (v)->exit(v,s) #ifdef CONFIG_CPP_HAVE_VARARGS # define nsh_error(v, ...) (v)->error(v, ##__VA_ARGS__) @@ -127,7 +127,7 @@ struct nsh_vtbl_s printf_like(2, 3); FAR char *(*linebuffer)(FAR struct nsh_vtbl_s *vtbl); void (*redirect)(FAR struct nsh_vtbl_s *vtbl, int fd_in, int fd_out, - FAR uint8_t *save); + int fd_err, FAR uint8_t *save); void (*undirect)(FAR struct nsh_vtbl_s *vtbl, FAR uint8_t *save); void (*exit)(FAR struct nsh_vtbl_s *vtbl, int status) noreturn_function; diff --git a/nshlib/nsh_fileapps.c b/nshlib/nsh_fileapps.c index 54efd6de2..66f41e36f 100644 --- a/nshlib/nsh_fileapps.c +++ b/nshlib/nsh_fileapps.c @@ -177,6 +177,39 @@ int nsh_fileapp(FAR struct nsh_vtbl_s *vtbl, FAR const char *cmd, } } #endif + + /* Handle redirection of error output */ + + if (param->file_err) + { + /* 2> file: Redirect stderr to a file */ + + ret = posix_spawn_file_actions_addopen(&file_actions, 2, + param->file_err, + param->oflags_err, + 0644); + if (ret != 0) + { + nsh_error(vtbl, g_fmtcmdfailed, cmd, + "posix_spawn_file_actions_addopen", + NSH_ERRNO); + goto errout_with_attrs; + } + } + else if (param->fd_err != -1) + { + /* 2>&1: Redirect stderr to stdout */ + + ret = posix_spawn_file_actions_adddup2(&file_actions, + param->fd_err, 2); + if (ret != 0) + { + nsh_error(vtbl, g_fmtcmdfailed, cmd, + "posix_spawn_file_actions_adddup2", + NSH_ERRNO); + goto errout_with_attrs; + } + } } #ifdef CONFIG_BUILTIN diff --git a/nshlib/nsh_parse.c b/nshlib/nsh_parse.c index 9b1bde875..9a67cc845 100644 --- a/nshlib/nsh_parse.c +++ b/nshlib/nsh_parse.c @@ -264,6 +264,12 @@ static const char g_redirect_out2[] = ">>"; static const size_t g_redirect_out2_len = sizeof(g_redirect_out2) - 1; static const char g_redirect_in1[] = "<"; static const size_t g_redirect_in1_len = sizeof(g_redirect_in1) - 1; +static const char g_redirect_err1[] = "2>"; +static const size_t g_redirect_err1_len = sizeof(g_redirect_err1) - 1; +static const char g_redirect_err2[] = "2>>"; +static const size_t g_redirect_err2_len = sizeof(g_redirect_err2) - 1; +static const char g_redirect_err3[] = "2>&1"; +static const size_t g_redirect_err3_len = sizeof(g_redirect_err3) - 1; #ifdef CONFIG_NSH_PIPELINE static const char g_pipeline1[] = "|"; static const size_t g_pipeline1_len = sizeof(g_pipeline1) - 1; @@ -503,6 +509,7 @@ static int nsh_execute(FAR struct nsh_vtbl_s *vtbl, FAR const struct nsh_param_s *param) { int fd_out = STDOUT_FILENO; + int fd_err = STDERR_FILENO; int fd_in = STDIN_FILENO; int ret; @@ -703,11 +710,36 @@ static int nsh_execute(FAR struct nsh_vtbl_s *vtbl, } } - /* Handle redirection of stdin/stdout file descriptor */ + /* Redirected error output? */ - if (vtbl->np.np_redir_out || vtbl->np.np_redir_in) + if (vtbl->np.np_redir_err) { - nsh_redirect(vtbl, fd_in, fd_out, save); + if (param->file_err) + { + /* 2> file: Open the redirection file for stderr */ + + fd_err = open(param->file_err, param->oflags_err, 0666); + if (fd_err < 0) + { + nsh_error(vtbl, g_fmtcmdfailed, argv[0], "open", + NSH_ERRNO); + return nsh_saveresult(vtbl, true); + } + } + else + { + /* 2>&1: redirect stderr to current stdout fd */ + + fd_err = fd_out; + } + } + + /* Handle redirection of stdin/stdout/stderr file descriptor */ + + if (vtbl->np.np_redir_out || vtbl->np.np_redir_in || + vtbl->np.np_redir_err) + { + nsh_redirect(vtbl, fd_in, fd_out, fd_err, save); } /* Then execute the command in "foreground" -- i.e., while the user @@ -723,7 +755,8 @@ static int nsh_execute(FAR struct nsh_vtbl_s *vtbl, * file descriptor. */ - if (vtbl->np.np_redir_out || vtbl->np.np_redir_in) + if (vtbl->np.np_redir_out || vtbl->np.np_redir_in || + vtbl->np.np_redir_err) { nsh_undirect(vtbl, save); fd_out = -1; @@ -885,10 +918,13 @@ static FAR char *nsh_cmdparm(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline, { .fd_in = -1, .fd_out = -1, + .fd_err = -1, .oflags_in = 0, .oflags_out = O_WRONLY | O_CREAT | O_TRUNC, + .oflags_err = O_WRONLY | O_CREAT | O_TRUNC, .file_in = NULL, - .file_out = NULL + .file_out = NULL, + .file_err = NULL }; FAR char *tmpfile; @@ -2447,10 +2483,13 @@ static int nsh_parse_command(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline) { .fd_in = -1, .fd_out = -1, + .fd_err = -1, .oflags_in = 0, .oflags_out = 0, + .oflags_err = 0, .file_in = NULL, - .file_out = NULL + .file_out = NULL, + .file_err = NULL }; #ifdef CONFIG_NSH_PIPELINE @@ -2469,6 +2508,7 @@ static int nsh_parse_command(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline) int ret; bool redirect_out_save = false; bool redirect_in_save = false; + bool redirect_err_save = false; #ifdef CONFIG_NSH_PIPELINE bool bg_save = false; #endif @@ -2492,6 +2532,7 @@ static int nsh_parse_command(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline) vtbl->np.np_redir_out = false; vtbl->np.np_redir_in = false; + vtbl->np.np_redir_err = false; /* Parse out the command at the beginning of the line */ @@ -2692,6 +2733,60 @@ static int nsh_parse_command(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline) param.oflags_in = O_RDONLY; param.file_in = nsh_getfullpath(vtbl, arg); } + else if (!strncmp(argv[argc], g_redirect_err3, g_redirect_err3_len)) + { + redirect_err_save = vtbl->np.np_redir_err; + vtbl->np.np_redir_err = true; + param.fd_err = STDOUT_FILENO; + } + else if (!strncmp(argv[argc], g_redirect_err2, g_redirect_err2_len)) + { + FAR char *arg; + if (argv[argc][g_redirect_err2_len]) + { + arg = &argv[argc][g_redirect_err2_len]; + } + else + { + arg = nsh_argument(vtbl, &saveptr, &memlist, NULL, &isenvvar); + } + + if (!arg) + { + nsh_error(vtbl, g_fmtarginvalid, cmd); + ret = ERROR; + goto dynlist_free; + } + + redirect_err_save = vtbl->np.np_redir_err; + vtbl->np.np_redir_err = true; + param.oflags_err = O_WRONLY | O_CREAT | O_APPEND; + param.file_err = nsh_getfullpath(vtbl, arg); + } + else if (!strncmp(argv[argc], g_redirect_err1, g_redirect_err1_len)) + { + FAR char *arg; + if (argv[argc][g_redirect_err1_len]) + { + arg = &argv[argc][g_redirect_err1_len]; + } + else + { + arg = nsh_argument(vtbl, &saveptr, &memlist, NULL, &isenvvar); + } + + if (!arg) + { + nsh_error(vtbl, g_fmtarginvalid, cmd); + ret = ERROR; + goto dynlist_free; + } + + redirect_err_save = vtbl->np.np_redir_err; + vtbl->np.np_redir_err = true; + param.oflags_err = O_WRONLY | O_CREAT | O_TRUNC; + param.file_err = nsh_getfullpath(vtbl, arg); + } #ifdef CONFIG_NSH_PIPELINE else if (!strncmp(argv[argc], g_pipeline1, g_pipeline1_len)) { @@ -2848,6 +2943,18 @@ dynlist_free: } #endif + /* Free the redirected error file path and restore state */ + + if (param.file_err) + { + nsh_freefullpath((char *)param.file_err); + } + + if (vtbl->np.np_redir_err) + { + vtbl->np.np_redir_err = redirect_err_save; + } + NSH_ALIASLIST_FREE(vtbl, &alist); NSH_MEMLIST_FREE(&memlist); #ifdef CONFIG_SCHED_INSTRUMENTATION_DUMP diff --git a/nshlib/nsh_script.c b/nshlib/nsh_script.c index a0db4d6d0..21bef19e4 100644 --- a/nshlib/nsh_script.c +++ b/nshlib/nsh_script.c @@ -64,7 +64,7 @@ static int nsh_script_redirect(FAR struct nsh_vtbl_s *vtbl, fd = open(CONFIG_NSH_SCRIPT_REDIRECT_PATH, 0666); if (fd > 0) { - nsh_redirect(vtbl, 0, fd, save); + nsh_redirect(vtbl, 0, fd, fd, save); } }