ทำไมเชลล์บางตัว `read` builtin ไม่สามารถอ่านบรรทัดทั้งหมดจากไฟล์ใน` / proc` ได้?


19

ในเชลล์คล้ายบอร์นบางตัวบิวอินreadไม่สามารถอ่านทั้งบรรทัดจากไฟล์ใน/proc(คำสั่งด้านล่างควรจะรันzshแทนที่$=shellด้วย$shellเชลล์อื่น ๆ ):

$ 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

readมาตรฐานต้องการอินพุตมาตรฐานต้องเป็นไฟล์ข้อความข้อกำหนดนั้นเป็นสาเหตุของพฤติกรรมที่หลากหลายใช่หรือไม่


อ่านคำนิยาม POSIX ของไฟล์ข้อความฉันจะตรวจสอบบาง:

$ 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

ไม่มีNULตัวละครในเนื้อหา/proc/sys/fs/file-maxและยังfindรายงานว่าเป็นไฟล์ปกติ (นี่เป็นข้อผิดพลาดfindหรือไม่)

ฉันเดาว่าเปลือกทำอะไรบางอย่างภายใต้ประทุนเช่นfile:

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

คำตอบ:


31

ปัญหาคือ/procไฟล์เหล่านั้นบน Linux ปรากฏเป็นไฟล์ข้อความเท่าที่stat()/fstat()เกี่ยวข้อง แต่ไม่ทำเช่นนั้น

เนื่องจากเป็นข้อมูลแบบไดนามิกคุณสามารถทำการread()เรียกระบบเพียงครั้งเดียวเท่านั้น(อย่างน้อยก็บางระบบ) การทำมากกว่าหนึ่งอาจทำให้คุณได้รับเนื้อหาสองชิ้นที่แตกต่างกันสองรายการดังนั้นดูเหมือนว่าข้อความที่สองread()จะไม่ส่งคืนอะไรเลย (หมายถึงจุดสิ้นสุดไฟล์) (เว้นแต่คุณlseek()จะกลับไปที่จุดเริ่มต้นเท่านั้น)

readยูทิลิตี้ต้องการที่จะอ่านเนื้อหาของแฟ้มหนึ่งไบต์ครั้งเพื่อให้แน่ใจว่าจะไม่อ่านตัวอักษรที่ผ่านมาการขึ้นบรรทัดใหม่ นั่นคือสิ่งที่dashทำ:

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

เชลล์บางตัวbashมีการปรับให้เหมาะสมเพื่อหลีกเลี่ยงการread()เรียกใช้ระบบจำนวนมาก พวกเขาตรวจสอบก่อนว่าไฟล์นั้นสามารถค้นหาได้หรือไม่และหากเป็นเช่นนั้นให้อ่านเป็นชิ้น ๆ จากนั้นพวกเขารู้ว่าพวกเขาสามารถวางเคอร์เซอร์กลับหลังบรรทัดใหม่หากพวกเขาได้อ่านผ่าน:

$ 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

ด้วยbashคุณจะยังคงมีปัญหาสำหรับไฟล์ proc ที่มีขนาดใหญ่กว่า 128 ไบต์และสามารถอ่านได้ในการเรียกระบบที่อ่านเพียงครั้งเดียว

bashดูเหมือนว่าจะปิดการใช้งานการเพิ่มประสิทธิภาพนั้นเมื่อ-dมีการใช้ตัวเลือก

ksh93ใช้การปรับให้เหมาะสมให้ดียิ่งขึ้นเพื่อให้กลายเป็นของปลอม ksh93 readทำการค้นหา แต่จดจำข้อมูลเพิ่มเติมที่ได้อ่านสำหรับถัดไปreadดังนั้นถัดไปread(หรือบิวอินอื่น ๆ ที่อ่านข้อมูลเหมือนcatหรือhead) ไม่พยายามแม้แต่readข้อมูล (แม้ว่าข้อมูลนั้นจะถูกแก้ไขโดย คำสั่งอื่น ๆ ในระหว่าง):

$ 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

อ่าใช่straceคำอธิบายที่เข้าใจง่ายกว่ามาก!
Stephen Kitt

ขอขอบคุณข้อมูลแบบไดนามิกที่สมเหตุสมผล ดังนั้นเชลล์ตรวจพบว่าเป็นข้อมูลแบบไดนามิกได้อย่างไร ถ้าฉันทำcat /proc/sys/fs/file-max | ...แล้วปัญหาก็หายไป
cuonglm

3
เปลือกตรวจไม่พบ ความจริงที่ว่าเป็นข้อมูลแบบไดนามิกหมายความว่าprocfsไม่สามารถจัดการการread(2)โทรติดต่อหลายไฟล์แบบต่อเนื่องได้ พฤติกรรมไม่ได้ขึ้นอยู่กับเปลือก การใช้catและการไพพ์ลิ่งทำงานได้เพราะcatอ่านไฟล์ที่มีขนาดใหญ่พอ readบิวด์อินของเชลล์จะอ่านจากไพพ์ทีละตัว
Stephen Kitt

1
mkshมีวิธีแก้ปัญหาบิตที่สกปรกคือ read -N 10 a < /proc/sys/fs/file-max
Ipor Sircer

1
@IporSircer จริง ดูเหมือนว่าการทำงานที่คล้ายกันในการทำงานกับzsh: read -u0 -k10(หรือใช้sysread; $mapfile[/proc/sys/fs/file-max]ไม่ทำงานเนื่องจากไฟล์เหล่านั้นไม่สามารถแก้ไขได้mmap) ในกรณีใด ๆ กับเปลือกใด ๆ a=$(cat /proc/sys/fs/file-max)หนึ่งสามารถเสมอไป กับบางส่วนรวมทั้งmksh, zshและksh93, a=$(</proc/sys/fs/file-max)นอกจากนี้ยังทำงานและไม่ได้แยกกระบวนการที่จะทำอ่านที่
Stéphane Chazelas

9

หากคุณสนใจที่จะรู้ว่าทำไม นี่คือดังนั้นคุณสามารถดูคำตอบในแหล่งเคอร์เนลที่นี่ :

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

โดยทั่วไปการค้นหา ( *pposไม่ใช่ 0) จะไม่ถูกนำไปใช้สำหรับการอ่าน ( !write) ของค่า sysctl ที่เป็นตัวเลข เมื่อใดก็ตามที่อ่านเสร็จ/proc/sys/fs/file-maxแล้วรูทีนที่__do_proc_doulongvec_minmax()เป็นปัญหา จะถูกเรียกจากรายการสำหรับfile-maxในตารางการกำหนดค่าในไฟล์เดียวกัน

รายการอื่น ๆ เช่น/proc/sys/kernel/poweroff_cmdมีการใช้งานผ่าน proc_dostring()ซึ่งจะอนุญาตให้ค้นหาดังนั้นคุณสามารถทำdd bs=1มันและอ่านจากเปลือกของคุณโดยไม่มีปัญหา

โปรดทราบว่าเนื่องจากเคอร์เนล 2.6 การ/procอ่านส่วนใหญ่มีการใช้งานผ่าน API ใหม่ที่เรียกว่า seq_file และสิ่งนี้รองรับการค้นหาดังนั้นเช่นการอ่าน/proc/statไม่ควรทำให้เกิดปัญหา การ/proc/sys/ดำเนินการตามที่เราเห็นไม่ได้ใช้ API นี้


3

ในความพยายามครั้งแรกดูเหมือนว่าบั๊กในเชลล์ที่ส่งกลับน้อยกว่าบอร์นเชลล์จริงหรือการส่งคืนอนุพันธ์ (sh, bosh, ksh, มรดกสืบทอด)

Bourne Shell ดั้งเดิมพยายามอ่านบล็อก (64 bytes) ที่ใหม่กว่า Bourne Shell รุ่นที่อ่านได้ 128 ไบต์ แต่จะเริ่มอ่านอีกครั้งหากไม่มีอักขระบรรทัดใหม่

พื้นหลัง: / procfs และการใช้งานที่คล้ายกัน (เช่น/etc/mtabไฟล์เสมือนที่ติดตั้ง) มีเนื้อหาแบบไดนามิกและการstat()โทรไม่ทำให้เกิดการสร้างเนื้อหาแบบไดนามิกขึ้นอีกครั้งก่อน ด้วยเหตุนี้ขนาดของไฟล์ดังกล่าว (จากการอ่านจนกระทั่ง EOF) อาจแตกต่างจากสิ่งที่stat()ส่งคืน

ระบุว่ามาตรฐาน POSIX ต้องการระบบสาธารณูปโภคในการอ่านค่าสั้นๆ ได้ตลอดเวลาซอฟต์แวร์ที่เชื่อว่า a read()ที่ส่งคืนน้อยกว่าจำนวนไบต์ที่สั่งซื้อนั้นเป็นข้อบ่งชี้ EOF ยูทิลิตีที่นำมาใช้อย่างถูกต้องจะเรียกread()เป็นครั้งที่สองในกรณีที่ส่งคืนน้อยกว่าที่คาดไว้จนกว่าจะส่งคืน 0 ในกรณีของreadบิวด์อินแน่นอนว่าเพียงพอที่จะอ่านจนกระทั่งEOF หรือจนกว่าNLจะเห็น

หากคุณเรียกใช้trussหรือโคลนทรัสมัดคุณควรจะสามารถตรวจสอบพฤติกรรมที่ไม่ถูกต้องสำหรับเชลล์ที่กลับมา6ในการทดสอบของคุณเท่านั้น

ในกรณีพิเศษนี้ดูเหมือนว่าจะเป็นข้อบกพร่องเคอร์เนล Linux ดู:

$ 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).

เคอร์เนล Linux ส่งคืน0ด้วยวินาทีreadและแน่นอนว่าไม่ถูกต้อง

บทสรุป: เชลล์ที่พยายามอ่านข้อมูลจำนวนมากพอสมควรจะไม่เรียกใช้บั๊กเคอร์เนลของ Linux


ตกลงคำตอบที่ออกพร้อมกับการตรวจสอบใหม่สำหรับข้อบกพร่องเคอร์เนล Linux
schily

มันไม่ใช่ข้อผิดพลาดมันเป็นคุณสมบัติ!
Guntram Blohm สนับสนุน Monica

นี่คือการเรียกร้องที่แปลกจริงๆ
schily

มันจะเป็นคุณสมบัติถ้ามีการบันทึกไว้ อ่านkernel.org/doc/Documentation/filesystems/proc.txtฉันไม่เห็นเอกสารสำหรับพฤติกรรม ที่กล่าวว่ามันชัดเจนว่าทำงานได้ตามที่ตั้งใจโดยผู้ใช้งานดังนั้นหากนี่เป็นการพิจารณาข้อบกพร่องมันเป็นข้อผิดพลาดในการออกแบบไม่ใช่การนำไปใช้
Charles Duffy

0

ไฟล์ภายใต้ / proc บางครั้งใช้อักขระ NULL เพื่อแยกฟิลด์ในไฟล์ ดูเหมือนว่าการอ่านจะไม่สามารถจัดการกับสิ่งนี้ได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.