Nach dem Superblock folgen die FATs ( FAT = file allocation table ). Diese sind zentraler Bestandteil des Filesystems. Wie der Name schon nahelegt wird die FAT benutzt, um den Files die zugehörigen Datenblöcke zuzuordnen. Genauer werden nicht Blöcke zugeordnet sondern Cluster. Ein Cluster besteht aus einem oder mehreren Sektoren. Ein Sektor kann wiederum aus mehreren 512 Byte grosses Blöcken bestehen. Ueblicherweise sind aber Sektoren bei FAT16 auch 512 Bytes gross, bestehen also aus genau einem Block. Die FAT ist ein Array aus 16bit langen Adressen. Jedem Eintrag in der FAT ist ein Daten-Cluster zugeordnet. Die Adresse in dem Eintrag gibt den nachfolgenden Cluster an und kann direkt als Index in die FAT verwendet werden. Spezielle Einträge in der FAT markieren den zugehörigen Cluster als frei (Eintrag 0x0000) oder das Ende eines Files (Eintrag 0xFFF8h - 0xFFFF). Um nun die an die Datenblöcke eines Files zu gelangen, liest man zunächst die Adresse des ersten Clusters aus dem Verzeichniseintrag aus. Ueber diesen kann man dann direkt den ersten Cluster auslesen und den zugehörigen Eintrag in der FAT finden. Solange in der FAT nicht die Markierung für das Dateiende steht, macht man mit dem Cluster, der in dem FAT-Eintrag angegeben ist, weiter.
Es kann mehrere FATs pro Filesystem geben (üblicherweise sind es bei FAT16 zwei). Wenn man die FAT verändert, muss man dann darauf achten, die Aenderung in allen FATs vorzunehmen um diese konsistent zu halten.
Nach den FATs folgt das Root-Verzeichnis. Für das Root-Verzeichnis ist ein Bereich fester Länge vorgesehen, während alle anderen Verzeichnisse wie normale Dateien im Datenbereich liegen und dynamisch erweitert werden können. Die Sonderstellung des Root-Verzeichnisses stammt aus den Anfängen von MS-DOS als es nur ein einziges Verzeichnis ohne Unterverzeichnisse gab. Durch die feste Länge des Root-Verzeichnis kann es dann natürlich vorkommen, dass dieses dann irgendwann einfach voll ist. Vom Inhalt der Blöcke her sind das Root-Verzeichnis und alle anderen Verzeichnisse gleich aufgebaut. Die Datenblöcke enthalten 32 Bytes lange Einträge für die im Verzeichnis enthaltenen Dateien und Unterverzeichnisse. Bei FAT16 werden von diesen 32Byte allerdings nicht alle verwendet:
| Offset | Länge | Value |
|---|---|---|
| 0 | 8 bytes | Name |
| 8 | 3 bytes | Extension |
| 11 | byte | Attribute
(00ARSHDV)
0: unused bit
A: archive bit,
R: read-only bit
S: system bit
D: directory bit
V: volume bit
|
| 22 | word | Zeit |
| 24 | word | Datum |
| 26 | word | erster Cluster |
| 28 | dword | File Grösse |
MS-DOS-Datei <-> Unix - Datei
In einem Verzeichnis-Eintrag für eine Datei im FAT16 Filesystem sind
viele Informationen nicht gespeichert, die UNIX benötigt (über die
Funktion getattr). So gibt zum Beispiel keine UderIDs und GroupIDs,
keine Zugriffsrechte wie unter Unix (lediglich ein Read-only bit)
und neu eine Zeit (modification time) statt 3 wie unter Unix.
Die User-ID,Group-ID und die Zugriffsrechte speichern wir daher in
für das Filesystem globalen Variablen, die über den mount-Befehl
gesetzt werden können. Ausserdem setzen wir das x Bit für alle
Verzeichnisse und löschen die read Bits für alle Read-only Dateien.
Da die Zeiten unter DOS ein völlig anderes Format haben als unter
Unix, müssen wir diese jeweils umrechnen.
Inode-Nummern
Jedem File sollte eine eindeutige Inode-Nummer zugewiesen werden.
Unser erster Ansatz war, die erste Cluster-Nummer dafür zu verwenden.
Allerdings gab das Probleme, da Dateien der Länge 0 im FAT16 Filesystem
keinen Datencluster zugeordnet haben und daher alle die Inodenummer 0
erhalten.
Der nächste Ansatz war, die Position des Directory-Eintrags auf der
Platte geeignet zu kodieren, da diese für jedes File eindeutig ist.
Dazu nehmen wir die Blocknummer, in der der Eintrag zu finden ist und
den Index des Eintrags in diesem Block. Erst hatten wir die Blocknummer
um 16 Bit nach links geshiftet und dann den Index aufaddiert, was
eigentlich keine Probleme machen sollte, da eine Inodenummer als
u_longlong_t definiert ist, also 64 bit lang ist.
Allerdings stellte sich heraus, dass diverse Programme dann doch
Probleme haben mit Inodenummern die nicht in einen 32bit int passen.
Daher shiften wir die Blocknummer jetzt nur noch um 8 bit, wodurch
die erzeugten Inodenummern in ein int passen. Und 8bit reichen für
den Index innerhalb eines Blocks auch locker aus, da in einen
512 byte Block genau 16 Verzeichniseinträge passen.
Kommunikation mit dem Device
Wir arbeiten auf einem blockorientierten Device.
Zur Kommunikation mit dem Device verwenden wir die Funktionen
bread, bwrite und brelse aus sys/buf.h . Diese ermöglichen
gepuffertes blockweises Lesen auf dem Device.
# cat /dev/sda2 > zipimageAuf eine Festplatte des Solarisrechners kopieren, und ans Loopdevice hängen.
# ./loplk -link /usr/tmp/zipimage /dev/rloop0Das FAT-Dateisystem laden und das Device mounten.
# modload fatfs # ./fatmount -u cnwawers -p 640 /dev/loop0 /mnt starting mount command mount: trying to mount ... doneund fertig ist die kleine Spielwiese.
# cd /mnt # ls -la total 2 drwxr-xrwx 1 root wheel 0 Jan 1 1970 . drwxr-xr-x 27 root other 1024 Mar 12 09:13 .. drwxr-x--x 1 cnwawers immdstud 2048 Jan 7 20:20 backup -rw-r----- 1 cnwawers immdstud 83161 Jun 13 1998 bookma~1.htm drwxr-x--x 1 cnwawers immdstud 2048 Jun 12 1998 cbm drwxr-x--x 1 cnwawers immdstud 2048 May 19 1998 dl drwxr-x--x 1 cnwawers immdstud 2048 Jun 20 1998 docs -rw-r----- 1 cnwawers immdstud 75854 Jan 10 17:35 homewo~1.gz -rw-r----- 1 cnwawers immdstud 679 Mar 8 16:06 maindir drwxr-x--x 1 cnwawers immdstud 4096 Mar 8 22:07 pix -rw-r----- 1 cnwawers immdstud 74767 Jan 8 21:45 takeme~1.gz -rw-r----- 1 cnwawers immdstud 2810635 Mar 11 22:16 tar.tgz -rw-r----- 1 cnwawers immdstud 631 Mar 8 16:05 test #