การเรียกลิงก์สัญลักษณ์ซ้ำ - อะไรทำให้ "รีเซ็ต"?


64

ฉันเขียนสคริปต์ทุบตีเล็ก ๆ น้อย ๆ เพื่อดูว่าเกิดอะไรขึ้นเมื่อฉันติดตามลิงก์สัญลักษณ์ที่ชี้ไปยังไดเรกทอรีเดียวกัน ฉันคาดหวังว่ามันจะสร้างไดเร็กตอรี่ที่ใช้งานได้นานมากหรือพัง. แต่ผลที่ได้ทำให้ฉันประหลาดใจ ...

mkdir a
cd a

ln -s ./. a

for i in `seq 1 1000`
do
  cd a
  pwd
done

บางส่วนของผลลัพธ์คือ

${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a
${HOME}/a/a
${HOME}/a/a/a
${HOME}/a/a/a/a
${HOME}/a/a/a/a/a
${HOME}/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a

เกิดอะไรขึ้นที่นี่

คำตอบ:


88

Patrice ระบุที่มาของปัญหาในคำตอบของเขาแต่ถ้าคุณต้องการทราบวิธีการได้รับจากที่นั่นกับสาเหตุที่คุณได้รับนั่นคือเรื่องราวที่ยาวนาน

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

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

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

ตัวอย่างเช่นกระบวนการที่พยายามค้นหาว่าไดเรกทอรีปัจจุบันคือ/a/b/cจะเปิด..ไดเรกทอรี (เส้นทางสัมพัทธ์ดังนั้นจึง..เป็นรายการในไดเรกทอรีปัจจุบัน) และค้นหาไฟล์ของไดเรกทอรีประเภทที่มีหมายเลข inode เดียวกัน.พบว่าcแมตช์ที่แล้วเปิด../..ไปเรื่อย ๆ /จนกว่าจะพบ ไม่มีความกำกวมอยู่ที่นั่น

นั่นคือสิ่งที่ฟังก์ชั่นgetwd()หรือgetcwd()C ทำหรืออย่างน้อยก็เคยทำ

ในบางระบบเช่น Linux รุ่นใหม่มีการเรียกระบบเพื่อส่งเส้นทาง canonical ไปยังไดเรกทอรีปัจจุบันซึ่งทำการค้นหาในพื้นที่เคอร์เนล (และอนุญาตให้คุณค้นหาไดเรกทอรีปัจจุบันของคุณแม้ว่าคุณจะไม่ได้อ่านการเข้าถึงส่วนประกอบทั้งหมดของมัน) และนั่นคือสิ่งที่getcwd()เรียกว่า บนลินุกซ์ที่ทันสมัยนอกจากนี้คุณยังสามารถหาเส้นทางไปยังไดเรกทอรีปัจจุบันผ่าน readlink (ที่) /proc/self/cwdบน

นั่นคือสิ่งที่ภาษาและเปลือกต้นส่วนใหญ่ทำเมื่อส่งคืนพา ธ ไปยังไดเรกทอรีปัจจุบัน

ในกรณีของคุณคุณสามารถโทรหาcd aที่อาจครั้งตามที่คุณต้องการเพราะมันเป็น symlink ไป.ที่ไดเรกทอรีปัจจุบันไม่เปลี่ยนแปลงดังนั้นทั้งหมดgetcwd(), pwd -P, python -c 'import os; print os.getcwd()', จะกลับมาของคุณperl -MPOSIX -le 'print getcwd'${HOME}

ตอนนี้การเชื่อมโยงระบบเชื่อมโยงกันซับซ้อนขึ้น

symlinksอนุญาตให้ข้ามในไดเรกทอรีต้นไม้ ใน/a/b/cถ้า/aหรือ/a/bหรือ/a/b/cเป็น symlink แล้วเส้นทางบัญญัติของ/a/b/cจะเป็นสิ่งที่แตกต่างอย่างสิ้นเชิง โดยเฉพาะอย่างยิ่ง..ในรายการไม่จำเป็นต้องเป็น/a/b/c/a/b

ในเชลล์เป้าหมายถ้าคุณทำ:

cd /a/b/c
cd ..

หรือแม้กระทั่ง:

cd /a/b/c/..

/a/bมีการรับประกันคุณจะจบลงในไม่ได้

เหมือนกับ:

vi /a/b/c/../d

ไม่จำเป็นต้องเหมือนกับ:

vi /a/b/d

kshแนะนำแนวคิดของไดเร็กตอรี่ปัจจุบันที่ใช้ในการทำงานเพื่อแก้ไขสิ่งนั้น. ผู้คนเคยชินกับมันและ POSIX ก็จบลงด้วยการระบุพฤติกรรมซึ่งหมายความว่าเชลล์ส่วนใหญ่ทุกวันนี้ก็ทำได้เช่นกัน:

สำหรับคำสั่งcdและpwdbuiltin ( และสำหรับคำสั่งเหล่านั้นเท่านั้น (แม้ว่าจะใช้กับpopd/ pushdบนเชลล์ที่มี)) เชลล์จะเก็บรักษาแนวคิดของตนเองเกี่ยวกับไดเร็กทอรีการทำงานปัจจุบัน มันถูกเก็บไว้ใน$PWDตัวแปรพิเศษ

เมื่อคุณทำ:

cd c/d

แม้ว่าcหรือc/dมี symlinks ขณะ$PWDcontaines /a/bมันผนวกc/dไปยังจุดสิ้นสุดเพื่อให้กลายเป็น$PWD /a/b/c/dและเมื่อคุณ:

cd ../e

แทนการทำก็ไม่chdir("../e")chdir("/a/b/c/e")

และpwdคำสั่งจะส่งคืนเนื้อหาของ$PWDตัวแปรเท่านั้น

สิ่งนี้มีประโยชน์ในเชลล์แบบโต้ตอบเนื่องจากpwdเอาต์พุตเส้นทางไปยังไดเร็กทอรีปัจจุบันที่ให้ข้อมูลว่าคุณไปถึงที่นั่นได้อย่างไรและตราบใดที่คุณใช้เฉพาะ..ในอาร์กิวเมนต์เพื่อcdและไม่ใช่คำสั่งอื่น ๆ มีโอกาสน้อยที่จะทำให้คุณประหลาดใจเพราะcd a; cd ..หรือcd a/..โดยทั่วไปแล้ว ไปยังที่ที่คุณอยู่

ตอนนี้ไม่ได้แก้ไขจนกว่าคุณจะทำ$PWD cdจนกว่าจะถึงครั้งต่อไปที่คุณโทรcdหรือpwdอาจมีสิ่งต่าง ๆ เกิดขึ้นมากมายส่วนประกอบของ$PWDสามารถเปลี่ยนชื่อได้ ไดเรกทอรีปัจจุบันจะไม่เปลี่ยนแปลง (เป็น inode เดียวกันเสมอแม้ว่าจะสามารถลบได้) แต่เส้นทางในไดเรกทอรีต้นไม้อาจเปลี่ยนแปลงได้อย่างสมบูรณ์ getcwd()คำนวณไดเรกทอรีปัจจุบันทุกครั้งที่เรียกใช้โดยการเดินทรีไดเรกทอรีเพื่อให้ข้อมูลมีความถูกต้องอยู่เสมอ แต่สำหรับโลจิคัลไดเร็กทอรีที่นำมาใช้โดยเชลล์ POSIX ข้อมูลใน$PWDอาจไม่เสถียร ดังนั้นเมื่อวิ่งcdหรือpwdกระสุนบางนัดอาจต้องการป้องกันสิ่งนั้น

ในตัวอย่างนั้นคุณจะเห็นพฤติกรรมที่แตกต่างกับกระสุนที่แตกต่างกัน

บางคนชอบksh93เพิกเฉยปัญหาทั้งหมดดังนั้นจะส่งคืนข้อมูลที่ไม่ถูกต้องแม้หลังจากที่คุณโทรcd (และคุณจะไม่เห็นพฤติกรรมที่คุณเห็นด้วยbash)

บางคนชอบbashหรือzshไม่ตรวจสอบว่า$PWDยังคงเป็นเส้นทางไปยังไดเรกทอรีปัจจุบันเมื่อแต่ไม่เมื่อcdpwd

pdksh ตรวจสอบทั้งpwdและcd(แต่pwdไม่อัปเดต$PWD)

ash(อย่างน้อยหนึ่งที่พบใน Debian) ไม่ได้ตรวจสอบและเมื่อคุณทำcd aมันจริง ๆ แล้วcd "$PWD/a"ดังนั้นหากไดเรกทอรีปัจจุบันมีการเปลี่ยนแปลงและ$PWDไม่ชี้ไปที่ไดเรกทอรีปัจจุบันจริง ๆ แล้วจะไม่เปลี่ยนเป็นaไดเรกทอรีในไดเรกทอรีปัจจุบัน แต่มีหนึ่งใน$PWD(และส่งคืนข้อผิดพลาดหากไม่มีอยู่)

หากคุณต้องการเล่นกับมันคุณสามารถทำได้:

cd
mkdir -p a/b
cd a
pwd
mv ~/a ~/b 
pwd
echo "$PWD"
cd b
pwd; echo "$PWD"; pwd -P # (and notice the bug in ksh93)

ในเปลือกหอยต่างๆ

ในกรณีของคุณเนื่องจากคุณกำลังใช้bashหลังจากที่cd a, bashการตรวจสอบว่า$PWDยังคงชี้ไปที่ไดเรกทอรีปัจจุบัน ในการทำเช่นนั้นมันจะเรียกstat()ค่าของ$PWDการตรวจสอบหมายเลขไอโหนดและเปรียบเทียบกับของ.การตรวจสอบหมายเลขไอโหนดและเปรียบเทียบกับที่ของ

แต่เมื่อค้นหา$PWDเส้นทางที่เกี่ยวข้องกับการแก้ไข symlink มากเกินไปที่stat()กลับมาพร้อมกับข้อผิดพลาดดังนั้นเชลล์ไม่สามารถตรวจสอบว่า$PWDยังสอดคล้องกับไดเรกทอรีปัจจุบันดังนั้นจึงคำนวณมันอีกครั้งด้วยgetcwd()และการปรับปรุง$PWDตาม

ตอนนี้เพื่อชี้แจงคำตอบของ Patrice การตรวจสอบจำนวน symlink ที่พบในขณะที่ค้นหาเส้นทางคือการป้องกันลูป symlink การวนซ้ำที่ง่ายที่สุดสามารถทำได้ด้วย

rm -f a b
ln -s a b
ln -s b a

หากไม่มีพนักงานรักษาความปลอดภัยนั้นcd a/xระบบจะต้องค้นหาตำแหน่งที่aเชื่อมโยงไปหาbและเป็น symlink ที่เชื่อมโยงไปยังaและมันจะดำเนินต่อไปเรื่อย ๆ วิธีที่ง่ายที่สุดในการป้องกันคือการยอมแพ้หลังจากการแก้ไขมากกว่าจำนวน symlink ที่กำหนดเอง

ตอนนี้กลับไปยังไดเรกทอรีการทำงานแบบลอจิคัลปัจจุบันและทำไมคุณลักษณะไม่ดีนัก สิ่งสำคัญคือต้องตระหนักว่ามันมีไว้สำหรับcdเชลล์เท่านั้นและไม่ใช่คำสั่งอื่น ๆ

ตัวอย่างเช่น

cd -- "$dir" &&  vi -- "$file"

ไม่เหมือนกับ:

vi -- "$dir/$file"

นั่นเป็นเหตุผลที่บางครั้งคุณจะพบว่าผู้คนแนะนำให้ใช้cd -Pในสคริปต์เสมอเพื่อหลีกเลี่ยงความสับสน (คุณไม่ต้องการให้ซอฟต์แวร์ของคุณจัดการกับข้อโต้แย้งที่../xแตกต่างจากคำสั่งอื่น ๆ เพียงเพราะเขียนในเชลล์แทนภาษาอื่น)

-Pตัวเลือกคือการปิดการใช้งานไดเรกทอรีตรรกะจัดการเพื่อให้cd -P -- "$var"จริงไม่เรียกchdir()กับเนื้อหาของ$var(ยกเว้นเมื่อ$varเป็น-แต่ที่เรื่องอื่น) และหลังจากcd -Pนั้น$PWDจะมีเส้นทางแบบบัญญัติ


7
Sweet Jesus! ขอขอบคุณสำหรับการดังกล่าวเป็นคำตอบที่ครอบคลุมก็จริงๆน่าสนใจมาก :)
ลูคัส

คำตอบที่ยอดเยี่ยมขอบคุณมาก! ฉันรู้สึกเหมือนฉันเลยรู้ว่าทุกสิ่งเหล่านี้ แต่ฉันไม่เคยเข้าใจหรือความคิดเกี่ยวกับวิธีที่พวกเขาทั้งหมดมารวมกัน คำอธิบายที่ดี
dimo414

42

นี่เป็นผลลัพธ์ของการ จำกัด ฮาร์ดโค้ดในแหล่งเคอร์เนล Linux เพื่อป้องกันการปฏิเสธการบริการขีด จำกัด ของจำนวน symlinks ที่ซ้อนกันคือ 40 (พบในfollow_link()ฟังก์ชันภายในfs/namei.cเรียกโดยnested_symlink()ในเคอร์เนลซอร์ส)

คุณอาจจะได้รับพฤติกรรมที่คล้ายกัน (และอาจเป็นขีด จำกัด อีกกว่า 40) กับเมล็ดอื่น ๆ ที่สนับสนุน symlink


1
มีเหตุผลหรือไม่ที่จะ "รีเซ็ต" แทนที่จะหยุด คือมากกว่าx%40 max(x,40)ฉันเดาว่าคุณยังคงเห็นว่าคุณเปลี่ยนไดเรกทอรีแล้ว
ลูคัส

4
ลิงก์ไปยังแหล่งข้อมูลสำหรับใครก็ตามที่อยากรู้อยากเห็น: lxr.linux.no/linux+v3.9.6/fs/namei.c#L818
Ben
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.