In der letzten Ausgabe wurden alle mit alternativen File-Systemen im Zusammenhang stehenden neuen GEMDOS-Funktionen ausführlich vorgestellt. Im zweiten Teil des Artikels kommt nun die praktische Anwendung zur Sprache. Dabei wird auch auf einige Besonderheiten eingegangen, wie beispielsweise das Laufwerk U und die Environment-Variable UNIXMODE.
Zur Erinnerung: Man sollte keinesfalls durch Prüfung irgendwelcher Cookies Rückschlüsse auf die Verfügbarkeit der neuen GEMDOS-Funktionen ziehen, denn alle können bzw. müssen durch einen Probeaufruf auf Vorhandensein geprüft werden. Erhält man den Rückgabewert EINVFN (-32L, siehe auch Tabelle 2 in der letzten Ausgabe), ist die Funktion nicht verfügbar.
Ein Programm, das alternative File-Systeme richtig ausnutzen möchte, sollte immer in der MiNT-Domain laufen, sobald dies durch Pdomain erreicht werden kann. Danach ist zu prüfen, ob die Aufrufe Dopendir, Dreaddir und Dclosedir vorhanden sind (dazu reicht es aus, auf Dopendir zu testen, Dreaddir und Dclosedir müssen dann auch vorhanden sein, sonst macht es ja keinen Sinn). Wenn nicht, ist eine vernünftige Unterstützung unmöglich, und man sollte von Standard-TOS-Dateinamen ausgehen (also 8+3 in Großbuchstaben) und Fsfirst/Fsnext zum Directory-Einlesen benutzen. Fehlt Dpathconft ist das zwar kein Beinbruch, allerdings hat man dann eben keine Möglichkeiten, nähere Informationen über das jeweilige Dateisystem zu erfragen, insbesondere nicht die maximale Länge eines Dateinamens.
Fehlt Fxattr, kann man einige der erweiterten Attribute nicht ermitteln und muß für die Standardattribute (Erzeugungszeit, Fileattribute) auf Fdatime und Fattrib zurückgreifen.
Hat man also festgestellt, daß das GEMDOS die genannten Funktionen besitzt, darf man Fsfirst/Fsnext zum Einlesen von Verzeichnissen getrost vergessen, da die von ihnen benutzte DTA-Struktur keinen Platz für längere Dateinamen und zusätzliche Informationen bietet, wie sie Unix-kompatible File-Systeme liefern. Statt dessen sollte man versuchen, mittels Dpathconf die maximale Länge eines File-Namens in dem gewünschten Verzeichnis zu ermitteln (wie im ersten Teil beschrieben: „." an den Pfad anhängen). Ist dies unmöglich, weil Dpathconf nicht vorhanden ist, sollte man Default-Werte nehmen, als Richtwerte können 64 Zeichen für den Dateinamen und 512 für einen Pfad genommen werden. Auf jeden Fall muß man in diesem Fall peinlich genau darauf achten, ob bei Dreaddir bzw. Dgetcwd nicht ERANGE geliefert wird!
Der nächste Schritt ist das Öffnen des Verzeichnisses mittels Dopendir (im „normalen" Modus!), dabei darauf achten, daß das gelieferte Handle im obersten Byte einen Wert ungleich 0xff (255) enthält, sonst ist ein Fehler aufgetreten. Niemals nur auf < 0 abfragen, um einen Fehler zu erkennen!
Wurde das Verzeichnis erfolgreich geöffnet, kann man nun mittels Dreaddir den ersten File-Namen auslesen. Möchte man außer dem Namen noch weitere Informationen wie Dateityp etc., muß man sie mittels Fxattr (oder, wenn das nicht geht, mit Fdatime/Fattrib) ermitteln. Mit weiteren Aufrufen von Dreaddirerhält man dann die Namen der restlichen Directory-Einträge, bis man ENMFIL als Rückgabewert erhält. Im Anschluß muß unbedingt der passende Dclosedir-Aufruf erfolgen, damit der von Dopendir benötigte Speicher wieder freigegeben wird.
Noch bequemer hat man es natürlich, wenn man zum Einlesen auf Dx-readdir zurückgreifen kann, denn in diesem Fall kann man Name und (erweiterte) Attribute mit einem einzigen Aufruf ermitteln, was auch die Geschwindigkeit erhöht. Dabei ist es wichtig zu beachten, daß man, anders als bei Dreaddir, aus der Verfügbarkeit von Dopendir nicht auf das Vorhandensein von Dxreaddir sch ließen kann; es ist eine explizite Prüfung nötig!
Wer neben den Verzeichniseinträgen auch eventuelle Labels berücksichtigen will, muß Dreadlabei benutzen, da Dreaddir nur Dateien (alle Typen) und Verzeichnisse berücksichtigt. Ist Dreadlabel nicht vorhanden, muß man notgedrungen auf Fsfirst ausweichen, was man aber nur tun sollte, wenn das File-System Standard-TOS-Filenamen benutzt (sprich: wenn Dpathconf bei Modus 5 DP_DOSTRUNC liefert).
Eine Besonderheit muß noch erwähnt werden: Viele Unix-Freaks haben sich mit Hilfe des Laufwerks U eine Unix-konforme Pfadstruktur geschaffen, bei der dann im Prinzip keine Laufwerksangabe mehr nötig ist, da man alles als Unterverzeichnisse von U erreichen kann (Unix kennt keine Laufwerksangaben wie GEMDOS). Die MiNT-Libs beachten daher die Environment-Variable UNIXMODE, in der u.a. festgelegt werden kann, welches Laufwerk als Wurzellaufwerk benutzt werden soll. In diesem Fall werden dann Pfadangaben mit führendem (Back)slash auf dieses Laufwerk gelenkt, aus \etc wird also beispielsweise U:\etc (wohlgemerkt, nur library-intern).
Das wäre alles noch völlig unwichtig, gäbe es da nicht die symbolischen Links. Dies sind Pseudodateien, die nur einen Pfad beinhalten, der dann bei einem Zugriff auf sie tatsächlich angesprochen wird (dies kann sowohl ein Datei- als auch ein Verzeichnispfad sein). „Unixer“ setzen als Zielpfad für symbolische Links aus Gewohnheit Unix-Pfade ein, also ohne Laufwerksangabe. Ist jetzt das aktuelle Laufwerk beim Lesen dieses Links nicht U bzw. das bei UNIXMODE angegebene, erfolgt der Zugriff auf ein falsches Verzeichnis bzw. File, da das Betriebssystem ja wegen der fehlenden Laufwerksangabe auf dem aktuellen Drive sucht. Daher sollte man, wenn die Environment-Variable UNIXMODE existiert und den Pattern rX enthält (er darf an beliebiger Stelle stehen, X steht dabei für A bis Z, dürfte aber fast immer U sein), das dort angegebene Laufwerk zum aktuellen machen und sämtliche Zugriffe nur darüber abwickeln (selbstverständlich nur, wenn dieses Laufwerk auch existiert).
U bildet unter MiNT und MagiC 3 übrigens eine Art „übergeordnetes Laufwerk“, von dem aus alle anderen dem GEMDOS bekannten Laufwerke über Unterverzeichnisse angesprochen werden können. C ist also auch als U:\c erreichbar. Darüber hinaus finden sich hier einige Pseudoverzeichnisse wie u:\proc oder u:\dev, in denen spezielle Files abgelegt sind, die besondere Funktionen erfüllen. u:\dev beispielsweise enthält Devices, also Ein-/Ausgabegeräte. Mit Hilfe von u:\dev\prn kann man so eine Datei ausdrucken, indem man sie unter diesem Namen speichert. Das alternative Desktop „Thing“ erkennt übrigens solche Device-Dateien und läßt auf sie ein Drag & Drop zu; allerdings sollte man das mit Vorsicht genießen, denn nicht jedes Device ist für die Ausgabe von Dateien gedacht bzw. geeignet.
Als letzte Möglichkeit ermöglicht Laufwerk U das „mounten“ von File-Systemen und das Anlegen von symbolischen Links (das ist allerdings keine garantierte Eigenschaft, wenn es existiert). Auf diese Weise lassen sich alternative File-Systeme oder neue Laufwerke auch später ins System einbinden (mounten) oder häufig benutzte Dateien/Verzeichnisse, die in Wirklichkeit irgendwo in den Untiefen der Festplatte(n) schlummern, direkt über das Wurzelverzeichnis von Laufwerk U ansprechen (symbolische Links).
Die oben erwähnten MiNTLibs machen übrigens prinzipiell einen optimalen Gebrauch von Dopendir & Co., benutzen aber leider noch MiNT-Abfragen, unter MagiC 3 arbeiten damit gelinkte Programme daher wie unter SingleTOS mit Fsfirst und Fsnext. Glücklicherweise sind jedoch schon Patches erhältlich, die dieses Problem lösen und die Benutzung der erweiterten Verzeichnisbefehle nur von ihrem tatsächlichen Vorhandensein abhängig machen. Es wird auch weiter an den MiNTLibs gearbeitet, um sie so weit wie möglich von MiNT- und MiNT-Versionsabfragen zu befreien. Allerdings sollte man dann vielleicht den Namen nochmal überdenken ...
Damit nicht alles in diesem Artikel nur graue Theorie bleibt, habe ich ein kleines Demonstrationsprogramm geschrieben, dessen Quellcode in Listing 1 zu finden ist. Es handelt sich dabei um eine Art "ls-Befehl für Arme“, das einfach alle Dateien eines Verzeichnisses ohne Attribute ausgibt. Der Aufruf erfolgt mittels Kommandozeile, in der das anzuzeigende Directory angegeben ist. Im Gegensatz zum „echten“ ls-Befehl werden keine Optionen unterstützt, es soll ja auch nur ein Beispiel sein. Im Listing werden alle oben beschriebenen Vorgehensweisen realisiert (soweit nötig), auch UNIXMODE wird berücksichtigt. Selbstverständlich funktioniert es auch, wenn bestimmte Funktionen nicht vorhanden sind, im Extremfall wird mit Fsfirst/Fsnext gearbeitet.
Ich hoffe, ich konnte mit dem Artikel und dem Listing ein wenig Licht ins Dunkel der richtigen Berücksichtigung alternativer File-Systeme bringen. Selbstverständlich konnten hier nicht alle Möglichkeiten beleuchtet werden, insbesondere so interessante Dinge wie (symbolische) Links, Pipes und Dateizugriffsrechte fanden keine gebührende Beachtung. Wenn genügend Interesse besteht, werde ich jedoch einen weiteren Artikel schreiben, der sich damit auseinandersetzt. Wer also entsprechende Informationen haben möchte, sollte dies der Redaktion und mir mitteilen.
Wer weitere Fragen zum Thema dieses Artikels hat, kann sich direkt an mich wenden. Meine Adresse:
Johann-Valentin-May-Straße 7 64665 Alsbach-Hähnlein
Literatur:
[1] Eric Smith, MiNT Programmer's Manual, Version 1.0 -1.11,1993 -1994, zu finden z.B. im Archiv mintl!2d.zoo (leider unvollständig und nicht auf dem neuesten Stand)
[2] Marcus Haebler, Länge von Dateinamen, Quicktip in ST-Computer 3/94
/* dirread.c vom 10.08.1995
* (c) 1995 MAXON Computer
* Autor: Thomas Binder */
#include <mintbind.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <atarierr.h>
#include <portab.h>
#define DFLT_MAXFLEN 64
#define DFLT_MAXPLEN 512
#define VERSION “V1.04 vom 10.08.1995“
/* Prototypen */
WORD rel2abs(char *path, char **destpath);
WORD d_getcwd(char *buf, WORD drive, WORD len);
void usage(void);
WORD main(WORD argc, char *argv[])
{
LONG maxflen,
dirh;
WORD rootdrive = -1,
count = 0,
err;
char *unixmode,
*argument,
*setpath,
*namebuf,
newdrive,
*r;
#ifdef __MINT__
_DTA
#else
DTA
#endif
my_dta,
*olddta;
/* Bei falscher Parameterzahl Syntax ausgeben */
if (argc ! = 2)
{
usage();
return(2);
}
/* Versuchen, in die MiNT-Domain zu schalten */
if (Pdomain(-1) != EINVFN)
Pdomain(1);
/*
* Aus übergebenem Pfad einen garantiert absoluten
* basteln, also stets in der Form X:\_____\
*/
if ((err = rel2abs(argv[1], &argument)) != 0)
{
puts("DirRead: Fehler bei rel2abs "
"aufgetreten!");
return(err);
}
/*
* Jetzt $UNIXMODE auswerten. Enthält es rX, wird
* X als Default-Laufwerk gesetzt (wenn es dem
* GEMDOS bekannt ist)
*/
if ((unixmode = getenv("UNIXMODE")) != NULL)
{
if ((r = strchr(unixmode, 'r')) != NULL)
{
newdrive = r[1];
if ((newdrive >= 'A') && (newdrive <= 'Z'))
{
newdrive -= 'A';
if (Dsetdrv(Dgetdrv()) & (1L << (LONG)newdrive))
{
rootdrive = (WORD)newdrive;
Dsetdrv(rootdrive);
}
}
}
}
if ((dirh = Dopendir(".", 0)) == EINVFN)
{
olddta = Fgetdta();
Fsetdta(&my_dta);
strcat(argument, "*.*"); err = Fsfirst(argument, 0x17);
while (!err)
{
count++;
#ifdef __MINT__
puts(my_dta.dta_name) ;
#else
puts(my_dta.d_fname);
#endif
err = Fsnext();
}
if (err == EPTHNF)
{
printf("DirRead: Verzeichnis %s "
"nicht erreichbar!\n", argument);
free(argument);
return(err);
}
Fsetdta(olddta);
}
else
{
if ((dirh & 0xff000000L) != 0xff000000L)
Dclosedir(dirh);
if ((rootdrive >= 0) && (argument[0] != ((char)rootdrive + 65)))
{
argument[1] = argument[0] | 32;
argument[0] = '\\';
setpath = argument;
}
else
{
Dsetdrv((WORD)argument[0]-65);
setpath = &argument[2];
}
if ((err = Dsetpath(setpath)) < 0)
{
printf("DirRead: Verzeichnis %s "
"nicht erreichbar!\n", argument);
free(argument);
return(err);
}
if (Dpathconf(".", -1) >= 3L)
{
maxflen = Dpathconf(".", 3);
if (maxflen == 0x7fffffffL)
maxflen = DFLT_MAXFLEN;
}
/* maxflen um 5 erhöhen, wegen Index und \0 */
maxflen += 5;
/* Speicher für den Filenamenspuffer anfordern */
if ((namebuf = malloc(maxflen)) == NULL)
{
puts("DirRead: Nicht genügend "
"Speicher!");
free(argument);
return(ENSMEM);
}
/* Versuchen, das Verzeichnis zu Öffnen */
dirh = Dopendir(argument, 0);
if ((dirh & 0xff000000L) == 0xff000000L)
{
printf("DirRead: Verzeichnis %b "
"nicht erreichbar!\n", argument);
free(namebuf);
free(argument);
return((WORD)dirh);
}
for (;;)
{
if ((err = (WORD)Dreaddir((WORD)
maxflen, dirh, namebuf)) != 0L)
{
if (err == ERANGE)
{
puts("*** Zu langer Filename "
"***");
continue;
}
else
break;
}
count++;
puts(fcnamebuf[4]);
}
/* Wichtig: Dclosedir nicht vergessen! */
Dclosedir(dirh);
free(namebuf);
}
if (count)
printf("%u Dateien\n", count);
else
puts("Keine Dateien!");
free(argument);
return(0);
}
WORD rel2abs(char *path, char **destpath)
{
char *act_path,
*temp,
*pos,
*next,
rootdir[4] = "x:\\";
WORD drive,
err;
LONG maxplen;
if (path[1] == ':')
{
drive = (path[0] & -32) - 65;
pos = &path[2];
}
else
{
if ((drive = Dgetdrv()) < 0)
return(drive);
pos = path;
}
rootdir[0] = (drive + 65);
if (Dpathconf(rootdir, -1) >= 2)
{
maxplen = Dpathconf(rootdir, 2);
if (maxplen == 0x7fffffffL)
maxplen = DFLT_MAXPLEN;
}
else
maxplen = DFLT_MAXPLEN;
maxplen += 2;
if ((*destpath = temp = malloc(maxplen * 2L)) == NULL)
{
return(ENSMEM);
}
/*
* Aktuellen Pfad des betroffenen Laufwerks
* ermittlen
*/
act_path = (char *)((LONG)temp + maxplen);
if ((err = d_getcwd(act_path, drive + 1, (WORD)maxplen)) < 0)
{
free(temp);
*destpath = NULL;
return(err);
}
/*
* Eventuellen Backslash am Ende des aktuellen
* Pfades entfernen
*/
if (*act_path && (act_path[strlen(act_path) - 1] == '\\'))
{
act_path[strlen(act_path) - 1] = 0;
}
sprintf(temp, "%c:", drive + 65);
if (*pos != '\\')
strcat(temp, act_path);
else
for (; *pos && (*pos == '\\'); pos++);
next = pos;
while ((next != NULL) && *pos)
{
if ((next = strchr(pos, '\\')) != NULL)
for (; *next && (*next == '\\'); *next ++ = 0);
if (!strcmp(pos, ".."))
{
if (strlen(temp) > 2)
*strrchr(temp, '\\') = 0;
}
else
{
if (strcmp(pos, "."))
{
strcat(temp, "\\");
strcat(temp, pos);
}
}
if (next != NULL)
pos = next;
}
strcat(temp, "\\");
return(0);
}
WORD d_getcwd(char *buf, WORD drive, WORD len)
{
LONG err;
if ((err = Dgetcwd(buf, drive, len)) !=
EINVFN)
{
return((WORD)err);
}
return(Dgetpath(buf, drive));
}
/*
* usage
*
* Gibt Aufrufsyntax aus.
*/
void usage(void)
{
puts("DirRead "VERSION);
puts("Geschrieben in Pure C von "
"Thomas Binder");
puts("\nAufruf: dirread Verzeichnis");
}