Auf der Schwelle zum Licht: Directory-Verwaltung Teil 1

Heute geht es um die Directory-Verwaltung. Da dies wieder ein sehr umfangreiches Kapitel des GEMDOS ist, haben wir es in zwei Teile aufgeteilt. Im ersten Teil beschäftigen wir uns mit der internen Verwaltung von Directories (Dateiverzeichnissen), Pfaden und den zugehörigen GEMDOS-Funktionen. Im zweiten Teil, der in der nächsten Ausgabe erscheint, geht es dann um Suchoperationen in Directories. Diesmal kommen die Freunde der allseits beliebten GEMDOS-Fehler wieder voll auf ihre Kosten.

Allgemeines

Zunächst ein paar allgemeine Bemerkungen zur Struktur der Directories (Dateiverzeichnisse) unter GEMDOS. Jedes Laufwerk verfügt über ein Root Directory (Wurzelverzeichnis), welches mit ‘' angesprochen wird. Das Root Directory kann neben "normalen” Anwenderdateien wie Programmen, Texten usw. weitere (Sub-)Directories (Ordner) enthalten, die eigene Namen haben. Jeder Ordner kann selbst wiederum weitere Ordner enthalten, so daß sich eine hierarchische Struktur ergibt.

Ein übergeordnetes Directory wird “Parent” genannt, ein Subdirectory ist ein “Child”. Diese Bezeichnungen sind jeweils relativ zu einem anderen Directory gemeint, d.h., ein Child kann selbst wiederum Parent eines anderen Directories sein.

Dateien werden durch ihren Zugriffspfad bezeichnet, der sich aus dem Laufwerk und der Abfolge von durchlaufenen Ordnern vom Root Directory bis zum Directory, in dem sich die Datei befindet, ergibt (z.B. ‘A:\ MEGAMAX\HEADERS\STDIO.H'). Dies ist jedem Anwender vom Arbeiten mit dem Desktop her bekannt.

Bei den dem Programmierer zur Verfügung stehenden GEMDOS-Funktionen können außer diesen vollständigen Dateipfaden auch nur bestimmte Teile davon angegeben werden. Beim Fehlen der Laufwerkskennung bezieht sich der Pfad auf das sogenannte “Standardlaufwerk”, welches mit ’Dsetdrv’ festgelegt werden kann (z.B. ‘\MEGAMAX\HEADERS\STDIO.H').

Wenn der erste Backslash (‘\’) fehlt, bezieht sich die Ordnerfolge nicht auf das Root Directory, sondern wird an den “Standardpfad”, der mit ‘Dsetpath’ eingestellt wird, angehängt (relative Pfadangabe). Z.B. meint bei einem Standardpfad ‘\MEGAMAX\’ für Laufwerk A: der Name ‘A:HEADERS\STDIO.H’ die vollständige Bezeichnung ’A:\MEGAMAX\HEADERS\ STDIO.H’. Diese beiden Abkürzungsmöglichkeiten können auch miteinander kombiniert werden, z.B. könnte beim letzten Beispiel auch nur "HEADERS\ STDIO.H’ angegeben werden, wenn A: Standardlaufwerk ist. Diese letzte Möglichkeit ist in den meisten Fällen zu bevorzugen, da hierbei die absolute Position einer Datei im Dateisystem nicht bekannt zu sein braucht.

Bei der kompletten Angabe von Dateinamen, wie in vielen Programmen leider der Fall, besteht die Gefahr, daß eine Datei z.B. immer nur auf Laufwerk A: gefunden wird, obwohl es sich auf C: oder in einem anderen Ordner befindet.

Weiterhin können die speziellen Directories '.' und '..' verwendet werden. '.' bezeichnet das aktuelle Directory selbst und '..' führt eine Directory-Ebene höher. So ist z.B. ‘HEADERS.\ STDIO.H’ identisch mit ‘HEADERS\STDIO.H’ und ‘..\ERRORS.OUT’ bezeichnet '\MEGAMAX\ERRORS.OUT’, wenn der Standardpfad wieder ‘\MEGAMAX\HEADERS' ist.

GEMDOS verwaltet für jedes Laufwerk einen eigenen Standardpfad. Dabei hat sogar jeder Prozeß seine eigenen Standardpfade und Standardlaufwerke. Sie werden an nachgeladene Child-Prozesse "weitervererbt”. Die Pfade des Parent-Prozesses können vom Child-Prozeß nicht verändert werden.

Bei der maximalen Schachtelungstiefe von Ordnern gibt es keine prinzipielle Begrenzung von Seiten des GEMDOS aus. In der Praxis gibt es Einschränkungen bei der maximalen Pfadlänge (s. 'Dgetpath’) und bei der “internen Speicherverwaltung''.

Aufbau der Directories

Subdirectories sind ganz ähnlich wie "normale” Dateien aufgebaut. Ihr Directory-Eintrag in ihrem Parent Directory unterscheidet sich hauptsächlich im Dateiattribut. Bei Directories ist hier Bit 4 gesetzt. Außerdem ist als “Dateilänge” immer Null eingetragen. Die Directory-Datei besteht aus lauter 32 Byte großen Directory-Einträgen. Ihre Struktur ist in Abb. 1 dargestellt.

'dir_name' ist der Dateiname im GEM-DOS-Format. Er ist immer genau 11 Zeichen lang, 8 Zeichen für den Hauptteil des Namens, 3 Zeichen für die Namenserweiterung (Extender). Zu kurze Namensteile werden mit SPACE aufgefüllt (‘DATEI.X’ wird hier als ‘DATEI X ‘ dargestellt). Jeder Dateiname darf pro Directory nur einmal Vorkommen. Es darf also keine Anwenderdatei und einen Ordner gleichen Namens geben. ‘dir_attr’ ist das "Dateiattribut”, das Auskunft über Typ und einige Eigenschaften der Datei gibt. Es wird nächsten Monat noch diskutiert.

Der timestamp (Zeit und Datum der Erstellung der Datei) findet sich in ‘dir_date’ und ‘dir_time’ (zur Repräsentation dieser Daten siehe spätere Folge über die “Timer-Funktionen”).

Die Nummer des ersten Clusters der Datei ist ‘dir_stcl’. Die weiteren Clusternummern werden mit der FAT ermittelt (s. März-Ausgabe). ‘dir_flen’ schließlich ist die Länge der Datei in Bytes.

Timestamp, Startcluster und Dateilänge sind übrigens im Intel-Format gespeichert, d.h. die Reihenfolge von höher- und niederwertigen Bytes ist gegenüber dem Motorola-Format gerade vertauscht. Beim Löschen von Dateien wird im Directory nur das erste Zeichen des Dateinamens auf $E5 gesetzt, der Rest bleibt erhalten. Da die zugehörigen Cluster in der FAT aber freigegeben werden, ist ein Rekonstruieren gelöschter Dateien nicht so ohne weiteres möglich.

typedef struct
{	char	dir_name[11];	/* Dateiname */
	char	dir_attr;	/* Attribut */
	char	dir_dummy [10] ;	/* unbenutzt */
	unsigned int dir_time; /* Erstellungszeit */ 
	unsigned int dir_date; /* Erstellungsdatum */ 
	int dir_stcl;	/* erster Cluster */
	long	dir_flen;	/* Dateilänge */
} DIR;

Abb. 1: Directory-Eintrag

Ein neu angelegtes Directory besteht zuerst aus einem Cluster, der anfangs gelöscht ist (nur Nullbytes enthält). Sobald mehr Dateien erzeugt werden, als hier Platz haben, wird das Directory automatisch um einen Cluster verlängert, der ebenfalls zuerst gelöscht wird.

Eine Ausnahme bildet wiederum das Root Directory. Es hat eine fest vorgegebene Länge, die nicht überschritten werden kann. Das Root Directory wird beim Formatieren komplett mit Nullen gefüllt. Ein Directory wird jedoch nie verkürzt, auch dann nicht, wenn der letzte Cluster überhaupt nicht mehr benutzt wird.

Neue Einträge werden immer an der ersten freien Position gemacht. Die Einträge haben somit keine bestimmte Reihenfolge. Für die sortierte Anzeige in den Fenstern ist der Desktop verantwortlich. Jedes Subdirectory hat noch zwei spezielle Einträge, die die Dateinamen ‘.’ und ‘’ haben. Sie werden bei Anlegen eines Subdirectory automatisch erzeugt und erscheinen im allgemeinen bei der Anzeige von Directories nicht. '.' verweist auf das Directory selbst (‘dir_stcl’ ist der eigene Start-Cluster), führt zum Parent Directory (‘dir_stcl’ ist der Start-Cluster des Parent Directories). Wenn das Root Directory der Parent ist, enthält ‘dir_stcl’ hier Null.

Directory-Dateien

GEMDOS verwaltet Directories ganz ähnlich wie Anwenderdateien. Das bedeutet, jedes Directory hat einen eigenen “File Descriptor” (FD), über den die eigentlichen Datei-Zugriffe wie Lesen, Schreiben und Positionieren laufen. Dabei finden die allgemeinen internen Routinen Verwendung, wie sie in der März-Folge beschrieben wurden. Für die von GEMDOS intern verwalteten Directory-Dateien gibt es keine Datei-Handles und keine “File Control Blocks” (FCB).

Da auf Directories wesentlich häufiger zugegriffen werden muß als auf andere Dateien, wird hier ein erhöhter Verwaltungsaufwand betrieben. Dies hat zur Folge, daß '.' und '..' von GEMDOS überhaupt nicht benutzt werden. Sie existieren nur aus Kompatibilitätsgründen zu PC-DOS.

GEMDOS merkt sich alle Verzeichnisse, auf die irgendwann einmal zugegriffen wurde, um bei weiteren Suchoperationen sofort zu wissen, was mit dem Directory los ist. Dies spart erheblich an Zugriffen auf die übergeordneten Directories. Zu jedem GEMDOS bekannten Directory existiert dazu eine Datenstruktur, die ich “Directory Descriptor” (DD) getauft habe. Die DDs sind in einer Baum-Struktur entsprechend der Directory-Hierarchie angeordnet. Jedesmal, wenn eine der GEMDOS-Dateifunktionen ein Directory anspricht, für das es noch keinen DD gibt, wird dieser automatisch angelegt und in den Baum einsortiert. GEMDOS geht sogar noch weiter, denn das Bekanntmachen neuer Directories geschieht sozusagen im Vorbeigehen. Bei jeder Suchoperation (also auch beim Suchen gewöhnlicher Dateien) werden nämlich alle Subdirectories, deren Einträge untersucht werden, bekannt gemacht. Da sich GEMDOS nicht für V und interessiert, gibt es dafür auch keine DDs.

“Directory Descriptoren” (DD)

DDs sind nun folgendermaßen aufgebaut (s. Abb. 2). Ähnlich wie die FDs beinhalten sie einige Informationen zum Eintrag des Directories in dessen Parent Directory. Dazu gehören der eigene Name im GEMDOS-Format (‘dd_name’), der erste Cluster (‘dd_stcl’) sowie Erstellungszeit und -datum

(‘dd_time’ und ‘dd_date’). Der Startcluster liegt in GEMDOS-Zählung und der timestamp im Intel-Format vor. Für weitere Erläuterungen vergleichen Sie bitte mit den Erklärungen zum FD aus der letzten Folge.

‘dd_dfd’ zeigt auf den FD des Directories und bedarf wohl keiner weiteren Erklärung. NIL zeigt an, daß das Directory zwar schon erkannt, aber noch kein FD benötigt wurde. Der FD wird erst dann eingerichtet, wenn auch tatsächlich ein Zugriff auf das Directory stattfindet. Auch eine Referenz auf das zugehörige Laufwerk muß sein: 'dd_dmd’ verweist auf den "Drive Media Descriptor”, der ja auch schon früher behandelt wurde.

Im letzten Artikel wurde erwähnt, daß die FDs aller geöffneten Dateien eines Directories in einer Liste verknüpft sind.

‘ dd_fdl ’ zeigt auf den Beginn dieser Liste. Dabei bedeutet NIL, daß keine Dateien geöffnet sind.

Doch kommen wir nun zum eigentlich interessanten Teil, der Verkettung der einzelnen DDs untereinander (Abb. 3). Der Rückverweis auf das eigene Parent Directory geschieht mit ‘dd_pardd’. Das Root Directory hat als einziges keinen Parent; daher steht hier NIL. ‘dd_chdd’ bezeichnet ein Child Directory; wenn noch keins bekannt ist, steht hier NIL. Die einzelnen Directories einer Hierarchie-Ebene sind in einer ‘Child-Liste’ mit ‘dd_link’ verbunden, das Ende der Liste wird durch NIL markiert. Die Reihenfolge der Directories in der Liste ist im allgemeinen umgekehrt wie in ihrem Parent. Das ist aber nicht immer so, daher ist diese Reihenfolge als Undefiniert anzusehen. Mit Hilfe dieser drei Zeiger kann sich GEMDOS also relativ schnell innerhalb der Directory Hierarchie bewegen, ohne daß Zugriffe auf den Massenspeicher notwendig sind.

Da Zugriffe auf das Parent Directory ziemlich häufig sind, gibt es noch weitere Verknüpfungen zwischen Parent und Child. In der Abb. 4 sind alle Beziehungen eines Child Directories zu seinem Parent dargestellt. Der FD des Parents kann direkt über ‘dd_parfd’ angesprochen werden, ohne Umweg über den DD des Parents.

Jedes Directory hat ja einen ganz normalen Eintrag in seinem Parent. ‘dd_parpos’ bezeichnet nun die Dateiposition dieses eigenen Eintrags im Parent. Über ‘dd_pardfd' und ‘dd_parpos’ kann also mit Hilfe von Fseek und Fwrite direkt der eigene Eintrag angesprochen und verändert werden. ‘dd_lpos’ zeigt auf die höchste Dateiposition, die bisher bei Suchoperationen in diesem Directory erreicht wurde. Dies ist für das automatische Erweitern des DD-Baumes wichtig (s. Teil 2).

Damit können wir auch noch einmal auf die FDs von Anwenderdateien der letzten Folge zurückkommen. Die Verbindungen mit dem zugehörigen Directory-Descriptor sind in Abb. 5 dargestellt. Es ist praktisch eine vereinfachte Version von Abb. 4.

Hier noch ein Hinweis für diejenigen, die sich mit der internen Speicherverwaltung des GEMDOS auskennen. Die DDs benötigen vier 8-Wort-Einheiten. Freigegebene DDs werden daher ebenso wie die FDs in der ‘mifl’-Liste 4 aufbewahrt.

typedef struct
{	char dd__name [11] ;	/* Directory-Name */
	int dd_stcl;		/* Start-Cluster */
	unsigned int	dd_time;	/* Erstellungs-Zeit */
	unsigned int	dd_date;	/* Erstellungs-Datum */
	FD *dd_dfd;		/* Zeiger	auf	eigenen FD */
	DD *dd_pardd;	/* Zeiger	auf	DD des parent directory */
	DD *dd_chdd;		/* Zeiger auf DD eines child directory */
	DD *dd_link;		/* Zeiger auf DD des nächsten	childs */
	DMD *dd_dmd;		/* Zeiger auf zugehörigen DMD */
	FD *dd_parfd;	/* Zeiger auf FD des parent directory */
	long dd_parpos;	/* Position des eigenen Eintrags im parent */
	long dd_lpos;	/* letzte	Directory-Suchposition */
	FD *dd_fdl;		/* Zeiger	auf	ersten User-Datei-FDi */
} DD;

Abb. 2: Directory Descriptor

Initialisierung des DD-Baumes

Wenn ein Laufwerk zum ersten Mal angeprochen wird, richtet GEMDOS, wie Sie schon wissen, einen “Drive Media Descriptor” (DMD) ein. Dabei wird automatisch der DD für das Root Directory erzeugt. Damit ist ein Minimal-Baum vorhanden, der bei nachfolgenden Operationen erweitert wird.

Der Baum wird dabei im Laufe der Zeit immer nur vergrößert. GEMDOS hat quasi ein perfektes Gedächtnis, was die Existenz von Directories angeht. Eine Ausnahme bildet der Mediumwechsel, bei dem der gesamte DD-Baum aufgelöst wird. Hieraus ergeben sich auch Probleme mit der Harddisk. Daeshieri.allg. sehr viele Ordner und nie Medienwechsel gibt, sammeln sich recht viele DDs an, die leicht zu einer Überlastung der internen Speicherverwaltung führen können.

Pfad-Handles - schon wieder neue Handles

Auf den DDs baut die Verwaltung der Standardpfade auf. Intern wird jeder Standardpfad über ein “Pfad-Handle” angesprochen. Diese Pfad-Handles sind für den Programmierer legal nicht zugänglich. Pfad-Handles liegen zwischen 1 und 39, hinzu kommt noch die Null als Dummy-Handle, das verwendet wird, wenn kein Pfad festgelegt ist.

Der Pfad selbst ist durch einen Zeiger auf den DD des Pfadendes, also des letzten Ordners des Pfades, eindeutig charakterisiert. Die Zuordnung dieser Zeiger zu den Pfad-Handles erfolgt über eine Tabelle C‘pathx[]’), die zu den globalen GEMDOS-Variablen gehört (Tab. 1). ‘pathx[5]’ liefert also z.B. den Pfad für Pfad-Handle 5. Die Gültigkeit eines Elements von ‘pathx[]’ wird durch ‘pthcntx[]’ angezeigt.

Beim Starten von Tochterprozessen werden die Standardpfade mit ihren Handles “vererbt”. Daher kann ein Handle mehrmals vergeben sein. Darüber wird mit 'pthcntx[]’ (Tab. 1) Buch geführt. Für jedes Pfad-Handle steht hier die Anzahl der Prozesse, denen der jeweilige Standardpfad bekannt ist. Null zeigt an, daß das Pfad-Handle unbenutzt ist. Dann ist das zugehörige ‘pathx[]’ Undefiniert (es muß nicht unbedingt NIL sein).

Nun muß GEMDOS noch wissen, welche Standardpfade jeder Prozeß hat. Dazu findet sich im Prozeßdescriptor (PD) das Feld ‘char p_drvx[16]’, in dem für jedes der möglichen 16 Laufwerke das Handle des Standardpfades steht (Null falls noch kein Standardpfad vergeben wurde, d.h. das Laufwerk noch nicht angesprochen wurde).

Abb. 3: Baum der Directory Descriptoren (Beispiel)

Vererbung von Pfaden

Bei ‘Pexec’ werden alle Pfad-Handles aus ‘p_drvx[]’ des Parent-PDs in den Child-PD kopiert, wobei ‘pthcntx[j’ inkrementiert wird. Kurioserweise geschieht dies auch bei Null-Handles, die eigentlich anzeigen, daß kein Standardpfad existiert. Dies führt dazu, daß ‘pthcntx[0]’ unsinnige Werte enthält. Dadurch wird erreicht, daß der Child-Prozeß die Pfade des Parents erhält, ohne daß für jeden Pfad ein neues Handle vergeben werden muß. Erst wenn das Child einen Pfad umsetzt, wird ein neues Handle fällig. Da sich an den Pfad-Handles des Parents nichts ändert, bleiben dessen Pfade von allen Aktionen des Childs unberührt.

Trotzdem sind 39 Pfad-Handles ein wenig knapp kalkuliert. Bei Verwendung wirklich aller 16 Laufwerke und Programme, die Pfade vieler Laufwerke neu setzen (z.B. Command Line Interpreter, Desktop) kann die Zahl gleichzeitig im Speicher haltbarer Prozesse schon auf 2-3 beschränkt sein.

Beim Terminieren eines Prozesses mit einer der ‘Pterm’-Funktionen wird 'pthcntx[]' bei allen gültigen Handles dekrementiert. Hier werden Null-Handles extra abgefangen. Es wird jedoch nicht berücksichtigt, ob ‘pthcntx[]’ auch wirklich echt größer als Null ist. Dies dürfte eigentlich auch nicht Vorkommen, aber auf Grund zweier Fehler (siehe weiter unten) kann dieser Fall doch eintreten, und schon hat man negative Werte.

Mit Abb. 6 ist ein Beispiel für die Pfadverwaltung mit zwei Prozessen gegeben. Prozeß 1 ist der Parent und hat Pfade für die Laufwerke A: und B: definiert. Prozeß 2 ist das Child und hat zunächst beide Pfade geerbt. Dann hat er einen Pfad für C: festgelegt und den Pfad für B: umdefiniert. Vergleichen Sie auch mit "Dset-path’.

So weit, so gut. Probleme tauchen aber auf, wenn man Programme mit ‘Pexec’ einmal laden, resident im Speicher halten und mehrmals starten möchte. Denn das Vererben der Pfade geschieht bei allen Pexec’-Modi außer 4 (“nur starten”). Die Übertragung der Pfade geschieht also nur ?eim ersten Start eines Programms und das auch noch zum Zeitpunkt des Ladens. Deshalb wäre es sinnvoller, dies mit den Modi 0 und 4 (“laden und starten” und “nur starten”) zu verknüpfen.

Abb. 4: Child und Parent Directory

Ähnliches gilt übrigens auch für die I/O-Umleitung. Beim ‘Pterm’ wäre es besser, wenn der PD wieder in den Zustand vor dem ‘Pexec’ gebracht, d.h. ‘p_drvx[]’ gelöscht würde. Während letzteres noch “von Hand” gemacht werden könnte, ist ersteres Problem nicht lösbar - halbwegs saubere Programmierung vorausgesetzt. Hier sei an die Atari-Programmierer appelliert, in Zukunft für Besserung zu sorgen.

Anmeldung von Laufwerken

Nach der Initialisierung des GEMDOS beim Systemstart sind keinerlei Pfad-Handles belegt (alle ‘p_drvx[]’ sind gelöscht), d.h. es gibt für GEMDOS noch keine Standardpfade. Für den Anwender gilt jedoch das Root Directory als Standardpfad, solange kein anderer mit ‘Dsetpath’ ausgewählt wird. Daher sorgt GEMDOS dafür, daß bei einem Zugriff auf ein Laufwerk, das noch keinen Standardpfad hat, automatisch das Root Directory als Standardpfad gesetzt wird. Dies geschieht in der Routine ‘d_chkdrv’, die bei allen Laufwerks- und Pfadoperationen aufgerufen wird, bevor mit Pfad-Handies usw. gearbeitet wird.

‘d_chkdrv’ wurde schon in der April-Ausgabe unter “Anmeldung von Laufwerken” beschrieben, jedoch folgt hier noch einmal das wesentliche, um den Zusammenhang zur Pfadverwaltung besser herauszustellen.

Abb. 5: Anwenderdatei und Directory

GEMDOS merkt sich, welche Laufwerke ihm bekannt sind, d.h. schon einmal angesprochen wurden. Bei bisher noch nicht bekanntem Laufwerk wird überprüft, ob es überhaupt ansprechbar ist (mit ‘Getbpb’), wobei alle elementaren Strukturen für das Laufwerk (DMD, FD für FAT und Root Directory, DD des Root Directory) initialisiert werden. Existiert das Laufwerk nicht, bricht ‘d_chkdrv’ hier ab. Ansonsten ist das Laufwerk auf jeden Fall bekannt.

Ein Null-Pfad-Handle in 'p_drvx[]' zeigt an, daß noch kein Standardpfad festgelegt wurde. In diesem Fall wird ein freies Pfad-Handle ermittelt, der DD des Root Directory, der zu diesem Zeitpunkt auf jeden Fall vorhanden ist, in ‘pathx[]' eingetragen und ‘pthcntx[]’ auf Eins gesetzt. Damit ist das Root Directory als Standardpfad definiert. Das gleiche passiert auch, wenn das Pfad-Handle in ‘p_drvx[]’ zwar gültig ist, aber ‘pathx[] ein NIL-Zeiger ist. Dieser Fall soll der besonderen Behandlung von Medienwechseln dienen.

DD *pathx[40];	/* $564C/	$564c/	$7dee:	Standardpfade */
char pthcntx[40]; /* $58c4/ $58c4/ $8066:	Zahl der Prozesse */
long inv_pos	/* $17b06/$fdlcl6/$fd3042:	Konstante */
	=-1L;
char dirown[]	/* $17b0a/$fdlcla/$fd3046:	Directory-Eintrag */
	= [	'.',0x20,0x20,0x20,0x20,0x20,
		0x20,0x20,0x20,0x20,0x20,
		0,0,0,0,'0,0,0,0,0,0,0]; 
char dirpar[]	/* $17b20/$fdlc30/$fd305c:	Directory-Eintrag */
	= [	'.','.',0x20,0x20,0x20,0x20,
		0x20,0x20,0x20,0x20,0x20 ,
		0,0,0,0,0,0,0,0,0,0,0];

Tabelle 1: Globale GEMDOS-Variablen zur Directory-Verwaltung

Diskettenwechsel - lieber nicht

Wie innerhalb dieser Serie schon des öfteren erwähnt, müssen bei Medienwechseln, die beim ST i.allg. auf Disketten beschränkt sind, alle das Laufwerk betreffenden internen Datenstrukturen freigegeben und gegebenenfalls neu initialisiert werden. Dies gilt selbstverständlich auch für die Pfadverwaltung. Deshalb gibt der “Media change”-Handler sämtliche DDs und FDs frei. Außerdem werden alle Pfade des betroffenen Laufwerks ungültig gemacht, indem ‘pthcntx[]’ auf Null gesetzt wird. Außerdem wird ‘pathx[]’ auf NIL gesetzt, um die oben erwähnte Sonderbehandlung durch ‘d_chkdrv' zu erzwingen, die aber nicht die rechte Wirkung zeigt. Das Problem besteht darin, daß die in den PDs gespeicherten Pfad-Handles von nun an ungültig gemachte Pfade bezeichnen.

Im Falle, daß der Pfad vorher nur vom eigenen Prozeß benutzt wurde, wird das Pfad-Handle bei der nächsten Gelegenheit neu vergeben, womöglich für den Standardpfad eines anderen Laufwerks. Dies hat dann zur Folge, daß dieser Pfad auch für das andere Laufwerk gilt. Und schon herrscht Chaos und der Programmierer wundert sich.

Abgesehen davon ist natürlich die Zählung mit ‘pthcntx[]- durcheinandergeraten, was weitere “Spätfolgen” (z.B. negative Werte in ‘pthcntx [] ’ nach der Prozeß-Terminierung!) nach sich ziehen kann. Der Fehler ließe sich beseitigen, indem nur ‘pathx[]’ gelöscht würde, ‘pthcntxf]’ aber unverändert bliebe. Die Handles blieben dann belegt und die ‘p_drvx[] -Felder würden weiterhin gültige Handles enthalten.

In den meisten Fällen wird das Laufwerk mit einem neuen Medium ansprechbar sein, so daß dann die Pfad-Handles wieder gültig werden sollten. Die Routine ‘d_chkdrv\ die vor jeder Benutzung der Pfade zum Einsatz kommt, fragt diesen Fall (Laufwerk bekannt, handle gültig, aber ‘pathx[]‘ NIL) sogar schon extra ab, reagiert aber falsch darauf. Bisher vergibt sie einfach ein neues Handle, dem das Root Directory als Pfad zugeordnet wird, und setzt es auch noch in ‘p_drvx[]\ Dies würde dann funktionieren, wenn dabei ‘ pthcntx []’ des alten Handles dekrementiert würde, da es nun einem Prozeß weniger bekannt ist. Dies fehlt vermutlich deshalb, weil auf Grund des ersten Fehlers ‘pthcntx[]’ schon vom “Media change”-Handler gelöscht wird.

Günstiger wäre es noch, gar kein neues Handle zu vergeben, sondern einfach ‘pathx[]' auf das Root Directory zu setzen. Damit wäre nach einem Mediumwechsel der alte Pfad einfach gegen das Root Directory ausgetauscht worden.

Directory-Suchoperationen

Alle GEMDOS-Funktionen, die einen beliebigen GEMDOS-Dateinamen (möglicher Pfad + Name) als Parameter haben, rufen eine wichtige Routine auf, die aus dem Dateipfad den DD des zugehörigen Directories und den übrig bleibenden eigentlichen Dateinamen ermittelt. Diese Routine heißt bei mir ‘d_getdir’.

Eine weitere wichtige Funktion ist ‘ d_srcname ’, die in einem schon ermittelten Directory nach bestimmten Dateien sucht. Beide Routinen werden in Teil 2 ausführlich erläutert. Entscheidend ist hier nur, daß bei beiden Funktionen alle Directories, die "auf dem Weg” liegen, automatisch in den DD-Baum eingefügt werden. Wenn übergeordnete Routinen mit Directories und Pfaden arbeiten, können sie sich darauf verlassen, daß alle benötigten Strukturen hiernach definiert sind.

Globale GEMDOS-Variablen

Eine Zusammenstellung findet sich in Tab. 1. Die Adressen sind wie üblich TOS-abhängig und beziehen sich auf das RAM-TOS vom 6.2.1986, das ROMTOS vom 6.2.1986 und das ROM-TOS vom 22.4.1987 (von links nach rechts), ‘dirown’ und ‘dirpar’ werden bei ‘Dcreate’ erläutert.

GEMDOS-Funktionen (TRAP #1)

Funktion $47 Dgetpath

long Dgetpath(char *buf, int drive)

Der aktuelle Standardpfad vom Laufwerk ‘drive’ (0 für Standard-Laufwerk, 1 für A:, 2 für B: usw.) wird ermittelt und in ‘buf’ abgelegt. Der String beinhaltet nicht die Laufwerkskennung, beginnt und endet nicht mit ‘' und ist nullterminiert. Der Standardpfad ‘A:\ORDNERl\ORDNER2' wird also als ‘ORDNER1\ORDNER2' geliefert.

Es gibt keine Größenbeschränkung von Seiten des GEMDOS; ‘buf muß groß genug bereitgestellt werden. Eine Größe von 128 Zeichen dürfte im allgemeinen ausreichen.

Rückgabewerte:

-46L (EDRIVE) Laufwerk existiert nicht, interner Fehler (kein interner Speicher, keine Pfad-Handles mehr)
0L alles ok

Arbeitsweise

Zuerst wird eine e ventuelle Angabe des Standardlaufwerks ausgewertet, d.h.. das gewünschte Laufwerk wird ermittelt. Wie üblich erfolgt der Aufruf von ‘d_chkdrv' Beginnend beim DD des Directories am Pfadende (erreicht durch ‘p_drvx[]’ und ‘pathx[]' wird sich mittels der ‘dd_pardd’-Zeiger rekursiv bis zum Root Directory hochgearbeitet. Beim Rücklauf der Rekursion werden die Directory-Namen (‘dd_name’) zum Pfad zusammengesetzt.

Stack-Überlauf-Probleme dürfte es hier nicht geben, da der für die Ausführung von GEMDOS-Funktionen eigens bereitgestellte Stack 3 kB groß ist. Dies ist eine richtig hübsche Routine; bei aller Kritik muß man die GEM-DOS-Programmierer auch einmal loben.

Funktion $3B Dsetpath

long Dsetpath(char *path)

Der Standardpfad für das in ‘path' mit angegebene Laufwerk wird neu gesetzt. Er gilt nur für den eigenen Prozeß und wird von diesem an Tochterprozesse vererbt, ‘path’ endet nicht mit ‘V. Da Accessories immer unter dem PD der Haupt-Applikation laufen, können sich hier Konflikte ergeben. Wenn Accessories Pfade umsetzen. findet die Applikation eventuell ihre Dateien nicht mehr. Daher sollten Accessories ‘Dsetpath’ (und auch ‘Dsetdrv’) nicht benutzen, sondern jeweils komplette Pfade angeben.

Rückgabewerte:

-1..-31 BIOS-Fehlermeldung bei Diskzugriff
-1L (ERROR) Laufwerk existiert nicht, interner Fehler (keine Pfad-Handles)
-34L (EPTHNF) Pfad nicht gefunden, interner Fehler (keine Pfad-Handles, zu wenig interner Speicher)
-39L (ENSMEM) interner Fehler (zu wenig interner Speicher)
OL alles ok.

Arbeitsweise

Zuerst wird das Laufwerk, bei dem der Standardpfad geändert werden soll, ermittelt. Wenn kein Laufwerk explizit im Pfad angegeben ist, wird das aktuelle genommen.

Auch hier wird zunächst ‘d_chkdrv’ aufgerufen. Anschließend wird der alte Pfad ungültig gemacht, indem ‘pthcntx[]’ um eins erniedrigt wird. Wenn der Pfad von mehreren Prozessen benutzt wird, bleibt das Pfad-Handle also noch belegt.

Nun wird ein freies Pfad-Handle gesucht (erkannt an einer Null in ‘pthcntx[]’), woraufhin mit ‘d_getdir’ der DD des gewünschten Directories (also des neuen Pfadendes) ermittelt wird.

Zuletzt wird ein Zeiger auf diesen DD in ‘pathx[]’ mit dem vorher gefundenen Pfad-Handle gespeichert, das zugehörige ‘pthcntx[]’ inkrementiert (hier also auf Eins gesetzt) und das Pfad-Handle in ‘p_drvx[]’ gemerkt.

Hier hat der Fehlerteufel mal wieder zugeschlagen:

Die Angabe des Laufwerks im Pfadnamen wird so gut wie ignoriert. Der Pfad bezieht sich immer auf das Standardlaufwerk. Beim zuerst ausgeführten ‘d_chkdrv’ wird noch das korrekte Laufwerk benutzt. ‘d_getdir’ wird aber ein Pfad ohne Kennung übergeben, so daß es auf dem Standardlaufwerk operiert. Dagegen verblaßt der andere Fehler geradezu.

Abb. 6: Verwaltung der Standardpfade (Beispiel)

Wenn kein Pfad-Handle mehr verfügbar ist (tritt praktisch nicht auf) oder der Pfad nicht gefunden wurde (durchaus möglich!), wird mit EPTHNF abgebrochen. Dabei ist aber der alte Pfad schon freigegeben (‘pthcntx[]’ dekrementiert), und die Referenz auf ihn über das Handle aus ‘p_drvx[]’ ist noch gültig. Dieser Fehler wirkt sich ganz ähnlich wie der unter “Diskettenwechsel” beschriebene aus.

Funktion $39 Dcreate

long Dcreate(char *path)

Ein neues Directory ‘path’ wird geschaffen. Eine Datei oder ein Directory gleichen Namens darf nicht schon existieren.

Rückgabewerte:

-1..-31 BIOS-Fehlermeldung bei Diskzugriff
-34L (EPTHNF) Pfad nicht gefunden, Laufwerk existiert nicht, illegaler Dateiname (‘.’ oder ‘..’), interner Fehler (keine Pfad-Handles, zu wenig interner Speicher)
-35L (ENHNDL) keine Datei-Handies mehr (intern wird ein Datei-Handle benötigt)
-36L (EACCDN) Diskette voll, Root Directory voll, Name existiert schon
-39L (ENSMEM) interner Fehler (zu wenig interner Speicher)
0L alles ok.

Arbeitsweise

‘Dcreate’ ist im Prinzip ein erweitertes ‘ Fcreate'. Daher wird zuerst das interne ‘Fcreate’ mit dem Dateiattribut $10 für Subdirectories aufgerufen. Zur Erinnerung sei hier noch einmal ein Teil der Beschreibung von ‘Fcreate’ wiederholt (s. letzte Folge).

Falls noch nicht vorhanden, wird für das Parent Directory des neu zu erzeugenden Directories ein FD angelegt. Die meisten Daten des FD korrespondieren mit entsprechenden des DD und werden direkt übernommen. Die Dateilänge (‘fd_len’) wird auf $7FFFFFFF gesetzt, da die Länge von Subdirectories nur durch die Kapazität des Mediums begrenzt ist. ‘fd_link’ und ‘fd_mode’ werden nicht benutzt.

Falls das Subdirectory schon existiert, wird abgebrochen. Nun wird im Parent Directory ein freier Eintrag gesucht und mit Dateiname, Attribut und timestamp initialisiert. Die Dateilänge und der Start-Cluster werden auf Null (letzteres zum Zeichen dafür, daß noch kein Cluster vergeben wurde) gesetzt. Außerdem wird dafür gesorgt, daß alle GEMDOS-Sektorpuffer zurückgeschrieben werden.

Jetzt wird die Datei mit einer der internen Open-Routinen, die auch das Handle generiert, richtig eröffnet (s. ‘Fopen’, letzte Folge). Hier werden Subdirectories wie jede andere Datei auch behandelt, d.h. das Subdirectory bekommt einen ganz normalen Eintrag im Parent Directory.

Nach dem ‘Fcreate' wird für das neue Subdirectory ein DD eingerichtet und in die Child-Liste des Parent Directories eingehängt. Außerdem wird auch gleich ein FD für das Subdirectory angelegt. Wenn für eine dieser Operationen nicht genügend interner Speicher vorhanden ist, wird zwar mit ENSMEM abgebrochen, doch die Subdirectory-Datei bleibt bestehen und geöffnet, insbesondere bleibt das Handle belegt und der Eintrag im Parent Directory bleibt erhalten.

Als nächstes ermittelt die FAT-Verwaltung einen freien Cluster und macht ihn zum ersten Cluster des Subdirectories. Bei vollem Speichennedium wird die Directory-Datei korrekt geschlossen, das Subdirectory im Parent Directory gelöscht und es erfolgt eine Fehlermeldung (EACCDN).

Nun werden die ersten zwei Einträge im Subdirectory (‘.’ und generiert. Die Roheinträge sind in den globalen GEM-DOS-Variablen zu finden (Tab. 1). Einziger Fehler hierbei: Der timestamp in diesen Einträgen wird nicht ins Intel-Format konvertiert. Wenn das Parent Directory das Root Directory ist, wird als Start-Cluster von Null eingesetzt (an Stelle der GEMDOS-eigenen negativen Cluster-Nummer).

Zuletzt wird die Subdirectory-Datei korrekt geschlossen, wobei alle Sektorpuffer zurückgeschrieben werden.

Funktion $3A Ddelete

long Ddelete(char *path)

Das Subdirectory 'path' wird gelöscht. Es muß dabei leer sein (bis auf die '.' Einträge und ‘..’).

Rückgabewerte:

-l..-31BIOS-Fehlermeldung bei Diskzugriff
-34L (EPTHNF) Pfad nicht gefunden, Laufwerk existiert nicht, interner Fehler (keine Pfad-Handles, zu wenig interner Speicher)
-36L (EACCDN) Directory nicht leer
-39L (ENSMEM) interner Fehler (zu wenig interner Speicher)
-65L (EINTRN) interner Fehler (s. “Arbeitsweise”)
0L alles ok

Arbeitsweise

Zuerst wird mit ‘d_getdir’ der DD des zu löschenden Subdirectories ermittelt und gegebenenfalls ein FD angelegt. Nun wird überprüft, ob das Subdirectory auch tatsächlich leer ist. Ab dem dritten Eintrag des Subdirectories werden alle Einträge, die mit $E5 beginnen (gelöschte Datei), überlesen. Wenn danach das Directory-Ende, erkannt an einem mit $00 beginnenden Eintrag oder am Ende der Directory-Datei, erreicht wird, ist das Subdirectory leer. Als nächstes wird das Subdirectory in der Child-Liste seines Parents gesucht. Dies muß eigentlich immer klappen. Es gibt aber eine Sicherheitsabfrage, die EINTRN zurückgeben soll, wenn der DD des Subdirectory nicht auffindbar ist. Leider ist diese Abfrage falsch programmiert, so daß es stattdessen einen Bus Error (2 Bomben) gibt.

Noch zwei weitere Fälle, die eigentlich nicht auftreten dürfen, werden abgefangen und als EINTRN gemeldet: Es gibt noch offene Dateien im leeren Subdirectory (‘dd_fdl’ ungleich NIL), oder das Subdirectory hat selbst noch Subdirectories (‘dd_chdd’ ungleich NIL). Jetzt wird der DD aus dem DD-Baum ausgehängt, DD und FD werden der internen Speicherverwaltung zurückgegeben und das Subdirectory wird mit einer internen Version von ‘Fdelete’ wie eine normale Datei gelöscht. Haben Sie schon einmal versucht, das Root Directory zu löschen? Eigentlich überflüssig zu erwähnen, daß dies nicht abgefangen wird. ‘Ddelete’ bedankt sich beim Zugriff auf den Parent-DD mit zwei netten Bömbchen.

Funktion $56 Frename

**long Frename(int dummy, char oldpth, char newpth)

Die Datei ‘oldpth’ wird in ‘newpth’ umbenannt, ‘newpth’ muß das gleiche Laufwerk bezeichnen, darf jedoch ein anderes Directory spezifizieren. Die Datei kann also nicht zwischen zwei Laufwerken kopiert, aber innerhalb eines Laufwerks verschoben werden. Dabei werden nur Directory-Verweise geändert, die Datensektoren der Datei bleiben an ihrem Platz, sodaß ‘Frename ’ wesentlich schneller als Kopieren und anschließendes Löschen der Datei ist. Darum ist es besonders schade, daß man dies vom Desktop aus nicht ausnutzen kann.

‘dummy’ ist unbenutzt und sollte Null sein, um Kompatibilität zu späteren GEMDOS-Versionen zu wahren.

Rückgabewerte:

-1..-31 BIOS-Fehlermeldung bei Diskzugriff
-33L (EFILNF) Datei nicht gefunden, interner Fehler (zu wenig interner Speicher), illegaler Quelldateiname, Datei ist Subdirectory
-34L (EPTHNF) Quell- oder Zielpfad nicht gefunden, Laufwerk existiert nicht, interner Fehler (keine Pfad-Handles, zu wenig interner Speicher)
-35L (ENHNDL) kein Datei-Handle für Datei im Quellpfad
-36L (EACCDN) Zieldatei existiert schon, Datei ist ‘read only’
-39L (ENSMEM) interner Fehler (zu wenig interner Speicher)
-48L (ENSAME) unterschiedliche Laufwerke in Quell- und Zielpfad
-65L (EINTRN) interner Fehler (s. “Arbeitsweise”)
OL alles ok.

Arbeitsweise

Zuerst wird mit ‘Fsfirst’/’Fsnext’ (s. Teil 2) überprüft, ob es eine Datei gleichen Namens wie ‘newpth’ schon gibt. Dann werden mit ‘d__getdir’ die DDs von Quell- und Zielpfad bestimmt. Daraufhin werden Quell- und Ziellaufwerk miteinander verglichen, um evtl, mit ENSAME abzubrechen. Die Datei wird mit ‘Fopen’ und Modus 2 (“lesen und schreiben”) eröffnet, daher erfolgt hier ein Abbruch bei ‘read only'-Dateien. Wenn Quell- und Zieldirectory gleich sind, wird direkt mit internem ‘Fwrite’ der alte Dateiname im Directory durch den neuen ersetzt.

Bei verschiedenen Directories wird zuerst die Datei im Quelldi-rectory gelöscht, indem $E5 ins erste Zeichen des Dateinamens geschrieben wird. Dann werden Dateiattribut, Start-Cluster, Dateilänge und timestamp gemerkt. Jetzt erst wird mit ‘Fcreate’ und dem alten Dateiattribut ein neuer Dateieintrag im Zieldirectory geschaffen. Die gemerkten Daten werden ins Zieldirectory übertragen und Zieldatei und Zieldirectory geschlossen, so daß alle Änderungen hier auf das Speichermedium übertragen werden.

In beiden Fällen wird zum Schluß die Quelldatei geschlossen. Nur wenn hierbei kein Fehler auftritt, wird die Quell-directory-Datei ebenfalls geschlossen. Beim Umbenennen zwischen Ordnern ist vor allem die Reihenfolge der Aktionen zu beachten. Wenn beim Anlegen des neuen Eintrags etwas schiefgeht (z.B. BIOS-Fehler beim Zugriff auf Diskette), ist die Datei im Quelldirectory schon gelöscht!

Schlimmer noch, daß beim ‘Fcreate’ für die Zieldatei Fehler nicht erkannt werden, was zu Bombenhagel führen muß. Und zu diesem Zeitpunkt ist die Datei ebenfalls schon gelöscht... Mögliche Fehler sind z.B. “Diskette voll", “Root Directory voll”, “keine Datei-Handles mehr”. Auch die Abfrage zu Beginn, ob der Zielname schon existiert, greift nicht immer. ‘Fsfirst’ wird nämlich mit dem Suchattribut Null aufgerufen. Deshalb werden “versteckte Dateien” (‘hidden-Bit' oder ‘system-Bit’ im Dateiattribut gesetzt) nicht bemerkt. Das spätere ‘Fcreate’ löscht nun rücksichtslos die versteckte Datei, um den neuen Eintrag für die umzubenennende Datei zu schaffen.

Nächsten Monat geht es dann weiter mit Teil 2 der Directory-Verwaltung.


Alex Esser
Aus: ST-Computer 07 / 1988, Seite 116

Links

Copyright-Bestimmungen: siehe Über diese Seite