Mengapa file biner ini ditransfer melalui "ssh -t" sedang diubah?

29

Saya mencoba menyalin file melalui SSH , tetapi tidak dapat digunakan scpkarena tidak mengetahui nama file yang saya butuhkan. Meskipun file biner kecil dan file teks dapat ditransfer dengan baik, file biner besar dapat diubah. Ini file di server:

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

Ini file setelah saya memindahkannya:

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

Perhatikan bahwa file yang diunduh lebih besar daripada yang ada di server! Namun, jika saya melakukan hal yang sama dengan file yang sangat kecil, maka semuanya berfungsi seperti yang diharapkan:

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

0m3.041s nyata pengguna 0m0.013s sys 0m0.005s

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

Kedua ukuran file adalah 26 byte dalam hal ini.

Mengapa file kecil dapat ditransfer dengan baik, tetapi file besar mendapatkan beberapa byte ditambahkan ke dalamnya?

dotancohen
sumber
10
Itu -tpilihan, yang memecah transfer. Jangan gunakan -tatau -T, kecuali Anda membutuhkannya untuk alasan yang sangat spesifik. Defaultnya berfungsi di sebagian besar kasus, sehingga opsi tersebut sangat jarang dibutuhkan.
kasperd
3
Tidak pernah terpikir saya akan mengatakan ini di abad ini, tetapi Anda mungkin ingin mencoba uuencode dan uudecode jika ssh -t catitu satu-satunya cara untuk mentransfer file.
Mark Plotnick
1
@MarkPlotnick versi modern dari uuencode / uudecode sekarang bernama base64 / base64 -d
Archemar

Jawaban:

60

TL; DR

Jangan gunakan -t. -tmelibatkan pseudo-terminal pada host jarak jauh dan seharusnya hanya digunakan untuk menjalankan aplikasi visual dari terminal.

Penjelasan

Karakter linefeed (juga dikenal sebagai baris baru atau \n) adalah karakter yang ketika dikirim ke terminal memberitahu terminal untuk memindahkan kursor ke bawah.

Namun, ketika Anda menjalankan seq 3di terminal, di situlah seqmenulis 1\n2\n3\nsesuatu seperti /dev/pts/0, Anda tidak melihat:

1
 2
  3

tapi

1
2
3

Mengapa demikian?

Sebenarnya, ketika seq 3(atau ssh host seq 3dalam hal ini) menulis 1\n2\n3\n, terminal melihat 1\r\n2\r\n3\r\n. Yaitu, umpan baris telah diterjemahkan ke carriage-return (tempat terminal memindahkan kursornya kembali ke kiri layar) dan umpan baris.

Itu dilakukan oleh driver perangkat terminal. Lebih tepatnya, dengan disiplin garis perangkat terminal (atau pseudo-terminal), modul perangkat lunak yang berada di kernel.

Anda dapat mengontrol perilaku disiplin garis itu dengan sttyperintah. Terjemahan LF-> CRLFdihidupkan dengan

stty onlcr

(yang umumnya diaktifkan secara default). Anda dapat mematikannya dengan:

stty -onlcr

Atau Anda dapat mematikan semua pemrosesan output dengan:

stty -opost

Jika Anda melakukannya dan menjalankannya seq 3, Anda akan melihat:

$ stty -onlcr; seq 3
1
 2
  3

seperti yang diharapkan.

Sekarang, ketika Anda melakukannya:

seq 3 > some-file

seqtidak lagi menulis ke terminal, itu menulis ke file, tidak ada terjemahan yang dilakukan. Begitu some-filejuga mengandung 1\n2\n3\n. Terjemahan hanya dilakukan saat menulis ke perangkat terminal. Dan itu hanya dilakukan untuk tampilan.

sama halnya, ketika Anda melakukannya:

ssh host seq 3

sshmenulis 1\n2\n3\napa pun sshkeluarannya.

Apa yang sebenarnya terjadi adalah bahwa seq 3perintah dijalankan hostdengan stdout diarahkan ke sebuah pipa. The sshserver pada tuan membaca ujung pipa dan mengirimkannya melalui saluran terenkripsi untuk Anda sshklien dan sshklien menulis itu ke stdout, dalam kasus Anda perangkat pseudo-terminal, di manaLF dijabarkan ke CRLFuntuk ditampilkan.

Banyak aplikasi interaktif berperilaku berbeda ketika stdout mereka bukan terminal. Misalnya, jika Anda menjalankan:

ssh host vi

vitidak suka, tidak suka outputnya ke pipa. Ia berpikir itu tidak berbicara dengan perangkat yang dapat memahami urutan pelarian posisi kursor misalnya.

Jadi sshada -tpilihan untuk itu. Dengan opsi itu, server ssh pada host menciptakan perangkat pseudo-terminal dan menjadikan stdout (dan stdin, dan stderr) dari vi. Apa yang vimenulis pada perangkat terminal melewati disiplin garis pseudo-terminal jarak jauh dan dibaca oleh sshserver dan dikirim melalui saluran terenkripsi ke sshklien. Itu sama seperti sebelumnya kecuali bahwa alih-alih menggunakan pipa , sshserver menggunakan pseudo-terminal .

Perbedaan lainnya adalah pada sisi klien, sshklien mengatur terminal dalam rawmode. Itu berarti bahwa tidak ada terjemahan yang dilakukan di sana ( opostdinonaktifkan dan juga perilaku sisi input lainnya). Misalnya, ketika Anda mengetik Ctrl-C, alih-alih menyela ssh, ^Ckarakter itu dikirim ke sisi jarak jauh, di mana garis disiplin terminal pseudo jarak jauh mengirimkan interupsi ke perintah jarak jauh.

Saat kamu melakukan:

ssh -t host seq 3

seq 3menulis 1\n2\n3\nke stdout-nya, yang merupakan perangkat pseudo-terminal. Karenanya onlcr, itu diterjemahkan pada host ke 1\r\n2\r\n3\r\ndan dikirim kepada Anda melalui saluran terenkripsi. Di pihak Anda tidak ada terjemahan ( onlcrdinonaktifkan), sehingga 1\r\n2\r\n3\r\nditampilkan tidak tersentuh (karenaraw mode) dan dengan benar di layar emulator terminal Anda.

Sekarang, jika Anda melakukannya:

ssh -t host seq 3 > some-file

Tidak ada perbedaan dari atas. sshakan menulis hal yang sama:, 1\r\n2\r\n3\r\ntetapi kali ini menjadi some-file.

Jadi pada dasarnya semua LFdalam output seqtelah diterjemahkan ke CRLFdalam some-file.

Itu sama jika Anda melakukannya:

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

Semua LFkarakter (0x0a byte) sedang diterjemahkan ke dalam CRLF (0x0d 0x0a).

Mungkin itulah alasan kerusakan pada file Anda. Dalam kasus file kedua yang lebih kecil, kebetulan file tersebut tidak mengandung 0x0a byte, sehingga tidak ada korupsi.

Perhatikan bahwa Anda bisa mendapatkan berbagai jenis korupsi dengan pengaturan tty yang berbeda. Jenis korupsi potensial lainnya yang terkait dengan -tadalah jika file startup Anda di host( ~/.bashrc, ~/.ssh/rc...) menulis sesuatu ke stderr mereka, karena dengan -tstdout dan stderr dari remote shell akhirnya digabung menjadi sshstdout (mereka berdua pergi ke pseudo perangkat -terminal).

Anda tidak ingin remote cat untuk output ke perangkat terminal di sana.

Kamu ingin:

ssh host cat remote-file > local-file

Anda bisa melakukannya:

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

Itu akan bekerja (kecuali dalam penulisan kasus korupsi stderr yang dibahas di atas), tetapi bahkan itu akan menjadi kurang optimal karena Anda akan memiliki lapisan pseudo-terminal yang tidak perlu dijalankan host.


Lebih menyenangkan:

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

BAIK.

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

LF diterjemahkan ke CRLF

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

OKE lagi.

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

Itu bentuk lain dari post-processing output yang dapat dilakukan oleh disiplin jalur 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

sshmenolak untuk memberi tahu server untuk menggunakan pseudo-terminal ketika inputnya sendiri bukan terminal. Anda dapat memaksanya dengan -tt:

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

Disiplin garis lebih banyak melakukan di sisi input.

Di sini, echotidak membaca inputnya atau diminta untuk output itu x\r\n\njadi dari mana asalnya? Itu adalah lokal echodari pseudo-terminal jarak jauh ( stty echo). The sshserver makan x\nitu dibaca dari klien ke sisi master dari pseudo-remote terminal. Dan garis disiplin itu menggemakannya kembali (sebelum stty opostdijalankan itulah sebabnya kita melihat a CRLFdan tidak LF). Itu terlepas dari apakah aplikasi jarak jauh membaca sesuatu dari stdin atau tidak.

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

The 0x3karakter bergema kembali sebagai ^C( ^dan C) karena stty echoctldan shell dan tidur menerima SIGINT karena stty isig.

Jadi sementara:

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

sudah cukup buruk, tapi

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

mentransfer file dengan cara sebaliknya jauh lebih buruk. Anda akan mendapatkan beberapa CR -> terjemahan LF, tetapi juga masalah dengan semua karakter khusus ( ^C, ^Z, ^D, ^?, ^S...) dan juga remote cattidak akan melihat eof ketika akhir local-filetercapai, hanya ketika ^Ddikirim setelah \r, \natau lainnya ^Dseperti ketika melakukan cat > filedi terminal Anda.

Stéphane Chazelas
sumber
5

Saat menggunakan metode itu untuk menyalin file, file-file tersebut tampak berbeda.

Server jarak jauh

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

Server lokal

Menjalankan ssh ... catperintah Anda :

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

Hasil dalam file ini di server lokal:

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

Menyelidiki mengapa?

Menyelidiki file yang dihasilkan di sisi lokal menunjukkan bahwa itu sudah rusak. Jika Anda -tberalih dari sshperintah Anda maka itu berfungsi seperti yang diharapkan.

$ 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

Checksum sekarang juga berfungsi:

# 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
sumber
Sim, terima kasih. Meskipun sebenarnya Anda adalah orang pertama yang mengirim jawaban yang benar, saya memilih Stéphane untuk jawaban yang dipilih karena kedalaman penjelasannya. Tidak perlu khawatir, Anda memiliki riwayat posting yang panjang yang saya pelajari, dan tentu saja saya memperbaiki tulisan-tulisan yang saya pelajari. Terima kasih.
dotancohen
@dotancohen - jangan khawatir, Anda menerima A yang menurut Anda adalah yang paling membantu Anda sebagai OP 8 -). Kemampuannya untuk menjelaskan mengapa sesuatu terjadi tidak ada bandingannya, kecuali oleh Gilles.
slm