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