Pourquoi ce fichier binaire transféré sur "ssh -t" est-il modifié?

29

J'essaie de copier des fichiers sur SSH , mais je ne peux pas l'utiliser scpcar je ne connais pas le nom de fichier exact dont j'ai besoin. Bien que les petits fichiers binaires et les fichiers texte se transfèrent correctement, les gros fichiers binaires sont modifiés. Voici le fichier sur le serveur:

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

Voici le fichier après l'avoir déplacé:

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

Notez que le fichier téléchargé est plus gros que celui du serveur! Cependant, si je fais de même avec un très petit fichier, alors tout fonctionne comme prévu:

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

réel 0m3.041s utilisateur 0m0.013s sys 0m0.005s

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

Les deux tailles de fichier sont de 26 octets dans ce cas.

Pourquoi les petits fichiers peuvent-ils être transférés correctement, mais les fichiers volumineux reçoivent des octets ajoutés?

dotancohen
la source
10
C'est l' -toption qui rompt le transfert. N'utilisez pas -tou -T, sauf si vous en avez besoin pour une raison très précise. La valeur par défaut fonctionne dans la grande majorité des cas, donc ces options sont très rarement nécessaires.
kasperd
3
Je n'aurais jamais pensé le dire au cours de ce siècle, mais vous voudrez peut-être essayer uuencode et uudecode si ssh -t catc'est la seule façon de transférer des fichiers.
Mark Plotnick
1
@MarkPlotnick version moderne de uuencode / uudecode s'appelle désormais base64 / base64 -d
Archemar

Réponses:

60

TL; DR

Ne pas utiliser -t. -timplique un pseudo-terminal sur l'hôte distant et ne doit être utilisé que pour exécuter des applications visuelles à partir d'un terminal.

Explication

Le caractère de saut de ligne (également appelé retour à la ligne ou \n) est celui qui, lorsqu'il est envoyé à un terminal, indique au terminal de déplacer son curseur vers le bas.

Pourtant, lorsque vous exécutez seq 3dans un terminal, c'est là seqqu'écrit 1\n2\n3\nquelque chose comme /dev/pts/0, vous ne voyez pas:

1
 2
  3

mais

1
2
3

Pourquoi donc?

En fait, quand seq 3(ou ssh host seq 3d'ailleurs) écrit 1\n2\n3\n, le terminal voit 1\r\n2\r\n3\r\n. Autrement dit, les sauts de ligne ont été traduits en retour chariot (sur lequel les terminaux déplacent leur curseur vers la gauche de l'écran) et en saut de ligne.

Cela se fait par le pilote de périphérique terminal. Plus exactement, par la discipline de ligne du terminal (ou pseudo-terminal), un module logiciel qui réside dans le noyau.

Vous pouvez contrôler le comportement de cette discipline de ligne avec la sttycommande. La traduction de LF-> CRLFest activée avec

stty onlcr

(qui est généralement activé par défaut). Vous pouvez le désactiver avec:

stty -onlcr

Ou vous pouvez désactiver tous les traitements de sortie avec:

stty -opost

Si vous faites cela et exécutez seq 3, vous verrez alors:

$ stty -onlcr; seq 3
1
 2
  3

comme prévu.

Maintenant, quand vous le faites:

seq 3 > some-file

seqn'écrit plus sur un terminal, il écrit dans un fichier, aucune traduction n'est en cours. Contient some-filedonc 1\n2\n3\n. La traduction n'est effectuée que lors de l'écriture sur un terminal. Et cela n'est fait que pour l'affichage.

de même, lorsque vous faites:

ssh host seq 3

sshécrit 1\n2\n3\nquelle que soit sshla sortie.

Ce qui se passe réellement, c'est que la seq 3commande est exécutée hostavec sa sortie standard redirigée vers un canal. Le sshserveur sur l'hôte lit l'autre extrémité du canal et l'envoie sur le canal crypté à votre sshclient et le sshclient l'écrit sur sa sortie standard, dans votre cas un périphérique pseudo-terminal, où LFs sont traduits en CRLFpour être affichés.

De nombreuses applications interactives se comportent différemment lorsque leur sortie standard n'est pas un terminal. Par exemple, si vous exécutez:

ssh host vi

vine l'aime pas, il n'aime pas que sa sortie passe dans un tuyau. Il pense qu'il ne parle pas à un appareil capable de comprendre les séquences d'échappement de positionnement du curseur par exemple.

A donc sshla -tpossibilité pour cela. Avec cette option, le serveur ssh sur l'hôte crée un périphérique pseudo-terminal et en fait la stdout (et stdin et stderr) de vi. Ce qui viécrit sur ce terminal passe par cette discipline de ligne de pseudo-terminal distant et est lu par le sshserveur et envoyé sur le canal crypté au sshclient. C'est la même chose qu'avant, sauf qu'au lieu d'utiliser un canal , le sshserveur utilise un pseudo-terminal .

L'autre différence est que du côté client, le sshclient met le terminal en rawmode. Cela signifie qu'aucune traduction n'y est effectuée ( opostest désactivée et également d'autres comportements côté entrée). Par exemple, lorsque vous tapez Ctrl-C, au lieu d'interrompre ssh, ce ^Ccaractère est envoyé vers le côté distant, où la discipline de ligne du pseudo-terminal distant envoie l' interruption à la commande distante.

Quand vous faites:

ssh -t host seq 3

seq 3écrit 1\n2\n3\nsur sa sortie standard, qui est un périphérique pseudo-terminal. En raison de onlcr, qui obtient traduit sur l' hôte pour 1\r\n2\r\n3\r\net vous sera envoyé sur le canal crypté. De votre côté, il n'y a pas de traduction ( onlcrdésactivée), 1\r\n2\r\n3\r\nest donc affiché intact (en raison du rawmode) et correctement sur l'écran de votre émulateur de terminal.

Maintenant, si vous le faites:

ssh -t host seq 3 > some-file

Il n'y a aucune différence d'en haut. sshva écrire la même chose:, 1\r\n2\r\n3\r\nmais cette fois en some-file.

Donc, fondamentalement, toutes les LFsorties de seqont été traduites CRLFen some-file.

C'est la même chose si vous le faites:

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

Tous les LFcaractères (0x0a octets) sont en cours de traduction en CRLF (0x0d 0x0a).

C'est probablement la raison de la corruption de votre fichier. Dans le cas du deuxième fichier plus petit, il se trouve que le fichier ne contient pas d'octets 0x0a, il n'y a donc pas de corruption.

Notez que vous pouvez obtenir différents types de corruption avec différents paramètres tty. Un autre type potentiel de corruption associé à -test si vos fichiers de démarrage sur host( ~/.bashrc, ~/.ssh/rc...) écrivent des choses sur leur stderr, car avec -tle stdout et le stderr du shell distant finissent par être fusionnés dans sshle stdout de (ils vont tous les deux au pseudo - dispositif terminal).

Vous ne voulez pas que la télécommande catémette sur un périphérique terminal.

Tu veux:

ssh host cat remote-file > local-file

Vous pourriez faire:

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

Cela fonctionnerait (sauf dans l' écriture dans le cas de corruption stderr discuté ci-dessus), mais même cela serait sous-optimal car vous auriez cette couche pseudo-terminale inutile en cours d'exécution host.


Un peu plus de plaisir:

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

D'ACCORD.

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

LF traduit en CRLF

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

OK encore.

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

C'est une autre forme de post-traitement de sortie qui peut être effectuée par la discipline de la ligne 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

sshrefuse de dire au serveur d'utiliser un pseudo-terminal lorsque sa propre entrée n'est pas un terminal. Vous pouvez cependant le forcer avec -tt:

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

La discipline de ligne en fait beaucoup plus du côté des entrées.

Ici, echone lit pas son entrée et n'a pas été invité à le sortir, x\r\n\nalors d'où vient-il? C'est le local echodu pseudo-terminal distant ( stty echo). Le sshserveur alimente la x\nlecture du client vers le côté maître du pseudo-terminal distant. Et la discipline de ligne qui en fait écho (avant stty opostest exécutée, c'est pourquoi nous voyons un CRLFet non LF). Cela est indépendant du fait que l'application distante lit ou non quoi que ce soit à partir de stdin.

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

Le 0x3caractère est renvoyé en écho comme ^C( ^et C) à cause de stty echoctlet le shell et sleep reçoivent un SIGINT car stty isig.

Donc pendant:

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

est déjà assez mauvais, mais

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

transférer des fichiers dans l'autre sens est bien pire. Vous obtenez des CR -> traduction de LF, mais aussi des problèmes avec tous les caractères spéciaux ( ^C, ^Z, ^D, ^?, ^S...) ainsi que la télécommande catne verra pas EOF lorsque la fin local-fileest atteint, seulement quand ^Dest envoyé après \r, \nou un autre ^Dcomme lorsque vous faites cat > filedans votre terminal.

Stéphane Chazelas
la source
5

Lorsque vous utilisez cette méthode pour copier le fichier, les fichiers semblent être différents.

Serveur distant

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

Serveur local

Exécuter votre ssh ... catcommande:

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

Résultats dans ce fichier sur le serveur local:

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

Enquêter pourquoi?

L'étude du fichier résultant du côté local montre qu'il a été corrompu. Si vous retirez le -tcommutateur de votre sshcommande, cela fonctionne comme prévu.

$ 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

Les sommes de contrôle fonctionnent désormais aussi:

# 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
la source
Merci Sim. Bien qu'en fait vous ayez été le premier à poster la bonne réponse, j'ai choisi Stéphane pour la réponse choisie en raison de la profondeur de son explication. Ne vous inquiétez pas, vous avez une longue histoire de messages que j'apprends, et bien sûr, je vote de manière positive ces messages que j'apprends. Merci.
dotancohen
@dotancohen - pas de soucis, vous acceptez quels que soient les A que vous pensez être ceux qui vous aident le plus en tant que PO 8-). Ses capacités à expliquer pourquoi les choses se passent sont sans égal, sauf par Gilles.
slm