Fachpraktikum 5 von 6: Dateisysteme
Transcription
Fachpraktikum 5 von 6: Dateisysteme
Institut für Technische Informatik und Kommunikationsnetze Fachpraktikum 5 von 6: Dateisysteme Testatbedingungen: Zusatztestat: Lösen der Aufgaben 2.1 bis 2.4 Selbstständiges Lösen der Aufgabe 2.5 Hinweis: Jeder arbeitet für sich, Betreuer/Nachbar fragen erlaubt Abgabe an Betreuer: Programm(e) auf Konsole ausführen und zeigen, Vorhandene Fragen aus dem Text beantworten, Eigenen Quelltext erklären Communication Systems Group ETH Zurich Ariane Keller <[email protected]> Markus Happe <[email protected]> Inhaltsverzeichnis 1 Einleitung 1.1 FUSE: Ein Dateisystem gespeichert in einer einzigen Datei . . . . . . . . . 1.2 Unser Beispiel-Dateisystem: Ein einfacher Kalender . . . . . . . . . . . . 1.3 Mounten einer Datenbankdatei . . . . . . . . . . . . . . . . . . . . . . . . 1 1 2 3 2 Aufgaben 2.1 Eine Kalender-Datenbank erstellen . . . . . . . . . . . . . . . 2.2 Die Kalender-Datenbank mounten und die Metadaten auflisten 2.3 Kalendereinträge (bzw. Dateien) lesen . . . . . . . . . . . . . 2.4 Kalendereinträge (bzw. Dateien) überschreiben . . . . . . . . 2.5 (Optional:) Ordner Implementierung . . . . . . . . . . . . . . 4 4 4 6 7 8 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einleitung 1.1 FUSE: Ein Dateisystem gespeichert in einer einzigen Datei Ziel dieses Praktikums ist es, anhand des gegebenen Mini-Dateisystems tikfs zu lernen, wie ein Dateisystem in Linux aufgebaut ist und wie es funktioniert. Dazu soll das vorgegebene Grundgerüst des tikfs Schritt für Schritt so erweitert werden, dass wir am Schluss ein read / write Filesystem-in-a-File haben, d.h. das gesamte Dateisystem wird in eine einzige grosse Datei abgelegt, welche auf dem ’normalem’ Linux-Dateisystem gespeichert ist. Unser Filesystem-in-a-File ist mittels libfuse1 implementiert. Üblicherweise sind Dateisysteme wie ext32 direkt im Kernel integriert. Das Dateisystem fuse jedoch exportiert diese Kernel-API in den Userspace, sodass man auf Userspace-Funktionen und -Libraries zurückgreifen kann. Ein prominentes fuse-Dateisystem ist beispielsweise sshfs3 , welches Ordner und Dateien eines entfernten Hosts über SSH auf dem eigenen Host einbinden (mounten) kann, sodass man annehmen könnte, dass diese Dateien vom eigenen Host seien. Ein anderes fuse-Dateisystem ist ntfs-3g4 , die Linux-Implementierung vom Windows NTFSDateisystem. 1 http://lingrok.org/xref/linux-linus/Documentation/filesystems/fuse.txt 2 In Linux http://lingrok.org/xref/linux-linus/fs 3 http://en.wikipedia.org/wiki/Sshfs 4 http://en.wikipedia.org/wiki/Ntfs-3g 1 1.2 Unser Beispiel-Dateisystem: Ein einfacher Kalender In unserem tikfs Mini-Dateisystem implementieren wir lediglich einen ganz einfachen Kalender. Die Dateien entsprechen den einzelnen Tagen und der Inhalt der Dateien entspricht den Meetings von diesem Tag. Abbildung 1 zeigt wie so ein Dateisystem aussehen kann: Abbildung 1: Ausgabe des tikfs Kalender-Dateisystems Damit dieses Dateisystem permanent gespeichert werden kann, schreiben wir alle Daten (Dateinamen, Dateidaten, Dateimetadaten) in eine einzige binäre Datei, welches wir normal abspeichern können (unser Äquivalent zur Festplatte). Damit wir diese Datei einfach ausserhalb unseres tikfs Dateisystems zu Testzwecken erstellen und editieren können, haben wir ein kleines Datenbank-Tool tikdb geschrieben. Abbildung 2 zeigt, wie dieses Tool benutzt wird. Abbildung 2: Handhabung vom Datenbank-Tool tikdb. Zusammengefasst heisst das also, dass wir unsere Binärdatenbank in ein Dateisystem als herkömmlichen Ordner mit einzelnen Dateien abbilden wollen, wobei die Dateien die Kalendereinträge repräsentieren sollen. Unterordner existieren nicht. 2 1.3 Mounten einer Datenbankdatei Ein Code-Template (tikfs.c) ist vorgegeben, welches unser Mini-Dateisystem implementiert. Um eine Kalenderdatenbank als Dateisystem mounten und danach bearbeiten zu können, müssen Sie in diesem Fachpraktikum die folgenden Funktionen implemtentieren: tikfs getattr, tikfs read und tikfs write. Um diese Funktionen programmieren zu können, müssen Sie verstehen, wie einzelne Kalendereinträge im tikfs Dateisystem eingebunden werden. Während des Mountens des Dateisystems wird die Datenbankdatei eingelesen und in ein Array der folgenden Struktur abgebildet: struct tikfs_fnode { struct tikdb_datahdr meta; off_t data; int dirty; uint8_t *cowbuff; size_t cowlen; }; // // // // // meta data fileoffset for actual data whether data was modified copy on write buffer copy on write length Der Metadatentyp struct tikdb datahdr ist wie folgt definiert struct tikdb_datahdr { char key[32]; // time_t ts_r; // time_t ts_w; // uint32_t len; // }; file name time stamp of last read access time stamp of lasw write access length of the data Alle anderen Funktionen, wie zum Beispiel für das Auslesen oder Zurückschreiben der einzelnen Dateien in die Kalender-Datei, werden vom Framework zur Verfügung gestellt. Beim Mounten des Dateisystems wird die Datenbankdatei eingelesen und ein Cache names table aufgebaut. Der Cache ist ein Array aus struct tikfs fnode Einträgen. Für jeden Tag wird ein solcher struct tikfs fnode in die Tabelle table eingeführt. Dabei wird die tikdb datahdr Struktur aus der Datenbankdatei beim Mounten ausgelesen und in die tifks fnode Komponente meta kopiert. Die Meetings der einzelnen Tage werden nicht in den Cache kopiert, sondern es wird eine Referenz bzw. Offset auf die Datenbankdatei gespeichert. Der Offset data enthält jener Byteoffset der Datenbankdatei, bei dem der Meetingseintrag beginnt. Auf die Komponenten dirty, cowbuff und cowlen kommen wir später in der Aufgabe 2.4 zurück. 3 2 Aufgaben Für dieses Fachpraktikum benötigen wir root-Rechte. Daher müssen die Aufgaben in der virtuellen Maschine (VMWare) gelöst werden. Starten Sie daher zunächst die VMWare. Wie Sie die VMWare starten können, entnehmen Sie bitte dem HowTo auf unserer Webseite http://www.csg.ethz.ch/education/lectures/Fachpraktikum/HowTo. 2.1 Eine Kalender-Datenbank erstellen Erstellen Sie eine Kalenderdatenbank todo.db mit mindestest drei Einträgen. Kompilieren Sie dazu zuerst das Datenbanktool tikdb gcc -Wall tikdb.c -o tikdb Erstellen Sie eine neue Datenbank. ./tikdb create todo.db Erstellen Sie abschliessend mind. drei Kalendereinträge. ./tikdb add todo.db "26.05.2014" "Watch Breaking Bad" ./tikdb add todo.db "16.06.2014" "Watch Mad Men" 2.2 Die Kalender-Datenbank mounten und die Metadaten auflisten Als nächsten Schritt wollen wir unsere Kalenderdatenbank als tikfs Dateisystem auf unserem Linux-System mounten und alle Kalendereinträge mit dem Kommando ls -la auflisten. Dazu kompilieren Sie tikfs entweder mit diesem Kommando: gcc -Wall tikfs.c -o tikfs ‘pkg-config fuse --cflags --libs‘ oder mit jenem Kommando: gcc -Wall tikfs.c -o tikfs -l fuse Erstellen Sie ein leeres Verzeichnis, z.B. tmp: mkdir tmp Mounten Sie als root an dieser Stelle das Dateisystem: # ./tikfs tmp todo.db Als nächstes versuchen wir in das Verzeichnis tmp zu wechseln und dort alle Dateien aufzulisten. Dort erhalten wir allerdings folgenden Fehler: # cd tmp # bash: cd: tmp: Input/Output error Daher Unmounten wir das tmp-Verzeichnis mittels: # umount tmp Implementieren Sie nun die Funktion tikfs getattr in der Datei tikfs.c, damit wir in das tmp-Verzeichnis wechseln können und auch die Metadaten unserer Kalendereinträge mit dem Kommando ls -la bzw. ls -lua auflisten können. 4 Hinweise zum Lösen der Aufgabe 2.2 Die Funktion tikfs getattr soll die Metadaten einer Datei zurückgeben. Dazu werden diese in einen struct stat geschrieben. Dieser ist in der man page zu fstat erklärt. In unserem Dateisystem geben wir der Einfachkeit halber statische Daten zurück, d.h. der Nutzer kann keine Metadaten von sich aus setzen. Wir definieren die für uns wichtigen Einträge wie folgt: st uid: user ID of owner: kann gesetzt werden mit getuid. st gid: group ID of owner: kann gesetzt werden mit getgid. st size: total size, in Bytes: Länge der Datei, nur für Dateien, nicht für Ordner st mode: protection: Zugriffsrechte (lesen, schreiben, ausführen) für entweder Dateien oder Ordner. Verwenden Sie S IFDIR für Ordner und S IFREG für Dateien und verodern Sie dies mit den gewünschten Lese-, Schreibe-, und Ausführrechte für den Owner, die Gruppe, und alle anderen. Verwenden Sie die in der man page chmod angegebenen Masken. Das sticky-Bit kann in unserem Falle ignoriert werden. st nlink: number of hard links: Unterschiedlich für Dateien (=1) und Ordner (=2)! st atime: time of last access: Der letzte Zeitpunkt, an dem der Inhalt der Datei gelesen wurde (in Unixzeit) st mtime: time of last modification: Der letzte Zeitpunkt, an dem der Inhalt der Datei verändert wurde (in Unixzeit) Die Unixzeit ist als die Anzahl an Sekunden definiert, die seit dem 1. Januar 1970 00:00 Uhr UTC vergangen sind. Implementieren Sie die Funktion tikfs getattr indem Sie (a) die User und Group ID mit den angegebenen Funktionen setzen (b) die korrekten Werte für die Zugriffsrechte und die Anzahl Hardlinks eintragen (c) st atime, st mtime und st size mit den Metadaten aus dem table Cache ausfüllen. Den korrekten Cache-Index für die Dateien (TIK FILE) ist bereits in node gespeichert. Für den Mountpoint (Mount-Ordner, TIK ROOT) muss ein eigener Wert festgelegt werden, z.B. die aktuelle Zeit. Bisher sind tikfs read und tikfs write lediglich Stubs, was allerdings kein Problem darstellt, eine Datenbankdatei zu mounten und sich mit Tools wie ls -la Metadaten anzeigen zu lassen. Wenn Sie die tikfs getattr-Funktion korrekt implementiert haben, sollten Sie in das Verzeichnis tmp wechseln und die Dateien auflisten können. 5 2.3 Kalendereinträge (bzw. Dateien) lesen Der nächste Schritt besteht nun darin, ein Read-only Dateisystem zu bauen. Dazu müssen Sie die tikfs read Routine zu implementieren. Dabei sind von der Funktionssignatur die Argumente buff, size und offset von Interesse. Hierbei ist buff der Zielpuffer der Länge size, welchen Sie mit Daten füllen sollen. Die Variable offset ist das angeforderte Offset von der Datei, bei dem size Bytes gelesen werden sollen. Wie auch in tikfs getattr haben Sie in der Variable node den table Index zum Cache. Stellen Sie sicher, dass der gewünschte Offset nicht grösser als die Komponente len der Dateimetadaten ist und limitieren Sie unter Berücksichtigung von offset den Parameter size so, dass nicht über das Dateiende hinaus gelesen wird. Falls all dies sichergestellt ist, dann springen Sie mit lseek (mit tikfs fd als FiledescriptorArgument) zum Offset in der Datenbankdatei und wenden Sie anschliessend ein read vom tikfs fd auf buff an. Hinweise zum Lösen der Aufgabe 2.3 • Die Variable tikfs fd ist bereits ein offener Descriptor vom todo.db • In table[node].data liegt der Offset vom Dateibeginn zum Beginn des node’ten Dateneintrags • Vergessen Sie nicht, den ’Lock’ rwing zurück zu setzen bei einem Fehler. Dies kann zum Beispiel mit Hilfe einer goto Anweisung zum Label out implementiert werden. (Fehlerbehandlung ist eines der wenigen Bereiche, in denen die Verwendung von goto sinnvoll ist.) Zum Testen Ihrer Implementierung mounten Sie Ihr Dateisystem neu und lesen Sie eine beliebige Datei aus, z.B. # ./tikfs tmp todo.db # cd tmp # cat 25.05.2014 6 2.4 Kalendereinträge (bzw. Dateien) überschreiben Im letzten Schritt wollen wir auch Schreibzugriff auf unsere Dateien erlangen. Dazu müssen wir die tikfs write Routine fertiggestellen und danach die Routinen tikfs read und tikfs getattr aktualisieren. Hierzu werden nun die Komponenten dirty, cowbuff, cowlen der Struktur struct tikfs fnode interessant. Schreibzugriff Wir implementieren ein copy-on-write (cow): sobald Daten eines Kalendereintrages verändert werden, wird vom Original eine Kopie im Heap gehalten. Ausserdem merkt sich unser Dateisystem, dass diese Datei verändert wurde, in dem Sie die Datei als dirty markiert. Beim Unmounten werden schlussendlich alle als dirty markierten Dateien zurückgeschrieben. Prüfen Sie also im ersten Schritt im table-Cache, ob eine Datei bereits als dirty markiert ist. Falls nicht, dann markieren Sie es als solches, setzen die cowlen zunächst auf die len der Metadaten und allokieren in cowbuff mit xmalloc ebensoviele Bytes. Wie in Ihrer read Implementierung lesen Sie nun mittels lseek und read die Originaldaten nach cowbuff ein. Implementieren Sie auch eine Fehlerbehandlung für das read. Dazu geben Sie bei einen Fehler auch den Speicher wieder frei, rufen Sie zudem return mit einem passendem Fehlerwert (aus errno.h) auf und setzen sie rwing zurück. Sobald die Abfrage von dirty implementiert ist, geht es nun an das eigentliche write. Bezüglich cowbuff können nun zwei Sonderfälle eintreten. • Einerseits kann es sein, dass cowlen kleiner ist als der Summe aus size und offset, sodass der Buffer mit xrealloc expandiert werden muss • andererseits kann cowlen grösser sein, sodass der Buffer verkürzt werden muss Schlussendlich kann nun mit memcpy der aus dem Userspace übergebene Buffer in cowbuff geschrieben werden und die write-time in den Metadaten aktualisiert werden. Lesezugriff und Metadaten anpassen Ist dies implementiert, dann müssen Sie die Fallunterscheidung bezüglich dirty auch nachträglich in tikfs read einbauen, beziehungsweise in tikfs getattr um eine korrekte Grösse anzeigen zu lassen. Das Zurückschreiben von als dirty markierten Dateien ist beim Unmounten bereits implementiert. Machen Sie sich beim Testen ein Backup von der originalen Datenbankdatei, da es durch Programmierfehler beim Zurückschreiben korrumpiert werden kann. Lösung Testen Testen Sie Ihre Implementation, indem Sie bestehenden Kalendereinträge manipulieren: # echo "Watch House of Cards" > "26.05.2014" Unmounten Sie das Dateisystem und testen Sie, ob tikdb die Änderungen korrekt anzeigt. cd .. umount tmp ./tikdb get todo.db "26.05.2014" 7 2.5 (Optional:) Ordner Implementierung Wenn Sie diese optionale Aufgabe selbstständig lösen, erhalten Sie ein zusätzliches Testat! Achtung: Diese Aufgabe ist deutlich schwieriger als die vorherigen Aufgaben und erfordern auch Änderungen am Framework. Implementieren Sie den Kalender so, dass die einzelnen Tage nicht mehr als Dateien sondern als Ordner angezeigt werden. In diesen Ordnern sollen dann die einzelnen Meetings als einzelne Dateien angezeigt werden. Es sollen wieder die drei Operationen get attr, read und write unterstützt werden und beim Unmounten alle Daten zurück in die Datenbankdatei geschrieben werden. 8