Mengapa beberapa shell `read` builtin gagal membaca seluruh baris dari file di` / proc`?

19

Dalam beberapa shell seperti Bourne, readbuiltin tidak dapat membaca seluruh baris dari file di /proc(perintah di bawah ini harus dijalankan zsh, ganti $=shelldengan $shellshell lain):

$ for shell in bash dash ksh mksh yash zsh schily-sh heirloom-sh "busybox sh"; do
  printf '[%s]\n' "$shell"
  $=shell -c 'IFS= read x </proc/sys/fs/file-max; echo "$x"'       
done
[bash]
602160
[dash]
6
[ksh]
602160
[mksh]
6
[yash]
6
[zsh]
6
[schily-sh]
602160
[heirloom-sh]
602160
[busybox sh]
6

readstandar mensyaratkan input standar perlu berupa file teks , apakah persyaratan itu menyebabkan beragam perilaku?


Baca definisi file teks POSIX , saya melakukan beberapa verifikasi:

$ od -t a </proc/sys/fs/file-max 
0000000   6   0   2   1   6   0  nl
0000007

$ find /proc/sys/fs -type f -name 'file-max'
/proc/sys/fs/file-max

Tidak ada NULkarakter dalam konten /proc/sys/fs/file-max, dan juga findmelaporkannya sebagai file biasa (Apakah ini bug dalam find?).

Saya kira shell melakukan sesuatu di bawah tenda, seperti file:

$ file /proc/sys/fs/file-max
/proc/sys/fs/file-max: empty
cuonglm
sumber

Jawaban:

31

Masalahnya adalah bahwa /procfile - file di Linux muncul sebagai file teks sejauh stat()/fstat()yang bersangkutan, tetapi tidak berperilaku seperti itu.

Karena ini adalah data dinamis, Anda hanya dapat melakukan satu read()panggilan sistem pada mereka (setidaknya untuk sebagian dari mereka). Melakukan lebih dari satu bisa membuat Anda dua potongan dari dua konten yang berbeda, jadi alih-alih sepertinya yang kedua read()hanya mengembalikan apa-apa (artinya end-of-file) (kecuali Andalseek() kembali ke awal (dan hanya ke awal)).

The readutilitas perlu membaca isi dari file satu byte pada suatu waktu untuk memastikan tidak membaca masa lalu karakter baris baru. Itu yang dashdilakukannya:

 $ strace -fe read dash -c 'read a < /proc/sys/fs/file-max'
 read(0, "1", 1)                         = 1
 read(0, "", 1)                          = 0

Beberapa shell seperti bashmemiliki optimasi untuk menghindari keharusan melakukan begitu banyak read()panggilan sistem. Mereka pertama-tama memeriksa apakah file itu dapat dicari, dan jika demikian, baca dalam potongan-potongan ketika mereka tahu mereka dapat mengembalikan kursor tepat setelah baris baru jika mereka telah membaca melewatinya:

$ strace -e lseek,read bash -c 'read a' < /proc/sys/fs/file-max
lseek(0, 0, SEEK_CUR)                   = 0
read(0, "1628689\n", 128)               = 8

Dengan bash , Anda masih akan memiliki masalah untuk file proc yang lebih dari 128 byte dan hanya dapat dibaca dalam satu panggilan sistem baca.

bashtampaknya juga menonaktifkan pengoptimalan itu ketika -dopsi digunakan.

ksh93dibutuhkan pengoptimalan lebih jauh sehingga menjadi palsu. ksh93 readmemang mencari kembali, tetapi mengingat data tambahan yang telah dibacanya untuk selanjutnya read, sehingga berikutnya read(atau salah satu builtin lain yang membaca data suka catatau head) bahkan tidak mencoba readdata (bahkan jika data tersebut telah dimodifikasi oleh perintah lain di antaranya):

$ seq 10 > a; ksh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 2
$ seq 10 > a; sh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 st
Stéphane Chazelas
sumber
Ah ya, stracepenjelasan berbasis jauh lebih mudah dimengerti!
Stephen Kitt
Terima kasih, data dinamis masuk akal. Jadi bagaimana shell mendeteksi data dinamisnya? Jika saya melakukannya cat /proc/sys/fs/file-max | ..., masalahnya hilang.
cuonglm
3
Shell tidak mendeteksi itu. Fakta bahwa itu adalah data dinamis berarti procfstidak dapat menangani beberapa read(2)panggilan berturut-turut ke file yang sama; perilaku tidak bergantung pada shell. Menggunakan catdan memipakan karya karena catmembaca file dalam potongan cukup besar; readbuilt-in shell kemudian membaca dari pipa satu karakter sekaligus.
Stephen Kitt
1
Ada sedikit solusi kotor di mksh. read -N 10 a < /proc/sys/fs/file-max
Ipor Sircer
1
@IporSircer. Memang. Pekerjaan serupa tampaknya bekerja dengan zsh: read -u0 -k10(atau digunakan sysread; $mapfile[/proc/sys/fs/file-max]tidak berfungsi karena file-file itu tidak dapat mmapdiedit). Dalam kasus apa pun, dengan cangkang apa pun, kita selalu bisa a=$(cat /proc/sys/fs/file-max). Dengan beberapa termasuk mksh, zshdan ksh93, a=$(</proc/sys/fs/file-max)juga berfungsi dan tidak memotong proses untuk melakukan pembacaan.
Stéphane Chazelas
9

Jika Anda tertarik untuk mengetahui alasannya? begitulah, Anda dapat melihat jawabannya di sumber kernel di sini :

    if (!data || !table->maxlen || !*lenp || (*ppos && !write)) {
            *lenp = 0;
            return 0;
    }

Pada dasarnya, pencarian ( *pposbukan 0) tidak diterapkan untuk membaca ( !write) dari nilai sysctl yang merupakan angka. Setiap kali membaca dilakukan dari /proc/sys/fs/file-max, rutin yang dimaksud __do_proc_doulongvec_minmax()dipanggil dari entri file-maxdi dalam tabel konfigurasi dalam file yang sama.

Entri lain, seperti /proc/sys/kernel/poweroff_cmddiimplementasikan melalui proc_dostring()yang memungkinkan pencarian, sehingga Anda dapat melakukannya dd bs=1dan membaca dari shell Anda tanpa masalah.

Perhatikan bahwa karena kernel 2.6, sebagian besar /procbacaan diimplementasikan melalui API baru bernama seq_file dan ini mendukung pencarian sehingga mis. Pembacaan /proc/statseharusnya tidak menyebabkan masalah. The /proc/sys/implementasi, seperti yang kita lihat, tidak menggunakan api ini.

meuh
sumber
3

Pada upaya pertama, ini terlihat seperti bug di shell yang mengembalikan kurang dari Bourne Shell asli atau turunannya kembali (sh, bosh, ksh, heirloom).

Bourne Shell asli mencoba membaca blok (64 byte) varian Bourne Shell yang lebih baru membaca 128 byte, tetapi mereka mulai membaca lagi jika tidak ada karakter baris baru.

Latar belakang: / procfs dan implementasi serupa (misalnya /etc/mtabfile virtual yang dipasang ) memiliki konten dinamis dan stat()panggilan tidak menyebabkan penciptaan kembali konten dinamis terlebih dahulu. Karena alasan ini, ukuran file seperti itu (dari membaca hingga EOF) mungkin berbeda dari apastat() dikembalikan.

Mengingat bahwa standar POSIX memerlukan utilitas untuk mengharapkan pembacaan pendek kapan saja, perangkat lunak yang percaya bahwa read()yang mengembalikan kurang dari jumlah byte yang dipesan adalah indikasi EOF yang rusak. Utilitas yang diimplementasikan dengan benar memanggil read()kedua kalinya jika itu mengembalikan kurang dari yang diharapkan - sampai 0 dikembalikan. Dalam kasus readbuiltin, tentu saja cukup untuk membaca sampai EOF atau sampai NLterlihat.

Jika Anda menjalankan trussatau klon truss, Anda harus dapat memverifikasi perilaku yang salah untuk shell yang hanya kembali 6dalam percobaan Anda.

Dalam kasus khusus ini, tampaknya itu adalah bug kernel Linux, lihat:

$ sdd -debug bs=1 if= /proc/sys/fs/file-max 
Simple copy ...
readbuf  (3, 12AC000, 1) = 1
writebuf (1, 12AC000, 1)
8readbuf  (3, 12AC000, 1) = 0

sdd: Read  1 records + 0 bytes (total of 1 bytes = 0.00k).
sdd: Wrote 1 records + 0 bytes (total of 1 bytes = 0.00k).

Kernel Linux mengembalikan 0 dengan yang kedua readdan ini tentu saja salah.

Kesimpulan: Kerang yang pertama kali mencoba membaca sepotong data yang cukup besar tidak memicu bug kernel Linux ini.

schily
sumber
OK, keluar jawaban dengan verifikasi baru untuk bug kernel Linux.
schily
Ini bukan bug, ini fitur!
Guntram Blohm mendukung Monica
Ini klaim yang sangat aneh.
schily
Ini akan menjadi fitur jika didokumentasikan. Membaca kernel.org/doc/Documentation/filesystems/proc.txt , saya tidak melihat dokumentasi untuk perilaku tersebut. Yang mengatakan, itu jelas berfungsi-sebagaimana-dimaksudkan-oleh-implementor, jadi jika ini dianggap bug, itu bug dalam desain, bukan implementasinya.
Charles Duffy
0

File-file di bawah / proc kadang-kadang menggunakan karakter NULL untuk memisahkan bidang di dalam file. Tampaknya membaca tidak dapat menangani ini.

Tony George
sumber