Por que esse arquivo binário transferido por "ssh -t" está sendo alterado?

29

Estou tentando copiar arquivos pelo SSH , mas não posso usá-lo scppor não saber o nome exato do arquivo que preciso. Embora pequenos arquivos binários e arquivos de texto sejam transferidos corretamente, arquivos binários grandes são alterados. Aqui está o arquivo no servidor:

remote$ ls -la
-rw-rw-r--  1 user user 244970907 Aug 24 11:11 foo.gz
remote$ md5sum foo.gz 
9b5a44dad9d129bab52cbc6d806e7fda foo.gz

Aqui está o arquivo depois que eu o movi:

local$ time ssh [email protected] -t 'cat /path/to/foo.gz' > latest.gz

real    1m52.098s
user    0m2.608s
sys     0m4.370s
local$ md5sum latest.gz
76fae9d6a4711bad1560092b539d034b  latest.gz

local$ ls -la
-rw-rw-r--  1 dotancohen dotancohen 245849912 Aug 24 18:26 latest.gz

Observe que o arquivo baixado é maior que o do servidor! No entanto, se eu fizer o mesmo com um arquivo muito pequeno, tudo funcionará conforme o esperado:

remote$ echo "Hello" | gzip -c > hello.txt.gz
remote$ md5sum hello.txt.gz
08bf5080733d46a47d339520176b9211  hello.txt.gz

local$ time ssh [email protected] -t 'cat /path/to/hello.txt.gz' > hi.txt.gz

usuário 0m3.041s real 0m0.013s sys 0m0.005s

local$ md5sum hi.txt.gz
08bf5080733d46a47d339520176b9211  hi.txt.gz

Ambos os tamanhos de arquivo são 26 bytes neste caso.

Por que arquivos pequenos podem ser transferidos corretamente, mas arquivos grandes recebem alguns bytes adicionados a eles?

dotancohen
fonte
10
É a -topção, que interrompe a transferência. Não use -tou -T, a menos que você precise deles por um motivo muito específico. O padrão funciona na grande maioria dos casos, portanto, essas opções raramente são necessárias.
precisa saber é o seguinte
3
Nunca pensei que eu diria isso neste século, mas você pode tentar o uuencode e o uudecode, se essa ssh -t catfor a única maneira de transferir arquivos.
MarkPlotnick
1
@MarkPlotnick versão moderna do uuencode / uudecode estão agora chamado base64 / base64 -d
Archemar

Respostas:

60

TL; DR

Não use -t. -tenvolve um pseudo-terminal no host remoto e deve ser usado apenas para executar aplicativos visuais a partir de um terminal.

Explicação

O caractere de avanço de linha (também conhecido como nova linha ou \n) é aquele que, quando enviado para um terminal, informa ao terminal para mover o cursor para baixo.

No entanto, quando você executa seq 3um terminal, é aí que seqescreve 1\n2\n3\npara algo como /dev/pts/0, você não vê:

1
 2
  3

mas

1
2
3

Por que é que?

Na verdade, quando seq 3(ou ssh host seq 3nesse caso) escreve 1\n2\n3\n, o terminal vê 1\r\n2\r\n3\r\n. Ou seja, os feeds de linha foram traduzidos para retorno de carro (no qual os terminais movem o cursor de volta para a esquerda da tela) e feed de linha.

Isso é feito pelo driver de dispositivo do terminal. Mais exatamente, pela disciplina de linha do dispositivo terminal (ou pseudo-terminal), um módulo de software que reside no kernel.

Você pode controlar o comportamento dessa disciplina de linha com o sttycomando A tradução de LF-> CRLFé ativada com

stty onlcr

(que geralmente é ativado por padrão). Você pode desativá-lo com:

stty -onlcr

Ou você pode desativar todo o processamento de saída com:

stty -opost

Se você fizer isso e executar seq 3, verá:

$ stty -onlcr; seq 3
1
 2
  3

como esperado.

Agora, quando você faz:

seq 3 > some-file

seqnão está mais gravando em um terminal, está gravando em um arquivo, não há tradução sendo feita. O some-filemesmo contém 1\n2\n3\n. A tradução é feita apenas ao gravar em um dispositivo terminal. E isso é feito apenas para exibição.

Da mesma forma, quando você faz:

ssh host seq 3

sshestá gravando, 1\n2\n3\nindependentemente do sshresultado da saída.

O que realmente acontece é que o seq 3comando é executado hostcom seu stdout redirecionado para um canal. O sshservidor no host lê a outra extremidade do canal e envia-o pelo canal criptografado para o seu sshcliente e o sshcliente grava no stdout, no seu caso, um dispositivo pseudo-terminal, para o qual LFsão traduzidos CRLFpara exibição.

Muitos aplicativos interativos se comportam de maneira diferente quando o stdout não é um terminal. Por exemplo, se você executar:

ssh host vi

vinão gosta, não gosta que sua saída vá para um cano. Ele acha que não está falando com um dispositivo capaz de entender as seqüências de escape do posicionamento do cursor, por exemplo.

Então sshtem a -topção para isso. Com essa opção, o servidor ssh no host cria um dispositivo pseudo-terminal e torna esse stdout (e stdin e stderr) de vi. O que viescreve nesse dispositivo terminal passa por essa disciplina remota de linha pseudo-terminal e é lido pelo sshservidor e enviado pelo canal criptografado para o sshcliente. É o mesmo de antes, exceto que, em vez de usar um pipe , o sshservidor usa um pseudo-terminal .

A outra diferença é que, no lado do sshcliente , o cliente define o terminal no rawmodo. Isso significa que nenhuma tradução é feita lá ( opostestá desativada e também outros comportamentos do lado da entrada). Por exemplo, quando você digita Ctrl-C, em vez de interromper ssh, esse ^Ccaractere é enviado para o lado remoto, onde a disciplina de linha do pseudo-terminal remoto envia a interrupção para o comando remoto.

Quando você faz:

ssh -t host seq 3

seq 3grava 1\n2\n3\nem seu stdout, que é um dispositivo pseudo-terminal. Por causa do onlcrque é traduzido no host para 1\r\n2\r\n3\r\ne enviado a você sobre o canal criptografado. Do seu lado, não há tradução ( onlcrdesativada); portanto, 1\r\n2\r\n3\r\né exibida intocada (por causa do rawmodo) e corretamente na tela do seu emulador de terminal.

Agora, se você fizer:

ssh -t host seq 3 > some-file

Não há diferença de cima. sshvai escrever a mesma coisa:, 1\r\n2\r\n3\r\nmas desta vez em some-file.

Então, basicamente todo o LFna saída de seqter sido traduzida para CRLFdentro some-file.

É o mesmo se você fizer:

ssh -t host cat remote-file > local-file

Todos os LFcaracteres (0x0a bytes) estão sendo convertidos em CRLF (0x0d 0x0a).

Essa é provavelmente a razão da corrupção no seu arquivo. No caso do segundo arquivo menor, acontece que o arquivo não contém 0x0a bytes, portanto, não há corrupção.

Observe que você pode obter diferentes tipos de corrupção com diferentes configurações de tty. Outro tipo potencial de corrupção associado -té se os arquivos de inicialização em host( ~/.bashrc, ~/.ssh/rc...) gravam coisas no stderr, porque com -to stdout e o stderr do shell remoto acabam sendo mesclados no sshstdout do s (ambos vão para o pseudo dispositivo terminal).

Você não deseja que o controle remoto catsaia para um dispositivo terminal lá.

Você quer:

ssh host cat remote-file > local-file

Você poderia fazer:

ssh -t host 'stty -opost; cat remote-file` > local-file

Isso funcionaria (exceto na gravação do caso stderr de corrupção discutido acima), mas mesmo isso seria sub-ideal, pois você teria a camada pseudo-terminal desnecessária em execução host.


Um pouco mais divertido:

$ ssh localhost echo | od -tx1
0000000 0a
0000001

ESTÁ BEM.

$ ssh -t localhost echo | od -tx1
0000000 0d 0a
0000002

LF traduzido para CRLF

$ ssh -t localhost 'stty -opost; echo' | od -tx1
0000000 0a
0000001

OK de novo.

$ ssh -t localhost 'stty olcuc; echo x'
X

Essa é outra forma de pós-processamento de saída que pode ser feita pela disciplina da linha de terminal.

$ echo x | ssh -t localhost 'stty -opost; echo' | od -tx1
Pseudo-terminal will not be allocated because stdin is not a terminal.
stty: standard input: Inappropriate ioctl for device
0000000 0a
0000001

sshse recusa a dizer ao servidor para usar um pseudo-terminal quando sua própria entrada não é um terminal. Você pode forçá-lo com -tt:

$ echo x | ssh -tt localhost 'stty -opost; echo' | od -tx1
0000000   x  \r  \n  \n
0000004

A disciplina de linha faz muito mais no lado da entrada.

Aqui, echonão lê sua entrada nem foi solicitado que a produza; x\r\n\nportanto, de onde ela vem? Esse é o local echodo pseudo-terminal remoto ( stty echo). O sshservidor está alimentando a x\nleitura do cliente para o lado principal do pseudo-terminal remoto. E a disciplina de linha disso ecoa de volta (antes stty oposté executada e é por isso que vemos um CRLFe não LF). Isso é independente de o aplicativo remoto ler algo do stdin ou não.

$ (sleep 1; printf '\03') | ssh -tt localhost 'trap "echo ouch" INT; sleep 2'
^Couch

O 0x3personagem é repetido como ^C( ^e C) por causa de stty echoctle a concha e o sono recebem um SIGINT porque stty isig.

Por enquanto:

ssh -t host cat remote-file > local-file

já é ruim o suficiente, mas

ssh -tt host 'cat > remote-file' < local-file

transferir arquivos do outro lado é muito pior. Você terá alguns CR -> Tradução LF, mas também problemas com todos os caracteres especiais ( ^C, ^Z, ^D, ^?, ^S...) e também o controle remoto catnão verá EOF quando o fim local-fileé alcançado, somente quando ^Dé enviado depois de um \r, \nou outro ^Dcomo ao fazer cat > fileno seu terminal.

Stéphane Chazelas
fonte
5

Ao usar esse método para copiar o arquivo, os arquivos parecem diferentes.

Servidor remoto

ls -l | grep vim_cfg
-rw-rw-r--.  1 slm slm 9783257 Aug  5 16:51 vim_cfg.tgz

Servidor local

Executando seu ssh ... catcomando:

$ ssh dufresne -t 'cat ~/vim_cfg.tgz' > vim_cfg.tgz

Resultados neste arquivo no servidor local:

$ ls -l | grep vim_cfg.tgz 
-rw-rw-r--. 1 saml saml 9820481 Aug 24 12:13 vim_cfg.tgz

Investigando por quê?

Investigar o arquivo resultante no lado local mostra que ele foi corrompido. Se você -tdesativar o sshcomando, ele funcionará conforme o esperado.

$ ssh dufresne 'cat ~/vim_cfg.tgz' > vim_cfg.tgz

$ ls -l | grep vim_cfg.tgz
-rw-rw-r--. 1 saml saml 9783257 Aug 24 12:17 vim_cfg.tgz

As somas de verificação agora também funcionam:

# remote server
$ ssh dufresne "md5sum ~/vim_cfg.tgz"
9e70b036836dfdf2871e76b3636a72c6  /home/slm/vim_cfg.tgz

# local server
$ md5sum vim_cfg.tgz 
9e70b036836dfdf2871e76b3636a72c6  vim_cfg.tgz
slm
fonte
Obrigado Sim. Embora você tenha sido o primeiro a postar a resposta correta, selecionei Stéphane para a resposta escolhida devido à profundidade de sua explicação. Não se preocupe, você tem um longo histórico de postagens com o qual estou aprendendo e, é claro, recomendo as postagens com as quais aprendi. Obrigado.
dotancohen
@dotancohen - não se preocupe, você aceita quais os A que você sente são os que mais o ajudam como OP 8). Suas habilidades para explicar por que as coisas acontecem são incomparáveis, exceto por Gilles.
slm