4
votes

I'm writing a perl script that kicks off a script on several different servers using ssh. The remote script needs to run as long as this script is running:

#!/usr/bin/perl
require 'config.cfg'

#@servers is defined in config.cfg
#Contains server info as [username,hostname]
#
# @servers = ([username,server1.test.com],[username,server2.test.com]) 
#
foreach $server ( @servers ) {
    my $pid = fork();
    if ( $pid == 0 ) {
        $u = ${$server}[0];
        $h = ${$server}[1];
        print "Running script on $h \n";
        print `ssh -tl $u $h perl /opt/scripts/somescript.pl`;
        exit 0;
    } else {
        die "Couldn't start the process: $!\n";
    }
}
[...]

When I run this script, I get the following output:

./brokenscript.pl
Running script on server01
$ tcsetattr: Input/output error
Connection to server01 closed.

The same result occurs when running with system (and backticks are preferred anyways since I want the output of the script). When I run the exact command between the backticks on the command line, it works exactly as expected. What is causing this?

2

2 Answers

5
votes

The tcsetattr: Input/output error message comes from ssh when it tries to put the local terminal into “raw” mode (which involves a call to tcsetattr; see enter_raw_mode in sshtty.c, called from client_loop in clientloop.c).

From IEEE Std 1003.1, 2004 (Posix) Section 11.1.4: Terminal Access Control, tcsetattr may return -1 with errno == EIO (i.e. “Input/output error”) if the calling process is in an orphaned (or background?) process group.

Effectively ssh is trying to change the setting of the local terminal even though it is not in the foreground process group (due of your fork and, the local script exiting (as evidenced by the apparent shell prompt that comes immediately before the error message in your quoted output)).

If you just want to avoid the error message, you can use ssh -ntt (redirect stdin from /dev/null, but ask the remote side to allocate a tty anyway) instead of ssh -t (add your -l and any other options you need back in too, of course).


More likely, you are interesting in keeping the local script running as long as some of the remote processes are still running. For that, you need to use the wait function (or one of its “relatives”) to wait for each forked process to exit before you exit the program that forked them (this will keep them in the foreground process group as long as the program that started them is in it). You may still want to use -n though, since it would be confusing if the multiple instances of ssh that you forked all tried to use (read from, or change the settings of) the local terminal at the same time.

As a simple demonstration, you could have the local script do sleep 30 after forking off all the children so that the ssh command(s) have time to start up while they are part of the foreground process group. This should suppress the error message, but it will not address your stated goal. You need wait for that (If I am interpreting your goal correctly).

0
votes

That probably happens because you are forcing SSH to allocate a tty when stdin/stdout are not really ttys. SSH tries to call some specific tty function on those handlers (probably forwarded from the remote side) and the call fails returning some error.

Is there any reason why you should be allocating a tty?

Is there also any reason to use the obsolete version 1 of the SSH protocol?