Perché questo file binario trasferito su "ssh -t" viene modificato?


29

Sto cercando di copiare i file su SSH , ma non riesco a utilizzarli scpperché non conosco il nome esatto di cui ho bisogno. Sebbene piccoli file binari e file di testo vengano trasferiti correttamente, i file binari di grandi dimensioni vengono modificati. Ecco il file sul server:

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

Ecco il file dopo averlo spostato su:

local$ time ssh me@server.com -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

Nota che il file scaricato è più grande di quello sul server! Tuttavia, se faccio lo stesso con un file molto piccolo, allora tutto funziona come previsto:

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

local$ time ssh me@server.com -t 'cat /path/to/hello.txt.gz' > hi.txt.gz

reale 0m3.041s utente 0m0.013s sys 0m0.005s

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

In questo caso entrambe le dimensioni del file sono 26 byte.

Perché i file di piccole dimensioni potrebbero essere trasferiti correttamente, ma ai file di grandi dimensioni potrebbero essere aggiunti alcuni byte?


10
È l' -topzione che interrompe il trasferimento. Non utilizzare -to -T, a meno che non sia necessario per un motivo molto specifico. L'impostazione predefinita funziona nella stragrande maggioranza dei casi, quindi queste opzioni sono molto raramente necessarie.
Kasperd,

3
Non avrei mai pensato di dirlo in questo secolo, ma potresti voler provare uuencode e uudecode se ssh -t catè l'unico modo per trasferire file.
Mark Plotnick,

1
@MarkPlotnick La versione moderna di uuencode / uudecode è ora denominata base64 / base64 -d
Archemar il

Risposte:


60

TL; DR

Non usare -t. -tcomporta uno pseudo-terminale sull'host remoto e deve essere utilizzato solo per eseguire applicazioni visive da un terminale.

Spiegazione

Il carattere di avanzamento riga (noto anche come newline o \n) è quello che, quando inviato a un terminale, dice al terminale di spostare il cursore verso il basso.

Tuttavia, quando corri seq 3in un terminale, è lì che seqscrive 1\n2\n3\nqualcosa del genere /dev/pts/0, non vedi:

1
 2
  3

ma

1
2
3

Perché?

In realtà, quando seq 3(o ssh host seq 3per quella materia) scrive 1\n2\n3\n, il terminale vede 1\r\n2\r\n3\r\n. In altre parole, gli avanzamenti di riga sono stati tradotti in ritorno a capo (su cui i terminali spostano il cursore indietro a sinistra dello schermo) e avanzamento di riga.

Questo viene fatto dal driver del dispositivo terminale. Più precisamente, dalla disciplina di linea del dispositivo terminale (o pseudo-terminale), un modulo software che risiede nel kernel.

Puoi controllare il comportamento di quella disciplina di riga con il sttycomando. La traduzione di LF-> CRLFè attivata con

stty onlcr

(che è generalmente abilitato per impostazione predefinita). Puoi disattivarlo con:

stty -onlcr

Oppure puoi disattivare tutta l'elaborazione dell'output con:

stty -opost

Se lo fai e corri seq 3, vedrai:

$ stty -onlcr; seq 3
1
 2
  3

come previsto.

Ora, quando lo fai:

seq 3 > some-file

seqnon sta più scrivendo su un terminale, sta scrivendo in un file, non è in corso alcuna traduzione. Quindi some-filecontiene 1\n2\n3\n. La traduzione viene eseguita solo quando si scrive su un dispositivo terminale. Ed è fatto solo per la visualizzazione.

allo stesso modo, quando lo fai:

ssh host seq 3

sshsta scrivendo 1\n2\n3\nindipendentemente sshdall'output.

Ciò che effettivamente accade è che il seq 3comando viene eseguito hostcon il suo stdout reindirizzato a una pipe. Il sshserver sull'host legge l'altra estremità del pipe e lo invia al sshclient tramite il canale crittografato e il sshclient lo scrive sul suo stdout, nel tuo caso un dispositivo pseudo-terminale, dove LFvengono tradotti CRLFper la visualizzazione.

Molte applicazioni interattive si comportano diversamente quando il loro stdout non è un terminale. Ad esempio, se si esegue:

ssh host vi

vinon gli piace, non gli piace che il suo output vada in pipe. Pensa che non stia parlando con un dispositivo in grado di comprendere, ad esempio, le sequenze di escape del posizionamento del cursore.

Quindi sshha l' -topzione per quello. Con tale opzione, il server ssh sull'host crea un dispositivo pseudo-terminale e lo rende stdout (e stdin e stderr) di vi. Ciò che viscrive su quel dispositivo terminale passa attraverso quella disciplina di linea pseudo-terminale remota e viene letto dal sshserver e inviato al sshclient tramite il canale crittografato . È lo stesso di prima tranne che invece di usare una pipe , il sshserver usa uno pseudo-terminale .

L'altra differenza è che sul lato client, il sshclient imposta il terminale in rawmodalità. Ciò significa che non viene eseguita alcuna traduzione ( opostè disabilitato e anche altri comportamenti sul lato input). Ad esempio, quando si digita Ctrl-C, invece di interrompere ssh, quel ^Ccarattere viene inviato al lato remoto, dove la disciplina di linea dello pseudo-terminale remoto invia l' interrupt al comando remoto.

Quando lo fai:

ssh -t host seq 3

seq 3scrive 1\n2\n3\nsul suo stdout, che è un dispositivo pseudo-terminale. A causa di onlcr, che viene tradotto su un host per 1\r\n2\r\n3\r\ne inviato sul canale criptato. Dalla tua parte non c'è traduzione ( onlcrdisabilitato), quindi 1\r\n2\r\n3\r\nviene visualizzato intatto (a causa della rawmodalità) e correttamente sullo schermo dell'emulatore di terminale.

Ora, se lo fai:

ssh -t host seq 3 > some-file

Non c'è alcuna differenza dall'alto. sshscriverò la stessa cosa:, 1\r\n2\r\n3\r\nma questa volta in some-file.

Quindi praticamente tutto l' LFoutput di seqè stato tradotto CRLFin some-file.

È lo stesso se lo fai:

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

Tutti i LFcaratteri (0x0a byte) vengono tradotti in CRLF (0x0d 0x0a).

Questo è probabilmente il motivo della corruzione nel tuo file. Nel caso del secondo file più piccolo, accade che il file non contenga byte 0x0a, quindi non c'è corruzione.

Si noti che è possibile ottenere diversi tipi di corruzione con impostazioni tty diverse. Un altro potenziale tipo di corruzione associato -tè se i tuoi file di avvio su host( ~/.bashrc, ~/.ssh/rc...) scrivono cose sul loro stderr, perché con -tlo stdout e lo stderr della shell remota finiscono per essere uniti nello sshstdout (entrambi vanno allo pseudo dispositivo terminale).

Non si desidera che il telecomando venga catemesso su un dispositivo terminale lì.

Tu vuoi:

ssh host cat remote-file > local-file

Potresti fare:

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

Funzionerebbe (tranne che nella stesura del caso di corruzione stderr discusso in precedenza), ma anche quello sarebbe subottimale poiché si avrebbe lo strato pseudo-terminale non necessario in esecuzione host.


Ancora più divertente:

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

OK.

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

LF tradotto in CRLF

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

OK di nuovo

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

Questa è un'altra forma di post-elaborazione dell'output che può essere eseguita dalla disciplina della linea terminale.

$ 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

sshsi rifiuta di dire al server di utilizzare uno pseudo-terminale quando il proprio input non è un terminale. Puoi forzarlo con -ttperò:

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

La disciplina di linea fa molto di più sul lato input.

Qui, echonon legge il suo input né è stato chiesto di emetterlo, x\r\n\nquindi da dove viene? Questo è il locale echodello pseudo-terminale remoto ( stty echo). Il sshserver sta alimentando la x\nlettura dal client al lato master dello pseudo-terminale remoto. E la disciplina di linea di ciò riecheggia (prima che stty opostvenga eseguito, ecco perché vediamo un CRLFe non LF). Questo è indipendente dal fatto che l'applicazione remota legga o meno qualcosa da stdin.

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

Il 0x3personaggio viene ripetuto come ^C( ^e C) a causa di stty echoctle shell e sleep ricevono un SIGINT perché stty isig.

Allora mentre:

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

è abbastanza male, ma

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

trasferire i file dall'altra parte è molto peggio. Otterrete alcuni CR -> Traduzione LF, ma anche problemi con tutti i caratteri speciali ( ^C, ^Z, ^D, ^?, ^S...) e anche il telecomando catnon vedrà EOF quando la fine del local-fileraggiungimento, solo quando ^Dviene inviato dopo una \r, \no un altro ^Dcome quando fai cat > filenel tuo terminale.


5

Quando si utilizza questo metodo per copiare il file, i file sembrano essere diversi.

Server remoto

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

Server locale

Eseguendo il ssh ... catcomando:

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

Risultati in questo file sul server locale:

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

Indagare perché?

L'analisi del file risultante sul lato locale mostra che è stato danneggiato. Se si toglie l' -tinterruttore dal sshcomando, allora funziona come previsto.

$ 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

I checksum ora funzionano anche:

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

# local server
$ md5sum vim_cfg.tgz 
9e70b036836dfdf2871e76b3636a72c6  vim_cfg.tgz

Grazie Sim. Sebbene in effetti tu sia stato il primo a pubblicare la risposta corretta, ho selezionato Stéphane per la risposta scelta a causa della profondità della sua spiegazione. Non preoccuparti, hai una lunga storia di post da cui sto imparando e, naturalmente, ho votato a favore di quei post da cui ho imparato. Grazie.
dotancohen,

@dotancohen - non preoccuparti, accetti quali mai A pensi che siano quelli che ti aiutano come OP di più Cool. Le sue capacità di spiegare perché le cose accadono non hanno rivali, tranne che per Gilles.
slm
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.