allyc has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks,

I am Looking for some advice with regards to Net::SFTP::Foreign which I think is a fantastic module.

I am trying to beef up the error trapping from my scripts to help me diagnose connect errors. When a connect fails with this module, it normally returns "Connection to remote server is broken" and the real error is sent to the STDERR for the console I am running. I assume the error is being sent to STDERR by the ssh executable its self.

I have had a look at Foreign.pm and I believe its because teh module uses ICP::Open2 to spawn the ssh.exe as this only allows STDIN and STDOUT to be captured.

I thought I would be clever and experiment with changing it to use ICP::Open3 and adding a STDERR file handle to the code.

When a connection fails, I can indeed read the STDERR file handle and the message from ssh is there (such as "Connection Timed Out" or "Permission Denied") which is great. Problem is that the module will not connect to servers which should work. The SSH command just hangs now so the Net::SFTP::Foreign->new method never returns.

Have I looked at this too simply as this is something I have never needed to look at before? Is what I am trying to achieve possible and am I going in the right direction?

Any help would be most gratefully appreciated.

Thanks,

Al

Replies are listed 'Best First'.
Re: Net::SFTP::Foreign Connection STDERR
by allyc (Scribe) on Sep 23, 2008 at 21:34 UTC
    Hi Again,

    Been doing some more playing.. reading the docs for open3 and realised the fh order is different to open2.. so now I get the connection.

    I have created a new fh called $sftp->{ssh_err} however how do I read the error from this when the code bombs?

    Think I am going to give up for tonight.. any help would be still gratefully received! Not that great at process / io stuff....

    Al

      The new method for Net::SFTP::Foreign accepts a transport option that lets you control how the SSH transport is constructed.

      Unfortunately it is not so simple as linking ssh stderr stream back to Perl through a pipe as it would block the child if you don't read from it at the right times, and that means going into the select loop at the core of Net::SFTP::Foreign.

      But there is a simple work around, just send ssh stderr to a file and read it afterwards:

      use Net::SFTP::Foreign; use File::Temp; my $hostname = 'localhost'; my $ssherr = File::Temp->new or die "File::Temp->new failed"; open my $stderr_save, '>&STDERR' or die "unable to dup STDERR"; open STDERR, '>&'.fileno($ssherr); my $sftp = Net::SFTP::Foreign->new($hostname, more => ['-v']); # the child ssh process has already been created with the redirected S +TDERR # so we can reset it on the parent: open STDERR, '>&'.fileno($stderr_save); if ($sftp->error) { print "sftp error: ".$sftp->error."\n"; seek($ssherr, 0, 0); while (<$ssherr>) { print "captured stderr: $_"; } } close $ssherr;
        Fantastic! I never thought of just redirecting the whole SDTOUT but it works a charm.

        Just makes it easier now to see which hosts fail because the account is locked, connection is refused or the SSH key has been refused.

        Thank you so much!

        Al

        File::Temp sets the close-on-exec flag, so for ssh to be able to actually write to the file, you need to clear it:
        use Fcntl qw(F_SETFD F_GETFD FD_CLOEXEC); my $ssherr = File::Temp->new; my $flags = fcntl($ssherr, F_GETFD, 0) or die "Can't get FD flags for '$ssherr': $!"; fcntl($ssh_err, F_SETFD, $flags & ~FD_CLOEXEC) or die "Cant clear close-on-exec flag for '$ssherr': $!"; <the rest of your code>
Re: Net::SFTP::Foreign Connection STDERR
by salva (Canon) on Apr 19, 2010 at 09:58 UTC
    Redirecting STDERR to a file is now natively supported by Net::SFTP::Foreign (version 1.58_01 required):
    my $ssherr = File::Temp->new or die "File::Temp->new failed"; my $sftp = Net::SFTP::Foreign->new($hostname, more => ['-v'], stderr_f +h => $ssherr); if ($sftp->error) { print "sftp error: ".$sftp->error."\n"; seek($ssherr, 0, 0); while (<$ssherr>) { print "captured stderr: $_"; } }