Auf der Schwelle zum Licht: Die obere Ebene der Dateiverwaltung

Im März-Heft hatten wir uns schon um die Dateiverwaltung des GEMDOS gekümmert. Damals ging es darum, wie Daten von bzw. zu Massenspeichern wie Floppy, Harddisk und RAM-Disk übertragen werden. Darauf baut nun die obere Ebene der Dateiverwaltung auf. Hier sind die Dateioperationen vom Erzeugen bis zum Löschen zu finden.

Der “File Descriptor”

Für jede geöffnete Datei legt GEM-DOS eine Struktur an, die ich “File Descriptor” (FD) genannt habe (Abb. 1). Die Erklärungen beziehen sich zunächst auf Anwenderdateien. Di-rectories und FATs haben auch ihre FDs, die sich allerdings in einigen Einzelheiten unterscheiden, was im Anschluß erläutert wird.

Die FDs enthalten zum einen Angaben über die Datei, die dem Directory entnommen werden. Dazu zählen der “timestamp”, bestehend aus Erstellungszeit (‘fd_time’) und Erstellungsdatum (‘fd_date’), der Start-Cluster (‘fd_stcl’) und die Dateilänge in Bytes (‘fd_len’). Interessanterweise fehlt hier das Dateiattribut, was von GEMDOS sowieso recht stiefmütterlich behandelt wird.

Im Directory liegen diese Werte im Intel-Format vor, bei dem die Reihenfolge von nieder- und höherwertigen Bytes gegenüber dem Motorola-Format vertauscht ist. Dadurch ist das Directory PC-DOS-kompatibel. ‘fd_stcl’ und ‘fd_len’ liegen im FD im Motorola-Format, ‘fd_time’ und ‘fd_date’ dagegen im Intel-Format vor.

‘fd_dirch’ enthält zwei Flags mit sehr unterschiedlichen Aufgaben.

Bit 0 wird gesetzt, wenn ‘fd_stcl’ oder lfd_len' im FD geändert wurden (bei dem timestamp funktioniert dies nicht, s.u. bei ‘Fdatime’). Beim Schließen der Datei werden dann die Änderungen im FD ins Directory übertragen.

Bit 1 wird von der Directory-Verwaltung benutzt, worauf wir in der nächsten Folge noch zurückkommen. Es wird gesetzt, wenn ein Directory einmal ganz gelesen wurde, d.h., wenn bei einer Suchoperation im Directory die gesuchte Datei einmal nicht gefunden wurde.

Beide Bits werden nie zurückgesetzt (außer natürlich beim Anlegen des FD), was zwar unsauber ist, aber funktioniert (s. Beschreibung von ‘Fclose’).

Die anderen Bits werden nicht benutzt.

‘fd_dmd’ zeigt auf den Drive Media Descriptor (DMD) des Laufwerks, auf dem sich die Datei befindet. Der DMD wurde in der letzten Folge erklärt.

Directories werden nicht nur mit den FDs verwaltet, sondern benötigen noch von mir ‘Directory Descripto-ren’ “getaufte” Strukturen. Sie stellen die Directory-Hierarchie dar und werden ebenfalls nächsten Monat abgehandelt. ‘fd_dirdd’ zeigt auf den Directory Descriptor des Directories, das zur Datei gehört.

typedef struct
{ FD *fd_link;	/*	Zeiger auf nächsten Daten-FD des gleichen Directories */
	int fd_dirch;	/* Bit 0=1: Directory-Änderung erfolgt */
					/* Bit 1=1: Directory einmal ganz durchsucht */ 

	unsigned int fd_time; /* Erstellungs-Zeit */ 
	unsigned int fd_date; /*	Erstellungs-Datum */
	int fd_stcl:	/*	Start-Cluster */
	long fd_len;	/*	Dateilänge in Bytes */
	DMD *fd_dmd;	/*	Zeiger auf zu Datei gehörenden DMD */
	DD *fd_dirdd;	/*	Zeiger auf DD	des	zugehörigen	Directories */
	FD *fd_dirfd;	/*	Zeiger auf FD	des	zugehörigen	Directories */
	long fd_dirpos;	/*	Position des eigenen Eintrags im Directory */
	long fd_fpos;	/*	akt. Zugriffs-Position in Datei */
	int fd_ccl;		/*	Cluster zu 'fd_fpos' */
	int fd_csec;	/*	Sektor zu 'fd_fpos' */
	int fd_clpos;	/*	akt. Zugriffs-Position in	Cluster */
	int fd_unused;	/*	unbenutzt */
	FD *fd_multi;	/*	Zeiger auf anderen FD der	gleichen Datei */
	int fd_mode;	/*	Zugriffsmodus beim Öffnen	der Datei */
} FD;

Abb. 1: Der File Descriptor (FD)

'fcLdirfd' zeigt auf den FD des zugehörigen Directories. ‘fd_dirpos’ ist die Dateiposition des Eintrages der Datei im eigenen Directory. Alle Zeichen einer Datei sind von Null an aufwärts durchnumeriert. Null bezeichnet somit den ersten Eintrag eines Directories, 32 den zweiten usw. (Directory-Einträge sind 32 Byte lang).

Als nächstes kommen einige Angaben über die aktuelle Dateiposition in der Datei selbst. Bekanntlich operieren Dateizugriffe mit ‘Fread’ und ‘Fwrite’ immer ab der aktuellen Dateiposition, die mit ‘Fseek’ neu festgelegt werden kann. ‘fd_fpos’ ist nun direkt diese Dateiposition (Null = Dateianfang). ‘fd_ccl’ ist der Cluster, ‘fd_csec’ ist der Sektor (in GEM-DOS-Zählweise), der zur Dateiposition gehört. ‘fd_clpos’ bezeichnet die Dateiposition relativ zum Beginn des Clusters.

Ein Beispiel: Auf einer Standard-Diskette (2 Sektoren/Cluster, 512 Byte/Sektor) belegt eine Datei die Cluster 2 und 3, also die Sektoren 4 bis 7 (GEMDOS-Zählung!). Die Dateiposition (‘fd_fpos’) 1538 verweist auf das Zeichen Nr. 2 im letzten Sektor. ‘fd_ccl’ hat demnach den Wert 3, ‘fd_csec’ ist 7 und ‘fd_clpos’ autet 514.

Der beim Öffnen einer Datei angegebene Zugriffsmodus wird in fd_mode’ festgehalten. fd_unused’ wird nicht benutzt.

Mit ‘fd_link’ und ‘fd_multi’ sind FDs miteinander verkettet (s.u.).

Auch das Root Directory (RD) hat -einen eigenen FD, der beim erstmaligen Ansprechen eines Laufwerks automatisch eingerichtet wird. Die Dateilänge ist dabei durch die Anzahl der RD-Sektoren festgelegt. Der erste Cluster ist negativ entsprechend der GEMDOS-Cluster-Zählung (siehe letzte Folge: ‘rdst’-Wert). Datum und Zeit sind Null (nicht definiert). ‘fd_multi’ und ‘fd_link’ sind ebenfalls NIL. Da das Root Directory in keinem anderen Directory verankert ist, sind ‘fd_dirdd’, ‘fd_dirfd’ und ‘fd_dirpos’ stets NIL. Auch ‘fd_mode’ ist unbenutzt und daher immer Null.

Subdirectories haben keine Länge (in ihrem Parent Directory ist Null als “Dateilänge” eingetragen). Da Subdirectories in ihrer Größe nicht begrenzt sind (außer durch die Gesamtkapazität des Mediums), wird ‘fd_len’ auf $7FFFFFFF gesetzt. Dies ist die größte positive 32-Bit-Zahl und damit die maximale von GEMDOS verwaltbare Dateigröße. Dadurch wird erreicht, daß innerhalb von Subdirectories beliebig gelesen und geschrieben werden kann (bei einer zu kleinen Länge würde ‘f_seek’ einen Fehler melden). Das eigentliche Verlängern eines Subdirectories (durch Anfügen eines weiteren Clusters) klappt trotzdem, da dieser Mechanismus hier nicht von der Dateilänge abhängt.

Wie beim Root Directory sind ‘fd_link', ‘fd_multi’ und ‘fd_mode’ unbenutzt und Null.

Auch der FD der FAT wird beim erstmaligen Zugriff auf ein Laufwerk automatisch angelegt.

Bei ihm sind überhaupt nur ‘fd_stcl’ (negative GEMDOS-Sektornummer), ‘fd_dmd’, ‘fd_fpos’, ‘fd_clpos’ und 'fd_len’ (FAT-Länge in Bytes, errechnet aus ‘fsiz’ des BPB) benutzt. Alle anderen Komponenten des FD werden verständlicherweise nicht verwendet und sind Null.

Die FD sind in ein großes Netz der verschiedensten Strukturen eingebunden, worauf wir erst nach Behandlung der Directory Descriptoren zurückkommen werden.

Die FDs werden dynamisch verwaltet. Mit der “internen Speicherverwaltung” wird für sie Platz reserviert, wenn sie gebraucht werden. Beim Schließen der Dateien, Mediumwechsel, usw. wird der Speicherplatz wieder frei.

Dazu noch eine Ergänzung für diejenigen unter Ihnen, die den Artikel über die Speicherverwaltung kennen: Ein FD benötigt 50 Byte; aufgerundet ergibt dies vier 8-Wort-Einheiten (482 = 64). Daher finden sich freigegebene FDs in der ‘mifl’-Liste 4. In dieser Liste finden sich auch die oben erwähnten Directory Descriptoren. Die 4er-Liste wird somit am meisten beansprucht und sorgt am ehesten dafür, daß der GEMDOS-Speicher knapp wird. Daher wird diese Liste von den “Mehr-Ordner-Programmen” oder dem Atari Harddisk-Treiber erweitert (manchmal auch die Liste 3 für die DMDs, obwohl dies nichts bringt), um den Exitus des GEMDOS hinauszuzögern.

Eine kleine Bemerkung am Rande: Wenn das nicht benutzte ‘fd_unused’ weggelassen würde, bräuchte der FD nur 48 Bytes und somit nur drei 8-Wort-Einheiten. Dies brächte 16 Bytes Ersparnis pro FD, was den sowieso knappen internen Speicher entlasten würde...

Datei-Handles

Die Anwenderdateien, also die Dateien, die von Programmen aus angesprochen werden (im Gegensatz zu Directory- und FAT-Dateien), erhalten beim Eröffnen mit ‘Fcreate’/ ’Fopen’ ein ‘Handle’ zugewiesen. Diese Handles (zu deutsch “Griff’, also bleiben wir lieber bei Handle) sind aus der Sicht von GEMDOS nur für die Kommunikation mit der “Außenwelt”, sprich den Anwender-Programmen, zuständig. Bei der ersten sich bietenden Gelegenheit wird der sich hinter dem Handle verbergende FD ermittelt. Alle internen Dateifunktionen operieren auf FDs, so daß sie auch für die Directory- und FAT-Dateien benutzt werden können.

Für Dateien werden die sogenannten “Non-standard Handles” vergeben. Dies sind Zahlen zwischen 6 und 80; somit kann GEMDOS theoretisch bis zu 75 (!) Dateien gleichzeitig geöffnet haben. In diversen Atari-Dokumentationen steht etwas von 40 Dateien (Handles bis 45), im Januar-Heft hatte ich noch 69 geschrieben (rechnen müßte man können). Vielleicht hat Digital Research hier GEMDOS selbst nicht so ganz getraut, oder es liegt eine Verwechslung mit den “Pfad-Handles” (nächste Folge) vor, von denen es nämlich 40 (eigentlich nur 39) geben kann.

Daneben gibt es noch die “Standard Handles” (0 bis 5) und die “Device Handles” (-1 bis -3), mit denen wir uns in den Folgen über die zeichenorientierten Geräte und der I/O-Umleitung beschäftigen werden.

Für’s erste bleiben wir also bei den Non-standard Handles, genauer sogar bei den “Datei-Handles”. Das sind die Handles, die letztendlich (nach Berücksichtigung aller Möglichkeiten der I/O-Umleitung) eine Datei auf einem blockorientierten Massenspeicher bezeichnen.

Der “File Control Block”

Für jedes Non-standard Handle gibt es eine kleine Struktur, die ich “File Control Block” (FCB) genannt habe (Abb. 2).

typedef struct
{	FD *f_fd;	/* Zeiger auf FD der Datei */
	PD *f_pd;	/* Zeiger auf Prozeßdescriptor des besitzenden Prozesses */ 
int f_cnt; /* Anzahl der vergebenen Handles */ 
} FCB;

Abb. 2: Der File Control Block (FCB)

‘f_fd’ ist ein Zeiger auf den zugehörigen FD (negative Werte werden für Devices benutzt). NIL zeigt an, daß das Handle nicht belegt ist. Mit ‘f_pd’ wird auf den Prozeßdescriptor des Prozesses verwiesen, der die Datei eröffnet hat. Auch hier steht NIL, wenn das Handle nicht belegt ist. ‘f_cnt’ zählt mit, an wieviele Aufrufer dieses Handle vergeben wurde. Ein Handle kann in Zusammenhang mit der I/O-Umleitung (kommt auch noch irgendwann einmal) mehr als einmal vergeben werden. Beim Schließen von Dateien soll hiermit bemerkt werden, wann die Datei von allen Handle Besitzern geschlossen wurde und FCB und FD somit freigegeben werden müssen.Die FCBs sind statisch in einer Tabelle (‘fcbx’) organisiert, so daß mit einem Handle direkt auf den FCB zugegriffen werden kann. Das heißt, ‘fcbx[handle-6]’ ist der FCB für ‘handle’ (die Subtraktion von 6 erfolgt, da Non-standard Handles erst bei 6 anfangen).

‘fcbx’ ist die einzige globale GEM-DOS-Variable für die Dateiverwaltung, die noch nicht in einer der letzten Folgen vorkam, trotzdem bekommt sie eine eigene Abbildung spendiert (Abb. 3). Die nicht legal benutzbaren Adressen beziehen sich wie üblich auf das “alte TOS” vom 6.2.1986 bzw. auf das "Blitter-TOS” vom 22.4.1987.

FCB fcbx[75];	/* $58f0/$8092: FCB-Tabelle für alle Dateien */

Abb. 3: Globale GEMDOS-Variablen für Dateiverwaltung

GEMDOS kontrolliert übrigens nicht, ob die Handles überhaupt im zulässigen Bereich von -3 bis 80 liegen. Die Fehlermeldung EIHNDL besagt nur, daß unter einem gültigen Handle keine geöffnete Datei registriert ist. Bei anderen Handles greift GEMDOS munter auf den nach ‘fcbx’ liegenden Speicherbereich zu und interpretiert das dort gefundene “Irgendetwas” als FCB... womit die Chancen auf einen Bus oder Address Error nicht schlecht stehen.

Multitasking with GEMDOS? No!

Nicht nur in der Prozeßverwaltung des GEMDOS finden sich einige Ansätze zum Multitasking, sondern auch in der Dateiverwaltung sind einige Strukturen, die aber so unausgereift sind, daß sie wohl nur als Basis für zukünftige Weiterentwicklungen (die es wohl aller Voraussicht nach nie geben wird) gedacht sein können. GEMDOS erlaubt das mehrfache Öffnen von (Anwender-)Dateien durch einen oder mehrere Prozesse. Außerdem gibt es noch die I/O-Umleitung, die hier nicht berücksichtigt wird. Bei jedem neuen Öffnen wird ein eigenes Handle mit FCB sowie ein eigener FD vergeben.

Alle FDs, deren Dateien im gleichen Directory liegen, sind in einer einfach verketteten Liste verknüpft (Abb. 4). Die FDs werden durch ‘fd_link’ verbunden (Ende durch NIL markiert), wobei die zuletzt eröffnete Datei am Anfang der Liste zu finden ist. Ein Zeiger auf den Beginn der Liste steht im Directory Descriptor (‘dd_fdl’), bei dem ich Sie erneut auf das nächste Mal vertrösten muß.

Abb. 4: Verkettung der FDs untereinander

Es existiert noch eine zweite Art von Listen mit FDs, in der alle FDs, die zur gleichen Datei gehören, aufgeführt sind (ebenfalls Abb. 4). Sie wird mit dem ‘fd_multi’-Zeiger realisiert .Ende wieder durch NIL markiert). Hier beginnt die Liste mit der zuerst eröffneten Datei, d.h. bei jedem Öffnen wird der FD hinten angehängt. Der Beginn dieser Liste ist jedoch nirgendwo vermerkt, so daß ein Arbeiten mit dieser Liste sich recht schwierig gestalten könnte. Kein Wunder, daß GEMDOS sich nicht groß darum kümmert.

In dem abgebildeten Beispiel wurde nun zuerst eine Datei mit dem Handle 6. dann eine zweite in einem anderen Directory mit dem Handle 7 eröffnet. Eine weitere Datei dieses Directories bekam nun Handle 8 und wurde an den Anfang der FD-Liste gehängt. Zum Schluß wurde die Datei mit Handle 7 ein zweites Mal eröffnet, so daß der zu Handle 9 gehörende FD nicht nur mit 'fd_link' sondern auch mit ‘fd_multi’ (von Datei mit Handle 7 aus) verkettet wurde.

Bei Dateioperationen könnte nun mit diesen Listen festgestellt werden, ob eine Datei von mehreren Prozessen oenutzt wird, um Zugriffsrechte zu regeln und Kollisionen zu vermeiden. Davon wird aber nur bei ‘Fdelete’ und Fopen’ - und das auch noch in mangelhafter Form - Gebrauch gemacht (s.u.). Auch Änderungen in den FDs (z.B. Dateilänge) müßten eventuell in die anderen FDs der selben Datei übertragen werden, was aber ebenfalls nicht gemacht wird. Daher ist es nicht ratsam, ernsthaft mit mehrfach geöffneten Dateien zu arbeiten, außer vielleicht bei reinen Lesezugriffen. Die ‘Multi-Liste’ wird gar noch nicht einmal korrekt verwaltet. Beim Schließen von Dateien werden FDs nicht ausgehängt, so daß sie im Prinzip nicht zu gebrauchen ist.

Im FCB ist zwar auch der besitzende Prozeß registriert, aber trotzdem erfolgen i.a. (außer bei ‘Fdelete’) keine Kontrollen. Dies bedeutet, daß man -Kenntnis der Handles vorausgesetzt - ohne weiteres Dateien fremder Prozesse bearbeiten kann.

GEMDOS-Funktionen (TRAP #1)

Es folgt die Beschreibung der GEMDOS-Funktionen, die diesmal den meisten Raum in Anspruch nimmt. Hier wird nur der direkte Zugriff auf Disk-Dateien behandelt. Die Dateifunktionen lassen sich auch für die zeichenorientierten Geräte (Bildschirm usw.) benutzen, worauf wir in einer späteren Folge zurückkommen.

Die Directory-Funktionen sowie ‘Frename’ werden ebenfalls auf ein anderes Mal verschoben.

Wenn bei der Beschreibung der Arbeitsweise vom Lesen oder Schreiben von FAT oder Directories die Rede ist, so geschieht dies meist mit den internen Versionen von ‘Fread’, ‘Fwrite’ und ‘Fseek’ (s. dort). Bei Directory-Operationen wird manchmal auch direkt auf den Sektorpuffer zugegriffen (siehe März-Folge).

Das Dateiattribut kann hier leider nicht mehr erklärt werden. Ein Bit dieses Attributs ist für den ‘read only’-Status zuständig, ‘readonly’ ist mit dem Desktop über die “zeige-Info”-Funktion zugänglich und wird dort als ‘nur lesen’ bezeichnet. Dateien mit diesem Status sollten nicht verändert oder gar gelöscht werden dürfen.

Funktion $3c Fcreate

int Fcreate(char *path, int attr)

Mit ‘Fcreate’ wird eine Datei ‘path’ neu angelegt. Wenn die Datei schon vorhanden war, wird sie vorher gelöscht. Die Datei hat hinterher die Länge Null.

‘attr’ ist das Dateiattribut, das die Datei beim Anlegen erhält. Mit ‘Fcreate’ können keine Subdirectories erzeugt werden. Das entsprechende Attribut-Bit wird ignoriert. Im Gegensatz zu anderen Funktionen erfolgt dabei keine Fehlermeldung. Normalerweise wird die Datei zum Lesen und Schreiben geöffnet. Wenn das ‘read only’-Bit des Attributs gesetzt ist, wird die Datei nur zum Lesen geöffnet. Da sie nach einem ‘Fcreate’ noch leer ist, kann man nicht mehr sehr viel machen, so daß man sie am besten gleich wieder schließt. Dies ist übrigens kein Bug, sondern mit voller Absicht so programmiert (“it’s a feature, not a bug”).

Die Datei läßt sich aber trotzdem beschreiben; der ‘read only’-Modus wird nämlich nicht immer richtig erkannt... (s. ‘Fwrite’).

Rückgabewerte:

-1..-31 BIOS-Fehlermeldung bei Diskzugriff
-34L (EPTHNF) Pfad nicht gefunden, unbekanntes Laufwerk, illegaler Dateiname, interner Fehler (keine Pfad-Handles, zu wenig interner Speicher)
-35L (ENHNDL) Kein Datei-Handle mehr frei
-36L (EACCDN) Datei existiert schon als Subdirectory oder ‘read only’-Datei, Medium oder Root Directory voll.
-39L (ENSMEM) interner Fehler (zu wenig interner Speicher)
-3..80 Handle, unter dem die Datei angesprochen werden kann (>= 6 bei Disk-Dateien).

Arbeitsweise

Das Subdirectory-Bit im Dateiattribut wird gelöscht und damit ignoriert. Wenn der Pfad nicht gefunden werden kann oder der Dateiname ungültig ist (‘. ’ und ‘..’ werden abgewiesen) wird abgebrochen. Die Directory-Verwaltung wird vorbereitet (im Vorgriff: für das Directory wird ein FD bereitgestellt, falls noch keiner existiert). Falls die Datei schon existiert, Wird sie erst einmal gelöscht, außer wenn es sich um eine ‘read only’-Datei oder ein Subdirectory handelt (Abbruch).

Nun wird im Directory ein freier Eintrag gesucht. Dabei wird, wenn die Datei schon existierte, auf jeden Fall der gerade eben gelöschte Eintrag wiederverwendet. Dies ist notwendig, da Referenzen in FDs auf den Directory Eintrag (‘fd_dirpos’) sonst nicht mehr stimmen würden. Außerdem entfällt langes Suchen nach einem freien Eintrag.

Der Directory-Eintrag wird initialisiert: Dateiname und Attribut werden übertragen, die Systemzeit wird als timestamp genommen, die Dateilänge ist Null und der Start-Cluster wird auf Null gesetzt. An dieser Null, die keine gültige Clustemummer darstellt, wird bei späteren Schreibzugriffen erkannt, daß noch gar kein Cluster vergeben wurde.

Nun wird dafür gesorgt, daß alle GEMDOS-Sektorpuffer zurückgeschrieben werden, damit die Änderung des Directories auch auf dem Speichermedium wirksam wird.

Jetzt wird die Datei erst richtig eröffnet. Dazu wird eine der internen Open-Routinen, die auch das Handle generiert, benutzt (s. ‘Fopen’). Der Zugriffsmodus hierbei ist bei ‘read only’ 0 (“nur lesen”), sonst 2 (“lesen und schreiben”).

Wenn beim Öffnen kein Fehler auf-tritt, wird das ‘fd_dirch’-Flag für Directory-Änderung (Bit 0) gesetzt. Diese Maßnahme scheint mir überflüssig zu sein, da der neue Directory-Eintrag schon gesichert ist und nachfolgende Schreibzugriffe, da sie die Datei vergrößern, das Flag sowieso setzen. Vielleicht wurde hier nach dem Motto “doppelt hält besser” verfahren.

Der relativ bekannte Fehler, daß bei einem ‘Fcreate’ manchmal nicht bemerkt wird, daß die Datei schon vorhanden ist, der wiederum dazu führt, daß dann zwei Dateien unter gleichem Namen im Directory stehen, wird durch die Directory-Verwaltung verschuldet.

Funktion $3d Fopen

int Fopen(char *path, int mode)

Eine schon existierende Datei ‘path’ wird eröffnet, ‘mode’ bestimmt die erlaubten Zugriffsarten:

0 nur lesen
1 nur schreiben
2 lesen und schreiben

Rückgabewerte:

-1..-31 BIOS-Fehlermeldung bei Diskzugriff
-33L (EFILNF) Pfad oder Datei nicht gefunden, unbekanntes Laufwerk, illegaler Dateiname, Datei ist Subdirectory, interner Fehler (keine Pfad-Handles, zu wenig interner Speicher)
-35L (ENHNDL) Kein Datei-Handle mehr frei
-36L (EACCDN) Datei ist ‘read only’ und ‘mode’ ist nicht 0
-39L (ENSMEM) interner Fehler (zu wenig interner Speicher)
-3..80 Handle, unter dem die Datei angesprochen werden kann (>= 6 bei Disk-Dateien).

Arbeitsweise

Nachdem GEMDOS sich vergewissert hat, daß die Datei auch tatsächlich existiert, werden der Zugriffsmodus und das ‘read only ’-Bit des Dateiattributs überprüft. Andere Werte für ‘mode’ als 0,1 und 2 werden kommentarlos hingenommen.

Die eigentliche Arbeit macht die nachfolgende Open-Prozedur, die auch von ‘Fcreate’ aufgerufen wird. Es wird nach einem freien Handle in ‘fcbx’ gesucht. Dabei wird immer das kleinste, freie Handle gefunden (bei eigenen Experimenten lassen sich somit “Handle-Verluste” leicht erkennen). Ein freies Handle wird hier an einem NIL des ‘f_pd’-Zeigers (nicht von ‘f_fd’!) erkannt. ‘f_pd’ und ‘f_cnt’ werden gesetzt (letzteres auf 1). Ein FD wird von einer eigenen Routine bereitgestellt, von der auch ‘f_fd’ des FCB definiert wird (merkwürdig genug). Wenn hierbei ein Fehler auftritt, wird er zwar korrekt an den Aufrufer zurückgegeben, aber der FCB bleibt halb ausgefüllt! Dies hat zur Folge, daß das Handle belegt bleibt. Bei der Prozeßterminierung wird sogar versucht, diese Datei zu schließen. Dies wird zwar von ‘f_close’ verweigert, aber selbst dann bleibt das Handle “verbraucht”. Welch ein Glück, daß der einzige Fehler, der hier auftreten kann, ein Mangel an “internem Speicher” ist, der sowieso mehr oder weniger tödlich ist. Trotzdem wird hier deutlich, welche Seiteneffekte in GEMDOS hineinprogrammiert wurden.

Viel eleganter wäre es gewesen, wenn die Funktion, die das Handle ermittelt, erst nach geglücktem Einrichten des FDs den FCB initialisiert, und zwar alle drei Werte auf einmal. Die untergeordnete Funktion bräuchte dann nur noch mit dem FD zu hantieren und sich nicht mehr um den FCB zu kümmern.

Doch nun zum Einrichten des Daten-FDs. Die meisten Komponenten werden dem Directory und dessen Strukturen (FD und Directory Descriptor) entnommen. Der Zugriffsmodus wird in ‘fd_mode’ gespeichert. Die Dateiposition wird auf den Dateianfang gesetzt.

Der FD wird in die Anwenderdatei-FD-Liste des Directories vorne eingehängt. Zuvor wird die Liste jedoch durchsucht, ob die Datei schon einmal eröffnet wurde. Wenn dies nicht der Fall ist, werden timestamp, Dateilänge und Start-Cluster ebenfalls aus dem Directory übernommen, ansonsten aus dem schon bestehenden FD kopiert.

Dabei werden übrigens zwei Byte zuviel kopiert, was aber keinen Schaden anrichtet. Dies ist auch eine typische GEMDOS-Macke, die sich auch an anderen Stellen findet.

Bei mehrfacher Eröffnung wird der ‘fd_multi’-Zeiger des “alten” FD auf den “neuen” gesetzt. Da neue FDs immer vorne in die FD-Liste eingehängt werden, ist sichergestellt, daß alle zu einer Datei gehörenden FDs durch ‘fd_multi’ miteinander verbunden werden. ‘fd_multi’ wird sonst nirgendwo im GEMDOS benutzt; auch nicht beim Schließen von Dateien, wo FDs aus der Liste ausgehängt werden müßten.

Funktion $3e Fclose

int Fclose(int handle)

Eine zuvor mit ‘Fcreate’/’Fopen’ eröffnete Datei wird geschlossen. Dabei werden alle noch gepufferten Änderungen der Datei selbst und seines Directory-Eintrags auf das Medium geschrieben.

Rückgabewerte:

-1..-31 BIOS-Fehlermeldung bei Diskzugriff
-37L (EIHNDL) Handle ungültig
-65L (EINTRN) Interner Fehler (es existiert kein FD zum Handle)
-0L (EOK) alles ok

Arbeitsweise

Das eigentliche Schließen einer Datei (auch Directory-Datei) übernimmt eine interne Close-Routine (‘f_fclose’). Danach wird die Datei auf FCB-Ebene geschlossen. Dazu wird ‘f_cnt’ um eins erniedrigt. Wenn das Datei-Handle nur einmal vergeben war (‘f_cnt’ jetzt Null), wird der FD der internen Speicherverwaltung zurückgegeben und ‘f_fd’ und ‘f_pd’ werden auf NIL gesetzt, so daß das Handle frei wird.

Die von ‘f_fclose’ gelieferte Fehlermeldung wird nun zurückgegeben. Hier soll schon verraten werden, daß ‘Fclose’ nur richtig funktioniert, wenn ein FD nur unter einem Handle ansprechbar ist, was aber bei Verwendung der I/O-Umleitung nicht der Fall sein muß. Auch mehrfach vergebene Handles (‘f_cnt’ > 1) führen zu Fehlfunktionen. Darauf kommen wir bei der Folge über die I/O-Umleitung noch zurück.

‘f_fclose’ bekommt einen FD und ein besonderes Flag übergeben, welches die auszuführenden Aktionen steuert. Bei Directory-Änderungen, die am gesetzten Bit 0 von ‘fd_dirch’ erkannt werden, werden der timestamp, die Dateilänge und der Start-Cluster vom FD ins Directory übertragen. Falls der FD zu einer Directory-Datei gehört, wird an Stelle der intern verwendeten Pseudo-Dateilänge $7FFFFFFF eine Null geschrieben (PC-DOS-Kompatibilität).

Bit 0 von ‘fd_dirch’ müßte jetzt eigentlich zurückgesetzt werden, um zu verhindern, daß bei nachfolgenden ‘f_fclose’-Aufrufen erneut gesichert wird. Da ‘f_fclose’ zur Zeit bei Daten-FDs nur zum endgültigen Schließen benutzt wird, fällt dies nicht auf. Bei Directory- und FAT-FDs treten Änderungen des eigenen Directory-Eintrages i.a. nicht auf.

Je nach Steuer-Flag, insbesondere bei Daten-FDs, wird der FD aus der FD-Liste des Directories entfernt. Wenn er dort nicht aufzufinden ist, wird mit der Fehlermeldung EINTRN abgebrochen.

Geht alles gut, werden alle Sektoren der Sektorpufferung mit ‘f_swrite’ herausgeschrieben. ‘f_swrite’ schreibt zwar nur die veränderten Sektoren zurück, sabotiert aber faktisch dabei die Sektorpufferung (siehe März-Ausgabe).

Funktion $3f Fread

int Fread(int handle, long count, char *buf)

Es werden ‘count’ Zeichen aus der Datei ‘handle’ in einen bei ‘buf’ beginnenden Speicherbereich geladen.

Rückgabewerte:

-1..-31 BIOS-Fehlermeldung bei Diskzugriff
-37L (EIHNDL) Handle ungültig >= 0 Zahl der tatsächlich gelesenen Zeichen, Vergleich mit ‘count’ gibt Auskunft, ob Fehler auf getreten ist (z.B. Dateiende).

Arbeitsweise

Mit dem durch das handle bestimmten FD wird eine interne Lese-Routine (‘f_fread’) aufgerufen, die auch bei FAT- und Directory-Dateien Verwendung findet.

‘f_fread’ begrenzt nun die Zahl der zu lesenden Zeichen (‘count’) so, daß das Dateiende nicht überschritten werden kann. Wenn nun gar kein Zeichen mehr zu lesen ist, da das Dateiende schon erreicht ist, wird sofort mit OL abgebrochen. Ansonsten kommt die zentrale Lese-/ Schreibroutine ‘f_frw’ zum Einsatz (siehe März-Ausgabe).

‘Fread’ kontrolliert nicht den mit ‘Fcreate’/’Fopen’ festgelegten Zugriffsmodus. Lesen aus einer nur zum Schreiben geöffneten Datei wird nicht verhindert!

Funktion $40 Fwrite

*int Fwrite(int handle, long count, char buf)

Es werden ‘count’ Zeichen aus einem bei ‘buf’ beginnenden Speicherbereich in die Datei ‘handle’ geschrieben.

Rückgabewerte:

-1..-31 BIOS-Fehlermeldung bei Diskzugriff
-37L (EIHNDL) Handle ungültig
>= 0 Zahl der tatsächlich geschriebenen Zeichen, Vergleich mit ‘count’ gibt Auskunft, ob Fehler aufgetreten ist (z.B. Speichermedium voll).

Arbeitsweise

Ähnlich wie bei ‘Fread’ wird mit dem FD eine interne Version ‘f_fwrite’ aufgerufen. Diese macht allerdings nichts weiter, als unmittelbar ‘f_frw’ aufzurufen.

Wie ‘Fread’ kontrolliert ‘Fwrite’ nicht den Zugriffsmodus. Daher ist es ohne weiteres möglich, eine nur zum Lesen geöffnete Datei (auch bei ‘read only’!) zu beschreiben! Dies ist auch der Grund dafür, warum eine mit ‘Fcreate’ als ‘read only’ erzeugte Datei überhaupt noch beschrieben werden kann.

Funktion $41 Fdelete

int Fdelete(char *path)

Die Datei ‘path’ wird gelöscht. Sie sollte dabei nicht geöffnet sein. Rückgabewerte:

-1..-31 BIOS-Fehlermeldung bei Diskzugriff
-33L (EFILNF) Pfad oder Datei nicht gefunden, unbekanntes Laufwerk, illegaler Dateiname, Datei ist Subdirectory, interner Fehler (keine Pfad Handles, zu wenig interner Speicher)
-35L (ENHNDL) Kein Datei Handle mehr frei
-36L (EACCDN) Datei ist ‘read only’, Datei von anderem Prozeß geöffnet -0L alles ok Arbeitsweise

Nach der Ermittlung des Directories, zu dem die Datei gehört, und der Überprüfung des ‘read only’-Bits wird eine interne Routine ‘f_fdelete’ aufgerufen, die auch beim Löschen von Directories verwendet wird.

In ‘f_fdelete’ finden sich wieder einige Teile, die zu einem Multitaskingfähigen GEMDOS gehören würden.

In der Liste des Directories aller offenen Dateien wird die Datei gesucht. Wenn sie gefunden wird und dem eigenen Prozeß gehört, wird sie geschlossen und es wird weiter gesucht (sie könnte ja mehrmals geöffnet sein). Wenn die Datei von einem anderen Prozeß geöffnet wurde, wird sofort mit EACCDN abgebrochen. Je nach Reihenfolge der FDs in der Liste, kann die Datei, wenn sie auch noch vom eigenen Prozeß geöffnet war, schon geschlossen worden sein oder auch nicht. Nach einem EACCDN weiß man also nicht, wie es um die Datei steht und ist ratlos. Hinzu kommt, daß die Datei, wenn sie dem eigenen Prozeß gehört, nur mit ‘f_fclose’ und nicht mit ‘Fclose’ geschlossen wird. Daher ist der FCB noch gültig und das Datei-Handle noch belegt.Da das Handle noch gültig ist, werden Zugriffe auf die schon gelöschte Datei nicht verhindert, was zu einer mittleren Katastrophe führt (ausprobiert habe ich es lieber noch nicht, da ich täglich schon mehr Bomben sehe, als mir lieb ist).

Beim (späteren) Schließen mit 'Fclose’ ist daher der FD schon freigegeben und man erhält ein EINTRN (s. ‘Fclose’), wobei das Handle nun frei wird. Es ist also möglich, eine geöffnete Datei zu löschen, nur darf man sich dann nicht über Fehlermeldungen von ‘Fclose’ wundem.

Es wäre also besser, wenn die FD-Liste zweimal durchsucht würde; einmal, um Zugehörigkeiten zu fremden Prozessen festzustellen, das andere Mal, um die eigenen Dateien zu schließen. Das korrekte Schließen ist jedoch gar nicht so einfach, da ein Aufmf von ‘Fclose’ an Stelle von ‘f_fclose’ auch den FD freigeben würde; was zu Komplikationen führt, die sich aber auch beseitigen lassen. Dann werden die FAT-Einträge aller Cluster der Datei auf Null gesetzt, wodurch die Cluster als frei markiert werden. Das erste Zeichen des Namens im Directory wird auf $E5 gesetzt, woran GEMDOS bei Directory-Operationen gelöschte Dateien erkennt. Anschließend werden mittels ‘f_fclose’ alle Sektorpuffer auf das Speichermedium zurückgeschrieben.

Funktion $42 Fseek

int Fseek(long offset, int handle, int mode)

Die aktuelle Dateiposition der Datei ‘handle’ wird neu gesetzt, ‘mode’ legt fest, worauf ‘offset’ bezogen ist.

0 position = offset
1 position = position + offset
2 position = Dateilänge + offset

Da die neue Position innerhalb der Datei liegen muß, ist ‘offset’ bei ‘mode'=0 immer positiv und bei ‘mode’=2 immer negativ zu wählen. Mit dieser Funktion kann auch die Dateilänge ermittelt werden:

file_len = Fseek(0L,handle,2) Rückgabewerte:

-1..-31 BIOS-Fehlermeldung bei Diskzugriff
-1L (ERROR) interner Fehler in FAT (Datei ist kürzer als erwartet)
-32L (EINVFN) ungültiger Modus (nicht 0,1,2)
-37L (EIHNDL) Handle ungültig
-64L (ERANGE) gewünschte Dateiposition nicht innerhalb der Datei
>= 0 neue Dateiposition (= gewünschte Dateiposition)

Arbeitsweise

Mit ‘mode’ und ‘offset’ wird die gewünschte neue Dateiposition nach obigen Formeln errechnet. Zusammen mit dem aus dem Handle ermittelten FD wird die interne Seek-Funk-tion ‘f_fseek’ aufgerufen, die auch bei Directory- und FAT-Dateien benutzt wird.

‘f_fseek’ prüft die gewünschte Dateiposition und bricht gegebenenfalls mit ERANGE ab. Diese Fehlermeldung wird bei den GEMDOS-eigenen Aufrufen jedoch nie abgefragt. GEMDOS hat offenbar grenzenloses Vertrauen in seine Directory- und FAT-Verwaltung.

Bei einer Position von Null werden einfach die Werte im FD, die die Dateiposition festlegen, auf Null gesetzt.

Ansonsten wird die FAT solange gelesen, bis der zur Position gehörende Cluster gefunden wurde. Bei Directory- und FAT-Dateien berechnet die FAT-Lese-Routine die GEM-DOS-Clusternummern anstatt sie aus der FAT zu holen.

Bei Zielpositionen kleiner als der aktuellen Position werden die Cluster vom Dateianfang aus verfolgt, bei größeren Zielpositionen vom aktuellen Cluster aus. Wenn die FAT-Verwaltung keinen Cluster mehr liefern kann, wird mit ERROR abgebrochen. Dies darf eigentlich aber nicht passieren, da ja vorher sichergestellt wurde, daß der Dateibereich nicht verlassen wird.

Funktion $43 Fattrib

int Fattrib(char *path, int set, char attr)

Das Dateiattribut der Datei ‘path’ wird ermittelt (set = 0) bzw. auf ‘attr’ gesetzt (set =1).

In einigen Dokumentationen ist die Bedeutung von ‘set’ gerade verkehrt herum angegeben.

Auf die Zugriffsberechtigung haben Attribut-Änderungen keinen Einfluß, da das ‘read only’-Bit nur beim Öffnen der Datei überprüft wird.

Rückgabewerte:

-1..-31 BIOS-Fehlermeldung bei Diskzugriff
-33L (EFILNF) Datei nicht gefunden, illegaler Dateiname, Datei ist Subdirectory
-34L (EPTHNF) Pfad nicht gefunden, unbekanntes Laufwerk, interner Fehler (keine Pfad Handles, zu wenig interner Speicher)
>= 0 set=0: ‘attr’ wird unverändert zurückgegeben set= 1: Dateiattribut der Datei ‘path’

Arbeitsweise

Die Directory-Verwaltung ermittelt das zugehörige Directory. Das Attribut wird mit ‘f_fseek’ und ‘f_fread’ bzw. ‘f_fwrite’ gelesen bzw. geschrieben. Beim Schreiben werden mit ‘f_fclose’ alle Sektorpuffer zurückgeschrieben, so daß das neue Attribut auf jeden Fall auf dem Medium gesichert wird.

Da das Attribut nicht im FD gespeichert wird, gibt es hier keine Konflikte zwischen Directory und FD. Daher kann ‘Fattrib’ auch bei geöffneten Dateien angewendet werden.

Funktion $57 Fdatime

int Fdatime(int *buf, int handle,int set)

Der timestamp der Datei ‘handle’ wird gelesen (set = 0) bzw. geschrieben (set =1).

In buf[0] steht dabei die zu schreibende bzw. die gelesene Zeit, in buf[1] findet sich das Datum (beide im GEMDOS-Format).

Das Ändern des timestamps ist auch bei ‘read only’-Status möglich. Auch der Zugriffsmodus beim Öffnen spielt keine Rolle.

Hier hat der Fehlerteufel mal wieder gewaltig zugeschlagen:

Das Setzen funktioniert nicht nach einem ‘Fcreate’ oder einem ‘Fopen’, wenn die Datei verlängert wurde (der alte timestamp überschreibt beim ‘Fclose’ den neu gesetzten).

Beim Schreiben liegen Zeit und Datum in ‘buf’ nach dem Aufruf im Intel-Format vor. Da dies wohl eher ein Bug als Absicht ist, sollte der Inhalt von ‘buf’ nachher nicht weiterverwendet werden, da dieser Fehler vielleicht noch einmal behoben wird. Die Gültigkeitskontrolle beim Handle wurde vergessen. Bei einem ungültigen Handle gibt es einen Bus Error (2 niedliche Bömbchen).

Arbeitsweise

Hier glaubten die Programmierer wohl, es sich besonders einfach machen zu können. Die Funktion ist nämlich genauso wie ‘Fattrib’ aufgebaut. Hinzu kommt natürlich die Konvertierung vom Motorola- ins Intel-Format, die direkt in ‘buf' durchgeführt wird, wodurch sich der zweite Fehler erklärt.

‘Fdatime’ operiert auf einer schon offenen Datei, der timestamp wird aber nur direkt im Directory geändert. Daher steht im FD noch der alte timestamp. Beim ‘Fclose’ wird er nun vom FD ins Directory “gesichert” und macht somit die Änderung durch ‘Fdatime’ rückgängig, wenn ‘fd_dirch’-Bit 0 gesetzt ist, was nach einem ‘Fcreate’ immer der Fall ist (siehe ‘Fclose’ und ‘Fcreate’).

Wenn man den timestamp mit ‘Fdatime’ liest, bevor die Datei geschlossen wird, erhält man noch den richtigen Wert, da auch das Lesen direkt aus dem Directory erfolgt.

Bei einer Datei, bei der keine Änderungen des Directory-Eintrags vorgenommen werden, geht alles gut, da hier ‘fd_dirch’-Bit 0 nicht gesetzt wird. Zur Demonstration gibt’s zur Abwechslung mal wieder ein kleines Demo-Programm (siehe Listing), mit dem Sie diesen Fehler nachvollziehen können. Sehen Sie sich auch den timestamp von ‘test’ nach dem Programmlauf an (mit “zeige Info” des Desktop).

‘Fdatime’ gibt gleich Anlaß zu einem Patch des TOS, der alle drei Fehler auf einen Schlag beseitigt (Abb. 5).

          Adresse
RAM-TOS   ROM-TOS   ROM-TOS   Bytes (in Hex)
6.2.86    6.2.86    22.4.87

00d372    fc74e4    fc779a    4a 80 66 04 70 db 60 60 20
                              6e 00 08 4a 6e 00 Oe 66 12
                              10 ed 00 07 10 ed 00 06 10
                              ed 00 09 10 ad 00 08 60 16
                              1b 58 00 07 1b 58 00 06 1b
                              58 00 09 1b 50 00 08 00 6d
                              00 01 00 04 70 00 60 2a

Abb. 5: Patch für TOS-Fehler in "Fdatime"

Das neue ‘Fdatime’ liefert EIHNDL bei einem ungültigen Handle und verändert ‘buf nicht mehr. Der time-stamp wird nur in den FD geschrieben bzw. aus ihm gelesen und das Bit 0 von ‘fd_dirch’ wird gesetzt, so daß er beim ‘Fclose’ ins Directory übernommen wird.

Ein sofortiges Sichern der Directory-Sektoren ist hier nicht möglich, da ‘f_fclose’ nicht dafür ausgelegt ist, einen Datei-FD zu sichern, ohne den FD aus der FD-Liste zu streichen. Durch einen kleinen Kniff ließe sich dies zwar doch erreichen, aber so eine kritische Operation ist ‘Fdatime’ nun auch wieder nicht, daß sich “schmutzige Tricks” rechtfertigen würden. Beim einer mehrfach geöffneten Datei müßte der timestamp eigentlich in allen FDs geändert werden, was der Patch in Anlehnung an die Praktiken des GEMDOS nicht macht (Änderungen von ‘fd_stcl’ und ‘fd_len’ werden auch nur in einem FD berücksichtigt).

Ausblick

Eng verknüpft mit der Dateiverwal-tung ist die Handhabung der Directories und ihrer Pfade, mit der wir uns nächstes Mal eingehend beschäftigen werden.

/* Demonstration der Fehler von Fdatime' */

#include <osbind.h>

test() { int fh1;
unsigned int buf[2];

/* Datei kreieren */ 
fh1 = Fcreate("test",0);

if (fh1 < 0)	/* Abbruch bei Fehler */
	return;

	/* von Fcreate generiertes Time stamp lesen */ 
	Fdatime(fh1,buf,0);	/* Time stamp lesen */
	printf("alte Zeit :%04% altes Datum : %04%\n",buf[0],buf[1]);

	buf[0] = 2;				/* Uhrzeit 0:00:02 */
	buf[1] = Tgetdate();	/* akt. Datum behalten */
	printf("gesetzte Zeit:%04% gesetztes Datum: %04%\n",buf[0],buf[1]);
	Fdatime(fh1,buf,1);		/* Time stamp setzen */

	/* Fdatime hat Zeit und Datum ins Intel-Format konvertiert */ 
	printf("gesetzte Zeit:%04%	gesetztes Datum: %04%\n"buf[0],buf[1]):

	/* Time stamp wurde korrekt geschrieben wie Kontrolle zeigt */
	Fdatime(fh1,buf,0);		/* Time stamp lesen */
	printf("gelesene Zeit:%04%	gelesenes Datum: %04%\n",buf[0],buf[1]);

	/* beim Schließen der Datei wird alter Time stamp zurückgeschrieben */
	Fclose(fh1);
}

main()
{
	test();
	Cnecin();
}

Alex Esser
Aus: ST-Computer 06 / 1988, Seite 36

Links

Copyright-Bestimmungen: siehe Über diese Seite