-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Process substitution as file name to redirection is not compiled by shcomp #165
Comments
So far I've only been able to reproduce this bug if the process substitution is combined with a redirection. This makes me wonder if the bug is similar to #2. |
From what I've been able to debug, the bug/omission is probably in one of these functions: Lines 157 to 227 in 56b530c
When Lines 222 to 223 in 56b530c
Lines 184 to 185 in 56b530c
Afaict shcomp does handle process substitutions correctly, unless one is combined with a redirection. In the bugged scenario, the p_arg call in the else if block is never reached. One idea I had for fixing this issue is to add a check for this scenario, but that alone doesn't fix the issue (it just causes a memory fault):
--- a/src/cmd/ksh93/sh/tdump.c
+++ b/src/cmd/ksh93/sh/tdump.c
@@ -219,7 +219,7 @@ static int p_comarg(register const struct comnod *com)
p_arg(com->comset);
if(!com->comarg)
sfputl(outfile,-1);
- else if(com->comtyp&COMSCAN)
+ else if((com->comtyp&COMSCAN) || (com->comio && (com->comio->iofile & IOPROCSUB)))
p_arg(com->comarg);
else
p_comlist((struct dolnod*)com->comarg); |
Some more information relevant to this bug: when Lines 197 to 201 in 6b9703f
When a normal file is passed to a redirection, iop->ioname points to the name of the file. For process substitutions iop->ioname is a newline:
$ cat ./hosts.sh
cat < /etc/hosts
$ hexdump -C <(shcomp ./hosts.sh)
00000000 0b 13 08 00 04 00 00 c0 00 0b 2f 65 74 63 2f 68 |........../etc/h|
00000010 6f 73 74 73 00 40 00 01 04 63 61 74 00 01 |osts.@...cat..|
0000001e
$ cat ./procsub.sh
cat < <(echo foo)
$ hexdump -C <(shcomp ./procsub.sh)
00000000 0b 13 08 00 04 00 00 a0 80 00 03 0a 02 00 40 00 |..............@.|
00000010 01 04 63 61 74 00 01 |..cat..|
00000017 |
I've studied the code in io.c for executing process substitutions with redirections: Lines 1176 to 1187 in 6b9703f
So, Based on that code, I've come up with the following provisional patch. It needs an extra hack to avoid the segfault in Note that the --- a/src/cmd/ksh93/sh/tdump.c
+++ b/src/cmd/ksh93/sh/tdump.c
@@ -219,6 +219,16 @@ static int p_comarg(register const struct comnod *com)
p_arg(com->comset);
if(!com->comarg)
sfputl(outfile,-1);
+ else if(com->comio && (com->comio->iofile & IOPROCSUB))
+ {
+ struct argnod *ap = (struct argnod*)stakalloc(ARGVAL+strlen(com->comio->ioname));
+ memset(ap, 0, ARGVAL);
+ if(com->comio->iofile & IOPUT)
+ ap->argflag = ARG_RAW;
+ ap->argchn.ap = (struct argnod*)com->comio->ioname;
+ ((struct fornod*)ap->argchn.ap)->fornam = Empty; /* avoid segfault when calling strlen() in p_arg */
+ p_arg(ap);
+ }
else if(com->comtyp&COMSCAN)
p_arg(com->comarg);
else This test script now compiles like this:
Backtrace of the crash:
So it crashes here, in Line 1224 in 6b9703f
That's as far as I got for now, I hope this is some progress. |
Implementing some handling for --- a/src/cmd/ksh93/sh/tdump.c
+++ b/src/cmd/ksh93/sh/tdump.c
@@ -197,6 +197,16 @@ static int p_redirect(register const struct ionod *iop)
else
sfputl(outfile,iop->iofile);
p_string(iop->ioname);
+ if(iop->iofile & IOPROCSUB)
+ {
+ struct argnod *ap = (struct argnod*)stakalloc(ARGVAL+strlen(iop->ioname));
+ memset(ap, 0, ARGVAL);
+ if(iop->iofile & IOPUT)
+ ap->argflag = ARG_RAW;
+ ap->argchn.ap = (struct argnod*)iop->ioname;
+ ((struct fornod*)ap->argchn.ap)->fornam = Empty; /* avoid segfault when calling strlen() in p_arg */
+ p_arg(ap);
+ }
if(iop->iodelim)
{
p_string(iop->iodelim);
--- a/src/cmd/ksh93/sh/trestore.c
+++ b/src/cmd/ksh93/sh/trestore.c
@@ -225,6 +225,16 @@ static struct ionod *r_redirect(Shell_t* shp)
iopold->ionxt = iop;
iop->iofile = l;
iop->ioname = r_string(shp->stk);
+ if(iop->iofile & IOPROCSUB)
+ {
+ struct argnod *ap = (struct argnod*)stkalloc(shp->stk,ARGVAL+strlen(iop->ioname));
+ memset(ap, 0, ARGVAL);
+ if(iop->iofile & IOPUT)
+ ap->argflag = ARG_RAW;
+ ap->argchn.ap = (struct argnod*)iop->ioname;
+ ((struct fornod*)ap->argchn.ap)->fornam = Empty;
+ r_arg(shp);
+ }
if(iop->iodelim = r_string(shp->stk))
{
iop->iosize = sfgetl(infile); $ cat ./procsub.sh
echo line 1
cat < <(echo line 2)
echo line 3
$ arch/*/bin/ksh <(arch/*/bin/shcomp ./procsub.sh)
line 1
line 3
$ hexdump -C <(arch/*/bin/shcomp ./procsub.sh)
00000000 0b 13 08 00 04 00 00 40 00 03 05 65 63 68 6f 05 |.......@...echo.|
00000010 6c 69 6e 65 02 31 00 01 00 a0 80 00 03 0a 02 02 |line.1..........|
00000020 00 00 84 0a 00 40 00 03 05 65 63 68 6f 05 6c 69 |.....@...echo.li|
00000030 6e 65 02 32 00 02 00 00 40 00 01 04 63 61 74 00 |ne.2....@...cat.|
00000040 02 00 40 00 03 05 65 63 68 6f 05 6c 69 6e 65 02 |..@...echo.line.|
00000050 33 00 03 |3..|
00000053 |
This bug was first reported at att#9. The <>; operator doesn't work correctly if it's used as the last command of a -c script. Reproducer: $ echo test > a; ksh -c 'echo x 1<>; a'; cat a x st This bug is caused by ksh running the last command of -c scripts with execve(2) instead of posix_spawn(3) or fork(2). The <>; operator is noted by the man page as being incompatible with the exec builtin (see also the ksh93u+ man page), so it's not surprising this bug occurs when ksh runs a command using execve: > <>;word cannot be used with the exec and redirect built-ins. The ksh2020 fix simply removed the code required for ksh to use this optimization at all. It's not a performance friendly fix and only papers over the bug, so this commit provides a better fix. src/cmd/ksh93/sh/xec.c: - The IOREWRITE flag is set when handling the <>; operator, so to fix this bug avoid exec'ing the last command if it uses <>;. See also commit 17ebfbf, which fixed another issue related to the execve optimization. src/cmd/ksh93/tests/io.sh: - Enable a regression test that was failing because of this bug. - Add the reproducer from att#9 as a regression test. src/cmd/ksh93/tests/options.sh: - This bugfix was causing the options regression test to segfault when run under shcomp. The cause is the same as ksh93#165, so as a workaround avoid parsing the command substitution with shcomp. This workaround should also avoid the other problem detailed in ksh93#274.
The <>; operator doesn't work correctly if it's used as the last command of a -c script. Reproducer: $ echo test > a; ksh -c 'echo x 1<>; a'; cat a x st This bug is caused by ksh running the last command of -c scripts with execve(2) instead of posix_spawn(3) or fork(2). The <>; operator is noted by the man page as being incompatible with the exec builtin (see also the ksh93u+ man page), so it's not surprising this bug occurs when ksh runs a command using execve: > <>;word cannot be used with the exec and redirect built-ins. The ksh2020 fix simply removed the code required for ksh to use this optimization at all. It's not a performance friendly fix and only papers over the bug, so this commit provides a better fix. This bug was first reported at: att#9 In addition, this commit re-enables the execve(2) optimization for the last command for scripts loaded from a file. It was enabled in in older ksh versions, and was only disabled in interactive shells: https://github.com/ksh93/ast-open-history/blob/2011-06-30/src/cmd/ksh93/sh/main.c#L593-L599 It was changed on 2011-12-24 to only be used for -c scripts: https://github.com/ksh93/ast-open-history/blob/2011-12-24/src/cmd/ksh93/sh/main.c#L593-L599 We think there is no good reason why scripts loaded from a file should be optimised less than scripts loaded from a -c argument. They're both scripts; there's no essential difference between them. So this commit reverts that change. If there is a bug left in the optimization after this fix, this revert increases the chance of exposing it so that it can be fixed. src/cmd/ksh93/sh/xec.c: - The IOREWRITE flag is set when handling the <>; operator, so to fix this bug, avoid exec'ing the last command if it uses <>;. See also commit 17ebfbf, which fixed another issue related to the execve optimization. src/cmd/ksh93/tests/io.sh: - Enable a regression test that was failing because of this bug. - Add the reproducer from att#9 as a regression test. src/cmd/ksh93/sh/main.c: - Only avoid the non-forking optimization in interactive shells. src/cmd/ksh93/tests/signal.sh: - Add an extra comment to avoid the non-forking optimization in the regression test for rhbz#1469624. - If the regression test for rhbz#1469624 fails, show the incorrect exit status in the error message. src/cmd/ksh93/tests/builtins.sh, src/cmd/ksh93/tests/options.sh: - This bugfix was causing the options regression test to segfault when run under shcomp. The cause is the same as <#165>, so as a workaround, avoid parsing process substitutions with shcomp until that is fixed. This workaround should also avoid the other problem detailed in <#274>. Resolves: #274
TODO: after fixing this bug, change these regression tests back to using process substitutions. ksh/src/cmd/ksh93/tests/builtins.sh Lines 988 to 1005 in 6701bb3
ksh/src/cmd/ksh93/tests/options.sh Lines 541 to 545 in 6701bb3
|
If we can't fix this before releasing the beta then we can at least temporarily include this to prevent the generation of broken compiled scripts: --- a/src/cmd/ksh93/sh/tdump.c
+++ b/src/cmd/ksh93/sh/tdump.c
@@ -219,6 +219,8 @@ static int p_comarg(register const struct comnod *com)
p_arg(com->comset);
if(!com->comarg)
sfputl(outfile,-1);
+ else if(com->comio && (com->comio->iofile & IOPROCSUB))
+ errormsg(SH_DICT,ERROR_exit(1),"process substitution as file name to redirection is not yet implemented for shcomp");
else if(com->comtyp&COMSCAN)
p_arg(com->comarg);
else |
This is minor, but error messages should be followed by --- a/src/cmd/ksh93/sh/tdump.c
+++ b/src/cmd/ksh93/sh/tdump.c
@@ -219,6 +219,11 @@ static int p_comarg(register const struct comnod *com)
p_arg(com->comset);
if(!com->comarg)
sfputl(outfile,-1);
+ else if(com->comio && (com->comio->iofile & IOPROCSUB))
+ {
+ errormsg(SH_DICT,ERROR_exit(1),"process substitution as file name to redirection is not yet implemented for shcomp");
+ UNREACHABLE();
+ }
else if(com->comtyp&COMSCAN)
p_arg(com->comarg);
else On another note, I tested the patch and it causes the io.sh regression tests to fail under shcomp:
|
The line number in the error message is wrong, it's the line number of the last Updated patch to give the correct line number: --- a/src/cmd/ksh93/sh/tdump.c
+++ b/src/cmd/ksh93/sh/tdump.c
@@ -219,6 +219,12 @@ static int p_comarg(register const struct comnod *com)
p_arg(com->comset);
if(!com->comarg)
sfputl(outfile,-1);
+ else if(com->comio && (com->comio->iofile & IOPROCSUB))
+ {
+ error_info.line = com->comline;
+ errormsg(SH_DICT,ERROR_exit(1),"process substitution as file name to redirection is not yet implemented for shcomp");
+ UNREACHABLE();
+ }
else if(com->comtyp&COMSCAN)
p_arg(com->comarg);
else This shows that the culprit is this command:
That's clearly wrong. Our detection of a process substitution is broken. |
The io.c code in this comment provided the clue. We need to exclude the --- a/src/cmd/ksh93/sh/tdump.c
+++ b/src/cmd/ksh93/sh/tdump.c
@@ -219,6 +219,12 @@ static int p_comarg(register const struct comnod *com)
p_arg(com->comset);
if(!com->comarg)
sfputl(outfile,-1);
+ else if(com->comio && (com->comio->iofile & IOPROCSUB) && !(com->comio->iofile & IOLSEEK))
+ {
+ error_info.line = com->comline;
+ errormsg(SH_DICT,ERROR_exit(1),"process substitution as file name to redirection is not yet implemented for shcomp");
+ UNREACHABLE();
+ }
else if(com->comtyp&COMSCAN)
p_arg(com->comarg);
else Now it seems to work:
|
Hmm, no, it's still broken. Only the first redirection is checked for a process substitution. If any regular redirection precedes a redirection with a process substitution, the latter is ignored again. This makes sense. The fix should probably be implemented in |
New patch: --- a/src/cmd/ksh93/sh/tdump.c
+++ b/src/cmd/ksh93/sh/tdump.c
@@ -194,6 +194,11 @@ static int p_redirect(register const struct ionod *iop)
{
if(iop->iovname)
sfputl(outfile,iop->iofile|IOVNM);
+ else if((iop->iofile & IOPROCSUB) && !(iop->iofile & IOLSEEK))
+ {
+ errormsg(SH_DICT,ERROR_exit(1),"process substitution as file name to redirection is not yet implemented for shcomp");
+ UNREACHABLE();
+ }
else
sfputl(outfile,iop->iofile);
p_string(iop->ioname);
@@ -215,6 +220,7 @@ static int p_redirect(register const struct ionod *iop)
static int p_comarg(register const struct comnod *com)
{
+ error_info.line = com->comline;
p_redirect(com->comio);
p_arg(com->comset);
if(!com->comarg) Now it does seem to be detected correctly:
|
I think I've fixed it. Once I started to understand some of this code, it was ridiculously simple. A process substitution as the argument to a redirection is encoded as a complete parse tree that is used as the file name for the redirection. --- a/src/cmd/ksh93/sh/tdump.c
+++ b/src/cmd/ksh93/sh/tdump.c
@@ -196,7 +196,10 @@ static int p_redirect(register const struct ionod *iop)
sfputl(outfile,iop->iofile|IOVNM);
else
sfputl(outfile,iop->iofile);
- p_string(iop->ioname);
+ if((iop->iofile & IOPROCSUB) && !(iop->iofile & IOLSEEK))
+ p_tree((Shnode_t*)iop->ioname); /* process substitution as file name to redirection */
+ else
+ p_string(iop->ioname); /* file name, descriptor, etc. */
if(iop->iodelim)
{
p_string(iop->iodelim);
--- a/src/cmd/ksh93/sh/trestore.c
+++ b/src/cmd/ksh93/sh/trestore.c
@@ -224,7 +224,10 @@ static struct ionod *r_redirect(Shell_t* shp)
else
iopold->ionxt = iop;
iop->iofile = l;
- iop->ioname = r_string(shp->stk);
+ if((l & IOPROCSUB) && !(l & IOLSEEK))
+ iop->ioname = (char*)r_tree(shp); /* process substitution as file name to redirection */
+ else
+ iop->ioname = r_string(shp->stk); /* file name, descriptor, etc. */
if(iop->iodelim = r_string(shp->stk))
{
iop->iosize = sfgetl(infile); After:
|
The commands within a process substitution used as an argument to a redirection (e.g. < <(...) or > >(...)) are simply not included in parse trees dumped by shcomp. This can be verified with a command like hexdump -C. As a result, these process substitutions do not work when running a bytecode-compiled shell script. The fix is surprisingly simple. A process substitution is encoded as a complete parse tree. When used with a redirection, that parse tree is used as the file name for the redirection. All we need to do is treat the "file name" as a parse tree instead of a string if flags indicate a process substitution. A process substitution is detected by the struct ionod field 'iofile'. Checking the IOPROCSUB bit flag is not enough. We also need to exclude the IOLSEEK flag as that form of redirection may use the IOARITH flag which has the same bit value as IOPROCSUB (see include/shnodes.h). src/cmd/ksh93/sh/tdump.c: p_redirect(): - Call p_tree() instead of p_string() for a process substitution. src/cmd/ksh93/sh/trestore.c: r_redirect(): - Call r_tree() instead of r_string() for a process substitution. src/cmd/ksh93/include/version.h: - Bump the shcomp binary header version as this change is not backwards compatible; previous trestore.c versions don't know how to read the newly compiled process substitutions and would crash. src/cmd/ksh93/tests/io.sh: - Add test. src/cmd/ksh93/tests/builtins.sh, src/cmd/ksh93/tests/options.sh: - Revert shcomp workarounds. (re: 6701bb3) Resolves: #165
$ cat /tmp/bar
echo ok > >(sed 's/ok/good/g')
wait
$ arch/*/bin/ksh <(arch/*/bin/shcomp /tmp/bar)
$ arch/*/bin/ksh /tmp/bar
good |
Are you sure? Your reproducer above works for me on macOS and Linux. An output process substitution is now also tested in io.sh and it passes on the CI runners. You're using a process substitution to run the reproducer, which may confuse things. What shell and version are you using to invoke the reproducer? Do process substitutions work on it? |
Nevermind. I thought I was testing against the latest commit, but I was using the wrong git branch (which was one commit behind). I'm now using the correct git commit and process substitutions work fine with shcomp now. Feel free to close this issue. |
Excellent. Thanks for keeping an eye on things. |
The commands within a process substitution (
<(
...)
an>(
...)
) are simply not included in parse trees dumped by shcomp. This can be verified with a command likehexdump -C
. As a result, process substitutions don't work when running a bytecode-compiled shell script.The bug/omission is likely to be somewhere in
src/cmd/ksh93/sh/tdump.c
which is the code for dumping a parse tree into a file. It may be thatsrc/cmd/ksh93/sh/trestore.c
also needs changing.This problem is also in 93u+, 93v- and ksh2020. Another one of those amazing bugs that have been there for decades. shcomp is basically not usable before this is fixed, unless you know the script does not contain any process substitutions.
The text was updated successfully, but these errors were encountered: