diff --git a/NEWS b/NEWS index 29ebe9c54..af9e39b3b 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,14 @@ For full details, see the git log at: https://github.com/ksh93/ksh Any uppercase BUG_* names are modernish shell bug IDs. +2021-03-11: + +- Fixed an intermittent bug that caused process substitutions to infinitely + loop in Linux virtual machines that use systemd. + +- Fixed a bug that caused process substitutions to leave lingering processes + if the command invoking them never reads from them. + 2021-03-09: - The ${!foo@} and ${!foo*} expansions yield variable names beginning with foo, diff --git a/src/cmd/ksh93/include/defs.h b/src/cmd/ksh93/include/defs.h index 41b1729d1..10941a6be 100644 --- a/src/cmd/ksh93/include/defs.h +++ b/src/cmd/ksh93/include/defs.h @@ -158,7 +158,7 @@ struct shared Shwait_f waitevent; }; -#define _SH_PRIVATE \ +#define __SH_PRIVATE_1 \ struct shared *gd; /* global data */ \ struct sh_scoped st; /* scoped information */ \ Stk_t *stk; /* stack pointer */ \ @@ -189,7 +189,6 @@ struct shared char *comdiv; /* points to sh -c argument */ \ char *prefix; /* prefix for compound assignment */ \ sigjmp_buf *jmplist; /* longjmp return stack */ \ - char *fifo; /* fifo name for process sub */ \ pid_t bckpid; /* background process id */ \ pid_t cpid; \ pid_t spid; /* subshell process id */ \ @@ -282,6 +281,14 @@ struct shared char exittrap; \ char errtrap; \ char end_fn; +#if !SHOPT_DEVFD +#define __SH_PRIVATE_2 \ + char *fifo; /* FIFO name for current process substitution */ \ + Dt_t *fifo_tree; /* for cleaning up process substitution FIFOs */ +#else +#define __SH_PRIVATE_2 +#endif +#define _SH_PRIVATE __SH_PRIVATE_1 __SH_PRIVATE_2 #include diff --git a/src/cmd/ksh93/include/version.h b/src/cmd/ksh93/include/version.h index 175363f3e..c53abee89 100644 --- a/src/cmd/ksh93/include/version.h +++ b/src/cmd/ksh93/include/version.h @@ -20,7 +20,7 @@ #define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */ #define SH_RELEASE_SVER "1.0.0-alpha" /* semantic version number: https://semver.org */ -#define SH_RELEASE_DATE "2021-03-09" /* must be in this format for $((.sh.version)) */ +#define SH_RELEASE_DATE "2021-03-11" /* must be in this format for $((.sh.version)) */ #define SH_RELEASE_CPYR "(c) 2020-2021 Contributors to ksh " SH_RELEASE_FORK /* Scripts sometimes field-split ${.sh.version}, so don't change amount of whitespace. */ diff --git a/src/cmd/ksh93/sh/args.c b/src/cmd/ksh93/sh/args.c index da193eacf..f9920bc70 100644 --- a/src/cmd/ksh93/sh/args.c +++ b/src/cmd/ksh93/sh/args.c @@ -761,6 +761,10 @@ struct argnod *sh_argprocsub(Shell_t *shp,struct argnod *argp) close(pv[1-fd]); sh_iosave(shp,-pv[fd], shp->topfd, (char*)0); #else + /* remember the FIFO for cleanup in case the command never opens it (see fifo_cleanup(), xec.c) */ + if(!shp->fifo_tree) + shp->fifo_tree = dtopen(&_Nvdisc,Dtoset); + nv_search(shp->fifo,shp->fifo_tree,NV_ADD); free(shp->fifo); shp->fifo = 0; #endif /* SHOPT_DEVFD */ diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index c116e3419..850307d82 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -82,16 +82,34 @@ struct funenv /* ======== command execution ========*/ #if !SHOPT_DEVFD + static pid_t fifo_save_ppid; + static void fifo_check(void *handle) { Shell_t *shp = (Shell_t*)handle; - pid_t pid = getppid(); - if(pid==1) + if(getppid() != fifo_save_ppid) { unlink(shp->fifo); sh_done(shp,0); } } + + /* Remove any remaining FIFOs to stop unused process substitutions blocking on trying to open the FIFO */ + static void fifo_cleanup(void) + { + if(sh.fifo_tree) + { + Namval_t *fifo = dtfirst(sh.fifo_tree); + if(fifo) + { + do + unlink(fifo->nvname); + while(fifo = dtnext(sh.fifo_tree,fifo)); + dtclose(sh.fifo_tree); + sh.fifo_tree = NIL(Dt_t*); + } + } + } #endif /* !SHOPT_DEVFD */ #if _lib_getrusage @@ -1371,6 +1389,9 @@ int sh_exec(register const Shnode_t *t, int flags) /* failure on special built-ins fatal */ if(jmpval<=SH_JMPCMD && (!nv_isattr(np,BLT_SPC) || command)) jmpval=0; +#if !SHOPT_DEVFD + fifo_cleanup(); +#endif } if(bp) { @@ -1533,6 +1554,9 @@ int sh_exec(register const Shnode_t *t, int flags) else if(!io) { setexit: +#if !SHOPT_DEVFD + fifo_cleanup(); +#endif exitset(); break; } @@ -1591,6 +1615,10 @@ int sh_exec(register const Shnode_t *t, int flags) pipes[2] = 0; coproc_init(shp,pipes); } +#if !SHOPT_DEVFD + if(shp->fifo) + fifo_save_ppid = shgd->current_pid; +#endif #if SHOPT_SPAWN # ifdef _lib_fork if(com && !job.jobcontrol) @@ -1678,6 +1706,14 @@ int sh_exec(register const Shnode_t *t, int flags) struct checkpt *buffp = (struct checkpt*)stkalloc(shp->stk,sizeof(struct checkpt)); struct ionod *iop; int rewrite=0; +#if !SHOPT_DEVFD + if(shp->fifo_tree) + { + /* do not clean up process substitution FIFOs in child; parent handles this */ + dtclose(shp->fifo_tree); + shp->fifo_tree = NIL(Dt_t*); + } +#endif if(no_fork) sh_sigreset(2); sh_pushcontext(shp,buffp,SH_JMPEXIT); @@ -1704,16 +1740,24 @@ int sh_exec(register const Shnode_t *t, int flags) #if !SHOPT_DEVFD if(shp->fifo && (type&(FPIN|FPOU))) { - int fn,fd = (type&FPIN)?0:1; - void *fifo_timer=sh_timeradd(500,1,fifo_check,(void*)shp); + int fn, fd, save_errno; + void *fifo_timer = sh_timeradd(50,1,fifo_check,(void*)shp); + fd = (type&FPIN) ? 0 : 1; fn = sh_open(shp->fifo,fd?O_WRONLY:O_RDONLY); + save_errno = errno; timerdel(fifo_timer); - sh_iorenumber(shp,fn,fd); - sh_close(fn); - sh_delay(.001,0); unlink(shp->fifo); free(shp->fifo); shp->fifo = 0; + if(fn<0) + { + if((errno = save_errno) != ENOENT) + errormsg(SH_DICT, ERROR_SYSTEM|ERROR_PANIC, + "process substitution: FIFO open failed"); + sh_done(shp,0); + } + sh_iorenumber(shp,fn,fd); + sh_close(fn); type &= ~(FPIN|FPOU); } #endif /* !SHOPT_DEVFD */ @@ -3229,6 +3273,10 @@ static void sh_funct(Shell_t *shp,Namval_t *np,int argn, char *argv[],struct arg char *fname = nv_getval(SH_FUNNAMENOD); struct Level *lp =(struct Level*)(SH_LEVELNOD->nvfun); int level, pipepid=shp->pipepid; +#if !SHOPT_DEVFD + Dt_t *save_fifo_tree = shp->fifo_tree; + shp->fifo_tree = NIL(Dt_t*); +#endif shp->pipepid = 0; sh_stats(STAT_FUNCT); if(!lp->hdr.disc) @@ -3283,6 +3331,10 @@ static void sh_funct(Shell_t *shp,Namval_t *np,int argn, char *argv[],struct arg _nv_unset(np, NV_RDONLY); } } +#if !SHOPT_DEVFD + fifo_cleanup(); + shp->fifo_tree = save_fifo_tree; +#endif } /* diff --git a/src/cmd/ksh93/tests/io.sh b/src/cmd/ksh93/tests/io.sh index ace60fe04..67a3ce8e6 100755 --- a/src/cmd/ksh93/tests/io.sh +++ b/src/cmd/ksh93/tests/io.sh @@ -715,5 +715,36 @@ got=$(export tmp; "$SHELL" -ec \ (redirect {v}>$tmp/v.out; echo ok2 >&$v) 2>/dev/null [[ -r $tmp/v.out && $(<$tmp/v.out) == ok2 ]] || err_exit 'redirect {varname}>file not working in a subshell' +# ====== +# Test for looping or lingering process substitution processes +# https://github.com/ksh93/ksh/issues/213 +procsub_pid=$( + ulimit -t unlimited 2>/dev/null # fork the subshell + true >(true) <(true) >(true) <(true) + echo "$!" +) +sleep .1 +if kill -0 "$procsub_pid" 2>/dev/null; then + kill -TERM "$procsub_pid" # don't leave around what is effectively a zombie process + err_exit "process substitutions loop or linger after parent shell finishes" +fi +(true <(true) >(true) <(true) >(true); wait) & +sleep .1 +if kill -0 $! 2> /dev/null; then + kill -TERM $! + err_exit "process substitutions linger when unused" +fi + +# process substitutions should work correctly with delays +procsub_delay() +{ + sleep .1 # a delay >50ms, the current fifo_check delay in xec.c + cat "$@" +} +exp=$'hi\nthere\nworld' +got=$(procsub_delay <(echo hi) <(echo there) <(echo world)) +[[ $got == "$exp" ]] || err_exit "process substitutions passed to function failed" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + # ====== exit $((Errors<125?Errors:125))