stderr sobre ssh -t

11

Isso envia a saída para STDERR, mas não propaga Ctrl+ C(ou seja, Ctrl+ Cmata, sshmas não o controle remoto sleep):

$ ssh localhost 'sleep 100;echo foo ">&2"'

Isso propaga Ctrl+ C(ou seja, Ctrl+ Cmata sshe o controle remoto sleep), mas envia STDERR para STDOUT:

$ ssh -tt localhost 'sleep 100;echo foo ">&2"'

Como forçar o segundo a enviar a saída STDERR para STDERR, enquanto ainda propaga Ctrl+ C?

fundo

O GNU Parallel usa 'ssh -tt' para propagar Ctrl+ C. Isso possibilita a execução remota de tarefas em execução. Mas os dados enviados ao STDERR devem continuar a ir para o STDERR no final do recebimento.

Ole Tange
fonte

Respostas:

5

Eu não acho que você pode contornar isso.

Com -tt, sshdgera um pseudo-terminal e torna o escravo parte do stdin, stdout e stderr do shell que executa o comando remoto.

sshdlê o que está vindo do seu (único) fd para a parte principal do pseudo-terminal e envia isso (via um único canal) para o sshcliente. Não existe um segundo canal para o stderr, pois existe sem -t.

Além disso, observe que a disciplina da linha de terminal do pseudo-terminal pode (e por padrão) alterar a saída. Por exemplo, o LF será convertido em CRLF por lá e não no terminal local; portanto, convém desativar o pós-processamento de saída.

$ ssh  localhost 'echo x' | hd
00000000  78 0a                                             |x.|
00000002
$ ssh -t localhost 'echo x' | hd
00000000  78 0d 0a                                          |x..|
00000003
$ ssh -t localhost 'stty -opost; echo x' | hd
00000000  78 0a                                             |x.|
00000002

Muito mais coisas acontecerão no lado da entrada (como o ^Cpersonagem que causará um SIGINT, mas também outros sinais, o eco e todo o manuseio envolvido no editor de linha de modo canônico ).

Você poderia redirecionar o stderr para um fifo e recuperá-lo usando um segundo ssh:

ssh -tt host 'mkfifo fifo && cmd 2> fifo' &
ssh host 'cat fifo' >&2

Mas o melhor IMO seria evitar o uso -tcompleto. Isso realmente significa apenas para uso interativo a partir de um terminal real.

Em vez de confiar na transmissão de um ^ C para permitir que a conexão remota seja encerrada, você pode usar um wrapper que faz a poll()para detectar a sshconexão interrompida ou encerrada.

Talvez algo como (simplificado, você queira adicionar uma verificação de erro):

LC_HUP_DETECTOR='
  use IO::Poll;
  $SIG{CHLD} = sub {$done = 1};
  $p = IO::Poll->new;
  $p->mask(STDOUT, POLLIN);
  $pid=fork; unless($pid) {setpgrp; exec @ARGV; die "exec: $!\n"}
  $p->poll;
  kill SIGHUP, -$pid unless $done;
  wait; exit ($?&127 ? 128+($?&127) : 1+$?>>8)
' ssh host 'perl -e "$LC_HUP_DETECTOR" some cmd'

O $p->mask(STDOUT, POLLIN)exposto acima pode parecer bobagem, mas a idéia é aguardar um evento de interrupção (para que a extremidade de leitura do pipe no stdout seja fechada). POLLHUP como uma máscara solicitada é ignorada. POLLHUP é apenas significativo como um evento retornado (para dizer que o fim da gravação foi fechado).

Temos que fornecer um valor diferente de zero para a máscara de evento. Se usarmos 0, perlnem ligue poll. Então, aqui usamos POLLIN.

No Linux, o que você solicitar, se o canal for quebrado, poll () retornará POLLERR.

No Solaris e no FreeBSD, onde os pipes são bidirecionais, quando a extremidade de leitura do pipe (que também é um fim de gravação) é fechada, ele retorna com POLLHUP (e POLLIN no FreeBSD, onde você precisa solicitar POLLIN ou $p->poll()não Retorna).

Não posso dizer o quão portátil é fora desses três sistemas operacionais.

Stéphane Chazelas
fonte
Gosto da sua ideia, mas não posso fazer seu wrapper detectar nenhum sinal, a menos que '-tt' esteja definido. Isso funciona parallel --tag -j1 'ssh -tt localhost perl/catch_wrap perl/catch_all_signals & sleep 1; killall -{} ssh' ::: {1..31}:, mas remova o '-tt' e, em seguida, ele não funciona.
precisa
@OleTange O objetivo do wrapper é que o SIGHUP seja enviado para o trabalho remoto quando o ssh morrer (após uma conexão ssh). Eu não sei o que o seu catch_all_signals faz, mas tudo o que obteria é que o SIGHUP e somente após a conexão ssh cair (por isso, se imprimir alguma coisa no stdout, você não a verá).
Stéphane Chazelas
catch_all_signals registra todos os sinais em um arquivo e, como mencionado, funciona com '-tt', mas falha sem. Em outras palavras: Ele não recebe um SIGHUP de catch_wrap quando o ssh morre.
precisa
Ainda funciona apenas -ttapós a sua edição. Lembre-se, se você não executar o comando paralelamente, o ssh herdará o terminal do qual você o executou.
Ole Tange
@OleTange, não consigo reproduzir, funciona para mim, você já testou com o código que publiquei? Poste seus catch_wrap e catch_all_signals em algum lugar para que eu possa dar uma olhada. Com -t, espero que não funcione.
Stéphane Chazelas
1

Para fazê-lo funcionar em outras plataformas, essa se tornou a solução final. Ele verifica se o cliente ssh desconectou e, portanto, o pai se tornou o pid 1:

$SIG{CHLD} = sub { $done = 1; };
$pid = fork;
unless($pid) {
    # Make own process group to be able to kill HUP it later
    setpgrp;
    exec $ENV{SHELL}, "-c", ($bashfunc."@ARGV");
    die "exec: $!\n";
}
do {
    # Parent is not init (ppid=1), so sshd is alive
    # Exponential sleep up to 1 sec
    $s = $s < 1 ? 0.001 + $s * 1.03 : $s;
    select(undef, undef, undef, $s);
} until ($done || getppid == 1);
# Kill HUP the process group if job not done
kill(SIGHUP, -${pid}) unless $done;
wait;
exit ($?&127 ? 128+($?&127) : 1+$?>>8)
Ole Tange
fonte