From 68123a8bb7696e8b6c2683ddf8cdf778ba1e2921 Mon Sep 17 00:00:00 2001 From: Andy Fiddaman Date: Wed, 30 Dec 2020 16:00:07 +0000 Subject: [PATCH] Pick an unused fd to capture subshell output https://github.com/att/ast/issues/198 --- src/cmd/ksh93/include/io.h | 1 + src/cmd/ksh93/sh/io.c | 18 ++++++++++++ src/cmd/ksh93/sh/subshell.c | 2 +- src/cmd/ksh93/tests/subshell.sh | 49 +++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/cmd/ksh93/include/io.h b/src/cmd/ksh93/include/io.h index 226e961bb6b1..076cd57cf3d5 100644 --- a/src/cmd/ksh93/include/io.h +++ b/src/cmd/ksh93/include/io.h @@ -81,6 +81,7 @@ extern void sh_iorestore(Shell_t*,int,int); extern Sfio_t *sh_iostream(Shell_t*,int); extern int sh_redirect(Shell_t*,struct ionod*,int); extern void sh_iosave(Shell_t *, int,int,char*); +extern int sh_get_unused_fd(Shell_t* shp, int min_fd); extern int sh_iovalidfd(Shell_t*, int); extern int sh_inuse(Shell_t*, int); extern void sh_iounsave(Shell_t*); diff --git a/src/cmd/ksh93/sh/io.c b/src/cmd/ksh93/sh/io.c index 3618c9d07707..08a75dec19bf 100644 --- a/src/cmd/ksh93/sh/io.c +++ b/src/cmd/ksh93/sh/io.c @@ -2764,3 +2764,21 @@ int sh_chdir(const char* dir) return(r); } +// Return the lowest numbered fd that is equal to or greater than the requested +// `min_fd` and which is not currently in use. +int sh_get_unused_fd(Shell_t* shp, int min_fd) { + int fd; + + while (1) { + if (fcntl(min_fd, F_GETFD) == -1) { + for(fd = 0; fd < shp->topfd; fd++) { + if (filemap[fd].save_fd == min_fd || filemap[fd].orig_fd == min_fd) break; + } + if (fd == shp->topfd) break; + } + min_fd++; + } + + return min_fd; +} + diff --git a/src/cmd/ksh93/sh/subshell.c b/src/cmd/ksh93/sh/subshell.c index 17a0b9509892..7d59ad0b68de 100644 --- a/src/cmd/ksh93/sh/subshell.c +++ b/src/cmd/ksh93/sh/subshell.c @@ -678,7 +678,7 @@ Sfio_t *sh_subshell(Shell_t *shp,Shnode_t *t, volatile int flags, int comsub) } if(iop && sffileno(iop)==1) { - int fd=sfsetfd(iop,3); + int fd=sfsetfd(iop,sh_get_unused_fd(shp, 3)); if(fd<0) { shp->toomany = 1; diff --git a/src/cmd/ksh93/tests/subshell.sh b/src/cmd/ksh93/tests/subshell.sh index a6d7475b5017..6cbff7910553 100755 --- a/src/cmd/ksh93/tests/subshell.sh +++ b/src/cmd/ksh93/tests/subshell.sh @@ -617,4 +617,53 @@ do if [[ -e $f ]] fi done +# ======================================== +# Test that closing file descriptors don't affect capturing the output of a +# subshell. Regression test for issue #198. +tmpfile=$(mktemp) +expected='return value' + +function get_value { + case=$1 + (( case >= 1 )) && exec 3< $tmpfile + (( case >= 2 )) && exec 4< $tmpfile + (( case >= 3 )) && exec 6< $tmpfile + + # To trigger the bug we have to spawn an external command. Why is a + # mystery but not really relevant. + $(whence -p true) + + (( case >= 1 )) && exec 3<&- + (( case >= 2 )) && exec 4<&- + (( case >= 3 )) && exec 6<&- + + print $expected +} + +actual=$(get_value 0) +if [[ $actual != $expected ]] +then + err_exit -u2 "failed to capture subshell output when closing fd: case 0" +fi + +actual=$(get_value 1) +if [[ $actual != $expected ]] +then + err_exit -u2 "failed to capture subshell output when closing fd: case 1" +fi + +actual=$(get_value 2) +if [[ $actual != $expected ]] +then + err_exit -u2 "failed to capture subshell output when closing fd: case 2" +fi + +actual=$(get_value 3) +if [[ $actual != $expected ]] +then + err_exit -u2 "failed to capture subshell output when closing fd: case 3" +fi + +rm $tmpfile + exit $((Errors<125?Errors:125))