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();
}