Subject: | STDOUT has O_NONBLOCK set after using pipe_out |
The following test script shows that STDOUT has the O_NONBLOCK flag set after calling Net::OpenSSH's pipe_out() and reading something from the returned filehandle:
#!/usr/bin/perl
use strict;
use warnings;
use Net::OpenSSH;
use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
use Test::More tests => 2;
my $host = "localhost";
my $ssh = Net::OpenSSH->new($host); $ssh->error and $ssh->error;
my($rout, $pid) = $ssh->pipe_out("echo", "bla");
{ my $flags = fcntl(STDOUT, F_GETFL, 0) or die $!; is(($flags & O_NONBLOCK), 0, "no O_NONBLOCK on STDOUT") }
<$rout>;
#close $rout; # does not matter
{ my $flags = fcntl(STDOUT, F_GETFL, 0) or die $!; is(($flags & O_NONBLOCK), 0, "no O_NONBLOCK on STDOUT") }
__END__
To reproduce the problem, the script has to be piped with stdout and stderr to something, e.g.
perl openssh-ononblock.pl |& cat
This is what I see (Linux Mint 18, FreeBSD 9.2; shell does not matter --- I tried zsh, bash, tcsh; perl version apparently also does not matter):
$ perl openssh-ononblock.pl |& cat
1..2
ok 1 - no O_NONBLOCK on STDOUT
not ok 2 - no O_NONBLOCK on STDOUT
# Failed test 'no O_NONBLOCK on STDOUT'
# at /tmp/openssh-ononblock.pl line 15.
# got: '2048'
# expected: '0'
# Looks like you failed 1 test of 2.
I think these are the interesting lines from strace:
14573 16:55:06.941599 sendmsg(3, {msg_name(0)=NULL, msg_iov(1)=[{"\0", 1}], msg_controllen=24, [{cmsg_len=20, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, [1]}], msg_flags=0}, 0 <unfinished ...>
14573 16:55:06.941617 sendmsg(3, {msg_name(0)=NULL, msg_iov(1)=[{"\0", 1}], msg_controllen=24, [{cmsg_len=20, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, [2]}], msg_flags=0}, 0 <unfinished ...>
14566 16:55:06.941667 recvmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"\0", 1}], msg_controllen=24, [{cmsg_len=20, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, [8]}], msg_flags=0}, 0) = 1 <0.000005>
14566 16:55:06.941682 recvmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"\0", 1}], msg_controllen=24, [{cmsg_len=20, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, [9]}], msg_flags=0}, 0) = 1 <0.000005>
14566 16:55:06.941737 fcntl(8, F_SETFL, O_WRONLY|O_NONBLOCK) = 0 <0.000004>
14566 16:55:06.941775 fcntl(9, F_SETFL, O_WRONLY|O_NONBLOCK) = 0 <0.000004>
This looks like file descriptor passing is used here, and stdout & stderr are passed to the other process, and O_NONBLOCK is set here, which seems to affect also the original process. Pid 14566 here is the process doing ["ssh", "-o", "ServerAliveInterval=30", "-o", "ControlPersist=no", "-2MNx", "-S", "/home/slaven.rezic/.libnet-openssh-perl/localhost-14562-376425", "localhost", "--"]
O_NONBLOCK on STDOUT is problematic, as a normal print() can fail with an EAGAIN error. My current workaround is to do something like this after dealing with pipe_out:
use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
my $flags = fcntl(STDOUT, F_GETFL, 0) or die $!;
fcntl(STDOUT, F_SETFL, $flags & ~O_NONBLOCK) or die $!;
No idea if this could be fixed in Net::OpenSSH, but maybe a couple of documentation lines would be good.