|Just another Perl shrine|
Re: STDOUT redirects and IPC::Open3by Eliya (Vicar)
|on Oct 18, 2011 at 15:26 UTC||Need Help??|
AFAICT, IPC::Open3 is buggy in that it doesn't (under the circumstance outlined below) properly wire the child's ends of the pipes to the standard file descriptors 0-2.
After having set up the pipes, it does (in the child process, after the fork):
where $dad_rdr is the parent's, and $kid_wtr is the child's end of the respective pipe (and xopen/xclose are just error-handling wrappers around the normal open/close builtins).
The problem with this code is that it doesn't achieve to "connect" (dup) file descriptor 1 to the pipe, unless STDOUT already is associated with file descriptor 1. If you look at fileno(STDOUT) immediately before the exec (somewhat further down in the module's code), you'll see that in your test case it isn't 1, as it's supposed to, but rather fileno($kid_wtr) — i.e. 9, for example. Now, no exec'ed normal child program, such as cat, is going to send its standard output to file descriptor 9... Rather, it will write to file descriptor 1 as usual — which in your case is the one inherited from the parent, which would typically still be connected to the terminal.
In other words, the problem is the "&=" in the above open statement, because that makes (as documented) STDOUT the same file descriptor as what is specified after the &=.
You may wonder why the module works under some (or most) circumstances. Reason is that the behavior of &= is special if STDOUT already is associated with file descriptor 1. In that case, open STDOUT, ">&=9" does not make STDOUT's file descriptor become 9, rather it'll still be 1. Due to this (undocumented, AFAICT) peculiarity, the child's end of the pipe is wired correctly under most circumstances, and things work as expected...
In your case, however, you've redirectd STDOUT to the variable $stdout, so it's no longer associated with file descriptor 1 (actually, in this particular case, it's no longer associated with any file descriptor at all, because printing to a string happens Perl internal, without any system file descriptors involved (fileno() reports -1 here)). For this reason, open STDOUT, ">&=9" works as documented, so the file descriptor behind STDOUT (where the exec'ed child should write to) actually does become 9, which cat of course knows nothing about.
The fix would be to make sure the child's end of the pipe is actually wired to where stdout would normally go, i.e. file descriptor 1 (and the same holds for stdin and stderr respectively, of course).
So, according to some testing, I would suggest to replace IPC::Open3's
Note that the "=" has been removed from the second open statement. Also note that open STDOUT, ">&=1" explicitly ignores errors, which would occur if there is no valid file descriptor 1. In this case, however, the dup which is behind the subsequent open statement should pick 1 anyway (because it's the lowest available), unless you've closed file descriptor 0 as well (in which case you have a more serious problem...).
This should handle the cases where the original (parent's) STDOUT file descriptor is still the default (1), or when it's been redirected (i.e. > 1, or none/-1 for memory handles). Also, it shouldn't matter whether there still is a valid file descriptor 1 (accessible via $oldout in your case), or whether it's been closed.
I've tested the fix with 5.12.3 on Linux, and it sems to work fine. Suggestions for improvements welcome.