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

I am troublesdhooting a perl expect script that interacts with a remote server running a custom OS on top of an OpenBSD kernel. The script creates an ssh connection and logs in successfully. It even shows the welcoming text of the custom OS and matches on the first custom OS's prompt. The script then sends a command to the remote server and the perl expect script echos back the command and closes the connection. I found the bit of trouble code in the Expect.pm module on or around lin 821 my $nread = sysread($exp, $buffer, 2048);. This sysread call fails to read anything off the socket after the system command is sent to the remote server and then sets $nread to 0 which is later interpreted into an EOF and sent to the server, $nread = 0 unless defined ($nread);.
if ($nread == 0) { ... ... blah blah $exp->hard_close(); }
First I thought it may be an issue with the tty or pty type because the app I am interacting with is not a standard type terminal service (popular Linux/BSD OS, cisco router, ssh console, etc). I tried setting the session to raw but that didn't help either. I then found some code in Expect.pm that appears to be re-assigning STDIN, STDOUT and STDERR starting on or around line 158:
close(STDIN); open(STDIN,"<&". $slv->fileno()) or die "Couldn't reopen STDIN for reading, $!\n"; close(STDOUT); open(STDOUT,">&". $slv->fileno()) or die "Couldn't reopen STDOUT for writing, $!\n"; close(STDERR); open(STDERR,">&". $slv->fileno()) or die "Couldn't reopen STDERR for writing, $!\n";
Could this have anything to do with it? Anyone suggestions on how I can get this to work with my non-standard OS?

Replies are listed 'Best First'.
Re: Expect.pm early termination?
by shmem (Chancellor) on Jul 25, 2006 at 06:42 UTC
    Hello Elijah,

    something is missing on line 17 of your script perhaps. Would you mind to post it? Whatever you are trying to accomplish, did that work for you on the command line with e.g. telnet or netcat? Since you login and get a prompt, I guess you are talking to some shell, but you'll tell me to make sure. Rather than a bug in Expect I expect one of the following to be happening:

    • the command doesn't get to the remote side
    • the remote side doesn't get the command right and chokes
    • the remote side doesn't find the command to execeute
    • the remote command dies on execution

    What kind of protocol are you speaking with that server? You could monitor the conversation on the network level with e.g. ethereal and check whether it matches your expectations.

    cheers,
    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
      Well if the source code will help then I will be glad to post it.
      #!/usr/bin/perl -w use strict; use Expect; my $username = "admin"; my $password = "password\n"; ##### flip this value to 0 to turn off stout and 1 to turn it on $Expect::Log_Stdout=1; # Lets see what expect is doing! $Expect::Exp_Internal = 1; #$Expect::Debug = 3; my $terminal = new Expect; $terminal->raw_pty(1); $terminal = Expect->spawn("ssh -l $username hostname"); $terminal->log_file("test_err.txt"); unless ($terminal->expect(30,"passphrase")) { die "Error 01::Never got password prompt, ".$terminal->exp_error(). +"\n"; } print "...recieved password prompt\n"; $terminal->send($password); unless ($terminal->expect(30, '/# ')) { die "Error 02::Never got first prompt, ".$terminal->exp_error()."\n +"; } $terminal->send("sys hard\n"); unless ($terminal->expect(30, '/# ')) { die "Error 03::Never got sec prompt, ".$terminal->exp_error()."\n"; } $terminal->send("exit\n"); $terminal->log_file(undef);
      I do not however believe there is an issue with it since it is so basic and have already troubleshot down to the Expect.pm module but if it helps you help me, there it is.

      Yes the entire session always works via command line and tcl expect and python expect, so I would venture a guess that it is perl expect that has the issue. Yes I am talking to some shell type application, sorry if that was not clear in my original post. The remote server shell mimics that of a Cisco IOS type OS.

      I have monitored the connection via ethereal but none of it really helps since ssh is an encrypted session I can not see what is being sent in the payload. Any other suggestions?

        Yes. Since you do $terminal->raw_pty(1), did you test
        $terminal->send("sys hard\r\n");

        i.e. sending CRLF? The script looks ok, and re-opening filehandles is ok too. It must not be Expect's fault if there's nothing to read from the socket; if nothing was sent, that's perfectly ok. I guess the remote shell doesn't grok the input is terminated, so I'd play with various setings of CRLF translations.

        HTH,
        --shmem

        _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                      /\_¯/(q    /
        ----------------------------  \__(m.====·.(_("always off the crowd"))."·
        ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: Expect.pm early termination?
by Elijah (Hermit) on Jul 26, 2006 at 19:03 UTC
    Just a heads up if anyone ever runs into this same issue. The problem ended up being in the Expect.pm module or more correct the IO::Tty module. The perl expect script I was running was talking to psh on the distant end and perl expect was sending a terminal window size of (0, 0) which libedit (which is used by psh) had a problem with. To solve this problem IO::Tty.pm has a function called clone_winsize_from() that allows you to set the terminal size yourself on the slave end. This ended up doing the trick:

    $terminal->slave->clone_winsize_from(\*STDIN);
    Adding this to any perl expect script that is having these same issues should do the trick. I have contacted the author of the Expect module and requested that he add explicit setting of the terminal window size in the "new" construct of the module. We shall see if this is in the next release.

    Actually what I suggested he add to his module was the following code:

    unless (eval{$self->clone_winsize_from(\*STDIN);}) { my $winsize = pack('SSSS', 25, 80, 0, 0); # rows, cols, #pixelsX, + #pixelsY ioctl($self->slave(), &IO::Tty::Constant::TIOCSWINSZ, $winsize); }
    Since STDIN may not always be a tty and thus will fail a POSIX tty check in Tty.pm, I added a check for that and then explicitly set the terminal size using ioctl() manually if Tty.pm fails to do so.

    This should cover both possibilities, but the portion that still allows Tty.pm to try and set it could be removed all together and just have Expect.pm explicitly set it itself every time. The above code (either method) can be added to your particular expect script too untill Expect.pm has this code. Just be sure to have the proper OO path for the function call (ie. $term->slave->clone_winsize_from(\*STDIN);).

      Hi, I face this same issue and thanks for yours solution. But I'm not sure where exactly to add those lines suggested by you in the expect.pm module. Can you kindly suggest me that?