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 <fangpeina@xiaomi.com>
This commit is contained in:
fangpeina 2025-12-16 23:09:50 +08:00 committed by Xiang Xiao
parent 80c2b43197
commit 35f3690a4c
7 changed files with 168 additions and 23 deletions

View file

@ -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;
};
/****************************************************************************

View file

@ -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

View file

@ -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;
}
/****************************************************************************

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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);
}
}