1

This actually works for me for a moment but all of a sudden it stops working. Stuck here for an hour now but still don't know why.

I have

 system("ssh -t machine command > stdout.log 2> error.log &")

Always getting an error saying Pseudo-terminal will not be allocated because stdin is not a terminal

===UPDATE===

I have tried -T (this simply does not return me any errors) and -t -t instead of -t and does not direct error in error.log either

===MORE UPDATE===

-t -t and -tt gives me the error "tcgetattr: Inappropriate ioctl for device serverCmd = 'ssh -x -n machine', and my intended program does not start at all. Dropping & or using qx will give me the same error Pseudo-terminal will not be allocated because stdin is not a terminal as long as I have -t

user3669481
  • 317
  • 7
  • 19

1 Answers1

0

Short   Use fork+exec (recommended), or use piped open, or drop -t pseudo-terminal


There is a lot going on here. You start ssh, which needs to deal with (pseudo-) terminal business, with streams being manipulated -- and for a backgrounded process. It's this last bit that makes it tricky. I get the same behavior as you do.

Here are three solutions/workarounds which work for me. I recommend the first one.

The point of the & in the command is that your script gets the control back right away. We can nicely achieve that right in the script. That is far cleaner, and does not depend on system details. So drop & and put the (ssh) process in the background yourself.

use warnings;
use strict;

my $cmd = 'ssh -t machine command  >stdout.log 2>error.log';

my $pid = fork // die "Can't fork: $!";

if ($pid == 0) {  # child
    exec($cmd);
    die "exec shouldn't have returned: $! ";
}
# Parent. Child became $cmd, which went its own way, never to return.
# Rest of your code, executed right away ...

The error.log will likely contain the line Connection to machine closed. (it does for me). A basic explanation of fork+exec is at the end, if needed.


The following are workarounds which may have problems and may not work everywhere, but they achieve what is needed in my tests.

  • If command doesn't need the controlling tty drop -t. The redirection and & still work for me.

  • Trick the STDIN problem by starting the (ssh) process using piped open

    my $cmd = 'ssh -t machine command >stdout.log 2>error.log &';
    my $pid = open my $fh, '|-', $cmd // die "Can't fork: $!;
    # your other code ...
    close $fh;
    

    This 'opens' (forks) a process that executes $cmd so that $fh is its STDIN, and our script can feed it input if needed. So we provide the STDIN and ssh's pseudo-terminal has no problem with it. If the command doesn't need input you can ignore that and the rest works as needed.

    This is a very clean way to fork another process and pipe to/from its STDIN or STDOUT, see tutorial perlopentut, reference open, and for more detail Safe Pipe Opens in perlipc. However, in this case I'd rather go with fork+exec above.


A basic note on fork-ing in this case

The fork creates a new process and returns its ID to the parent, and 0 to the child. (It's about the only call that returns twice.) This new process is a clone of the parent, except that for that process the $pid is zero, while for the parent it's a (large) integer. (There are some other far smaller differences.)

So the child does enter the next if ($pid == 0) { } block while the parent does not. This way we get to separate child and parent, so each can run the code dedicated to them. We get parallel execution (in principle, and also mostly in practice).

Inside the if block, the child process is replaced altogether by the program executed by exec, in this case by your ssh command. The exec simply never returns (unless the call itself fails). The parent continues to run after the if, executing normally any code that is there without blocking.

This way we spin off another program, while your program can continue with its business without waiting for the other to finish.

zdim
  • 64,580
  • 5
  • 52
  • 81
  • @user3669481 I've considerably expanded the answer, adding two more workarounds. I still recommend fork+exec. – zdim Jul 20 '16 at 18:16
  • Just tried fork and exec but still got the same error (did not drop &, if I drop it, one widget on my interface can't be opened up)..tried again with -tt and -t -t, giving me the error "tcgetattr inappropriate ioctl for device" and the connection closed before starting up the intended program – user3669481 Jul 22 '16 at 01:16
  • @user3669481 Right, it's the `&` that is troubling. The point of fork+exec is that you then don't need it -- the script fires off `ssh ...` and continues, it doesn't block. Apparently, the problem is with that "_command_", and /or with the interface you mention. Why does that specifically need the `&`? Can you edit your question to explain what you are doing, is that feasible? If not, perhaps another question is suitable. As for running a command that logs `STDOUT` and `STDERR` in a non-blocking way, you have the answer(s) here. Finally, did you try `ssh` _without_ `-t`, per first bullet? – zdim Jul 22 '16 at 03:50
  • I tried again with dropping ```&```, it happens to take a long time for the page to stop loading but the program does run. However the error being returned is still ```Pseudo-terminal will not be allocated because stdin is not a terminal```. Dropping ```-t``` in ```ssh``` simply doesn't give me any errors. If I add ```2 >dir/error.log``` in the actually bash script file on my remote machine, the error can be properly captured. – user3669481 Jul 22 '16 at 13:59
  • @user3669481 I don't know why the behavior would change (that it takes a long time) for fork+exec. Perhaps something can/should be done differently with the rest of what is happening in your software. The `system` is pretty much `fork`+`exec`+`wait`. By doing fork+exec manually you just drop the `wait` so that it doens't block. But it's good if it all works without `-t`, if I understood you right. I don't know why your `command` gets affected so much by that `&` (which you don't need otherwise when you do fork+exec). – zdim Jul 22 '16 at 17:06
  • sorry I meant dropping ```-t``` didn't capture any errors – user3669481 Jul 22 '16 at 22:58
  • @user3669481 Ah. Yeah, expected w/o `-t`, I think. A lot is going on the remote host. The less you do with a remote job, the better. (1) Can you redirect error and output within the "_command_" itself -- is that your script? (Is that what you mean by "_bash script_" ?) (2) I don't understand why the "_command_" itself needs that `&` in particular. That seems to be entirely due to what your software is actually doing. As for this script (in this question) to not block and wait for `ssh`, but instead to fire it off and continue, the fork+exec must work. And _piped open_ should, too. – zdim Jul 22 '16 at 23:12