פתרון שם למבני מערכת הקבצים¶
נניח שתהליך פתח קובץ, ותהליך אחר שינה את שמו. האם הקריאה הבאה דרך המתאר אמורה להיכשל? השאלה הזאת נפתרת כאשר מפרידים בין שם בנתיב לבין האובייקט שנפתח. הנתיב הוא דרך להגיע לאובייקט בזמן פעולה מסוימת, ואינו ידית קבועה שמלווה כל קריאה עתידית.
שכבת VFS מאפשרת לממשקי קבצים לעבוד מול מימושים שונים. ה־inode מתאר אובייקט מערכת קבצים ומטא־נתונים שלו, ה־dentry קושר רכיב שם למבנה חיפוש, וה־mount מציב מערכת קבצים בתוך סביבת השמות. אובייקט הפתיחה שראינו בפרק הרביעי מחזיק מצב של שימוש פעיל, כגון offset והפניה לאובייקטים הדרושים להמשך העבודה.
הקוד הבא יוצר תיקיית ניסוי חדשה בלבד. אם היא כבר קיימת הוא נכשל, כך שאינו דורס תיקייה קודמת. הוא משנה שם של קובץ פתוח, מסיר את השם החדש וממשיך לקרוא דרך המתאר.
use std::{fs::{self, OpenOptions}, io::{self, Read, Seek, SeekFrom, Write}};
use std::os::unix::fs::MetadataExt;
fn main() -> io::Result<()> {
fs::create_dir("vfs-lab")?;
let original = "vfs-lab/original";
let renamed = "vfs-lab/renamed";
let mut file = OpenOptions::new().read(true).write(true)
.create_new(true).open(original)?;
file.write_all(b"still reachable through the descriptor")?;
let before = file.metadata()?;
fs::rename(original, renamed)?;
let after = fs::metadata(renamed)?;
assert_eq!((before.dev(), before.ino()), (after.dev(), after.ino()));
fs::remove_file(renamed)?;
println!("remaining links={}", file.metadata()?.nlink());
file.seek(SeekFrom::Start(0))?;
let mut text = String::new();
file.read_to_string(&mut text)?;
println!("{text}");
drop(file);
fs::remove_dir("vfs-lab")?;
Ok(())
}
הזוג של מספר התקן ו־inode משמש כאן להשוואה בתוך הניסוי. מספר inode לבדו אינו מזהה גלובלי בין מערכות קבצים, וגם הזוג אינו זהות נצחית אחרי מחיקה ומחזור. ההוכחה שהמתאר ממשיך לעבוד אינה תלויה בכך שנוכל למצוא שוב את אותו שם.
כאשר פותרים נתיב כמו a/b/file, המערכת עוברת בין רכיבי שם, הרשאות וסביבות mount. קישור סמלי יכול להכניס נתיב נוסף לתהליך החיפוש, וקישור יחסי מתפרש בהקשר המתאים למיקומו. לכן ספירת .. במחרוזת בלבד אינה ניתוח מלא של המקום שאליו הפעולה תגיע.
מטמון dentries יכול לשמור גם תוצאה שלילית, כלומר ידיעה ששם מסוים לא נמצא בהקשר נתון. זאת אופטימיזציה של חיפוש שמות, ולא קובץ נסתר. מערכות קבצים מרוחקות או משתנות עשויות לדרוש כללי אימות ורענון אחרים, ולכן עלות פתיחת נתיב אינה תמיד אותה עלות של קריאת הנתונים שכבר פתחנו.
ההבחנה בין שם לבין אובייקט מסבירה גם למה מחיקת קובץ גדול אינה תמיד מפנה מיד מקום. אם תהליך מחזיק פתיחה או מיפוי רלוונטיים, הנתונים יכולים להישאר בחיים אף שאין להם שם רגיל בספרייה. כלי תפעול צריך לבדוק מתארים פתוחים ולא להסתפק ברשימת שמות הקבצים.
מצד שני, פתיחה מחודשת של אותו נתיב אינה מבטיחה שנקבל את אותו קובץ. בין שתי הפתיחות מישהו יכול להחליף את הרשומה בספרייה. אם היישום זקוק להמשך עבודה עם אותו אובייקט, שמירת המתאר היא חוזה חזק יותר משמירת מחרוזת הנתיב בלבד.
המערכות /proc ו־sysfs מזכירות לנו שלא כל קובץ הוא אוסף בייטים קבוע על דיסק. פעולת קריאה יכולה לבקש מהקרנל לייצר תמונת מצב, וגודל שמופיע במטא־נתונים אינו בהכרח מספר הבייטים שנקרא עד EOF. לכן ממשק Read צריך לטפל בתוצאות בפועל ולא להקצות תמיד לפי metadata.len כאילו זאת הבטחה סופית.
בחקירת מקור הקרנל נוכל לעקוב אחרי פתרון הנתיב לשכבת filesystem מסוימת, אבל שמות פונקציות פנימיות משתנים בין גרסאות. נתעד את העץ וה־commit, ונפריד בין החוזה של open לבין המסלול הפנימי שביצע אותו בסביבת המעבדה.
המודל הציבורי מופיע בעמוד path_resolution, והמבנים הפנימיים מוסברים בתיעוד VFS של הקרנל. בזמן תחקיר נשאל אם הכשל שייך לחיפוש שם, לפתיחת האובייקט או לפעולה על אובייקט שכבר פתוח.