From 99028e9f709ee44f7c730d2883e31e37adab4d57 Mon Sep 17 00:00:00 2001 From: Martijn Dekker Date: Mon, 23 Mar 2026 22:45:04 +0000 Subject: [PATCH] Fix bug in comsubs involving nested functions with redirects (re: e373e8c1, 274def65, 0ceb4864, 5def4398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vincent Mihalkovič (@vmihalko) from Red Hat reports: > When executing a function in a subshell: the output is lost when > a previous line in the function redirects to stderr while not > having any output. > > ### Minimal reproducer > > #!/usr/bin/ksh > function echo_devnull { > echo "DEVNULL" >/dev/null > } > function func { > echo_devnull >&2 > echo "FUNC" > } > OUT=$(func) > echo "OUT: ${OUT}" > > Expected: 'OUT: FUNC' Actual: 'OUT:' (empty) > > In 'OUT=$(func)', output from 'func' is lost when a prior line in > 'func' runs the customer pattern 'echo_devnull >&2' (redirect > that call’s stdout to stderr) and 'echo_devnull' itself sends > stdout to '/dev/null'. The following 'echo "FUNC"' should appear > in 'OUT' but 'OUT' is empty. bash is OK. src/cmd/ksh93/sh/io.c: sh_redirect(): - Amend the forking workaround yet again. Return to something closer to the original 93u+ workaround: fork if redirections persist past the command (flag==1 || flag==2) or standard output is on an Sfio string buffer. All regression tests pass, so this should not introduce the 93u+ bugs fixed in the meantime. Resolves: https://github.com/ksh93/ksh/issues/951 Upstream-commit: 99028e9f709ee44f7c730d2883e31e37adab4d57 Resolves: RHEL-155803 --- src/cmd/ksh93/sh/io.c | 4 +- src/cmd/ksh93/tests/subshell.sh | 67 +++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/cmd/ksh93/sh/io.c b/src/cmd/ksh93/sh/io.c index ea83132..d54c649 100644 --- a/src/cmd/ksh93/sh/io.c +++ b/src/cmd/ksh93/sh/io.c @@ -1143,10 +1143,8 @@ int sh_redirect(struct ionod *iop, int flag) * 'redirect'. This forking workaround is necessary to avoid that bug. * For shared-state comsubs, forking is incorrect, so error out then. * TODO: actually fix the bug and remove this workaround. - * (Note that sh.redir0 is set to 1 in xec.c immediately before processing - * redirections for any built-in command, including 'exec' and 'redirect'.) */ - if(sh.subshell && sh.comsub && sh.redir0==1) + if(sh.subshell && sh.comsub && (flag==1 || flag==2 || (sfset(sfstdout,0,0) & SFIO_STRING))) { struct ionod *i; for(i = iop; i; i = i->ionxt) diff --git a/src/cmd/ksh93/tests/subshell.sh b/src/cmd/ksh93/tests/subshell.sh index 7a91abf..1f896f1 100755 --- a/src/cmd/ksh93/tests/subshell.sh +++ b/src/cmd/ksh93/tests/subshell.sh @@ -1219,5 +1219,72 @@ got=$("$SHELL" -c 'x=$(fn(){ return 265; };echo ok|fn); echo exited $?' 2>&1) [[ e=$? -eq 0 && $got == "$exp" ]] || err_exit "regression involving SIGPIPE in subshell" \ "(expected status 0 and $(printf %q "$exp"), got status $e and $(printf %q "$got"))" +# ====== +# Command substitution loses stdout after nested function redirects stdout to /dev/null +# https://github.com/ksh93/ksh/issues/951 + +got=$( + function echo_devnull { + echo "DEVNULL" >/dev/null + } + function func { + echo_devnull >&2 + echo "FUNC" + } + OUT=$(func) + echo "OUT: ${OUT}" +) +exp='OUT: FUNC' +[[ $got == "$exp" ]] || err_exit "bug 951 test 1a (expected $(printf %q "$exp"), got $(printf %q "$got"))" + +got=$( + function echo_devnull { + echo "DEVNULL" >/dev/null + } + function func { + echo_devnull >&2 + echo "FUNC" + } + OUT=${ func; } + echo "OUT: ${OUT}" +) +exp='OUT: FUNC' +[[ $got == "$exp" ]] || err_exit "bug 951 test 1b (expected $(printf %q "$exp"), got $(printf %q "$got"))" + +got=$( + function echo_devnull { + ulimit -c 0 + } + function func { + echo_devnull >&2 + echo "FUNC" + } + OUT=$(func) + echo "OUT: ${OUT}" +) +exp='OUT: FUNC' +[[ $got == "$exp" ]] || err_exit "bug 951 test 2a (expected $(printf %q "$exp"), got $(printf %q "$got"))" + +got=$( + function echo_devnull { + ulimit -c 0 + } + function func { + echo_devnull >&2 + echo "FUNC" + } + OUT=${ func; } + echo "OUT: ${OUT}" +) +exp='OUT: FUNC' +[[ $got == "$exp" ]] || err_exit "bug 951 test 2b (expected $(printf %q "$exp"), got $(printf %q "$got"))" + +got=$( + OUT=$(ulimit -c 0 >&2; echo OK) + echo "OUT: ${OUT}" +) +exp='OUT: OK' +[[ $got == "$exp" ]] || err_exit "bug 951 test 3 (expected $(printf %q "$exp"), got $(printf %q "$got"))" + # ====== exit $((Errors<125?Errors:125)) -- 2.53.0