Welcher Besitzer einer Festplatte kennt nicht das Problem: Nach einiger Zeit scheinen Lese- und Schreiboperationen immer langsamer zu werden. Neben dem psychologischen Phänomen der Gewöhnung des Benutzers an die Geschwindigkeit des neuen Gerätes liegt dies vor allem an der entstandenen Unordnung auf der Platte. Ordnung schafft zum Beispiel HD-Pack, geschrieben in Modula-2.
Dieses Programm reorganisiert die Verteilung der Cluster aller Files und Directories innerhalb einer logischen Partition der Festplatte. Dabei werden Files und Directories in zwei Bereichen der Festplatte dicht an dicht gespeichert, um die Anzahl der Bewegungen der Leseköpfe auf der Festplatte bei der Suche in Inhaltsverzeichnissen und beim Lesen und Schreiben von Files auf ein Minimum zu reduzieren.
Das Ergebnis eines einmaligen Reorganisationslaufes ist eine deutlich erhöhte Geschwindigkeit bei nachfolgenden Lade- und Schreiboperationen für diese Partition. Insbesondere bei I/O-intensiven Programmen (z.B. ST-TeX, Modula- und C-Compilern) kann bei einer vollen Partition eine bis zu 6-fach kürzere Programmlaufzeit erreicht werden.
Nach dem Programmstart genügen die Eingabe des Laufwerksbuchstabens der zu optimierenden Partition (z.B. ‘C’) und eine Bestätigung mit der Taste ‘P' um die Reorganisation zu starten. Die Arbeitszeit des Programmes hängt stark von der Belegung der Platte ab: Bei regelmäßigen Reorganisationsläufen genügen 5 Minuten für eine 16-Megabyte-Partition, während der erste Lauf durchaus auch einmal eine halbe Stunde dauern kann.
Am Ende des Programmes müssen Sie den Rechner mit der RESET-Taste neu booten, damit das GEM-Desktop wieder korrekt initialisiert, wird. Ein Abbruch des Programmes während der Programmausführung führt jedoch in 99% der Fälle zu einem Verlust großer Datenmengen in der bearbeiteten Partition. Außerdem sollten Sie das Programm direkt nach dem Abtippen erst einmal auf einer Partition laufen lassen, von deren Inhalt Sie sich Backup-Kopien erstellt haben. (Falls Sie sich so eine Partition völlig zerstört haben, hilft nur noch die Option ZERO im Programm HDX.PRG.)
Hier soll nicht zum x-ten Mal das Betriebssystem GEMDOS des ATARI beschrieben werden. Diese Informationen, das Verständnis der verwendeten Optimierungsmethode betreffend, finden Sie u.a. in den am Schluß des Artikel aufgelisteten Grundlagenartikeln. An dieser Stelle soll nur das Prinzip von HD-Pack Thema sein:
GEMDOS, als die oberste Ebene des Betriebssystems TOS, besitzt unter anderem eine Mittlerrolle zwischen dem Anwenderprogramm, das mit Begriffen wie Datei, Directory, Laufwerk arbeitet, und der gerätenahen Ebene des BIOS, das nur Geräte kennt, auf denen jeweils eine Anzahl von Blöcken (genannt Cluster) direkt adressiert werden kann. Jeder Cluster seinerseits umfaßt einen oder mehrere Sektoren, die jeweils eine feste Größe besitzen.
So ist z.B. die Partition ‘C:’ für das BIOS ein Gerät mit der Nummer 2, das 16384 Cluster besitzt, von denen jeder 2 Sektoren mit je 512 Bytes umfaßt. Jedes Lesen eines Inhaltsverzeichnisses, einer Datei, das Anlegen und Löschen von Dateien etc. wird von GEMDOS in eine Folge von Lese- und Schreibbefehlen für Sektoren auf einem bestimmten Gerät abgebildet, die an BIOS weitergeleitet werden.
Dabei geht GEMDOS davon aus, daß Cluster aufeinanderfolgender Nummern auch physikalisch (auf der Floppy oder auf der Festplatte) nahe beieinander liegen, so daß Dateien, soweit möglich, nicht über viele weit auseinanderliegende Cluster verstreut werden. Im Laufe der Zeit (nach vielen Schreib- und Löschvorgängen) ist GEMDOS jedoch gezwungen, dies dennoch zu tun. Besonders unangenehm ist das, falls es sich nicht um normale Dateien, sondern um Unterverzeichnisse (Directories) handelt, da diese besonders häufig gelesen werden müssen.
Hier setzt nun HDPack ein: Für eine gegebene Plattenbelegung verschiebt es alle Directories direkt zum ‘Anfang’ der Festplatte (niedrige Clusternummern), während alle übrigen Dateien zum ‘Ende’ der Festplatte verschoben werden. Außerdem werden alle Directories so sortiert, daß alle Subdirectory-Einträge vor den restlichen Dateinamen stehen.
Sind im obigen Beispiel 1000 Cluster auf der Platte unbelegt, sowie 50 Cluster für Unterverzeichnisse belegt, ergibt sich die folgende optimierte Belegung:
Cluster 0 bis 49 Directories
Cluster 50 bis 1049 frei
Cluster 1050 bis 16383 übrige Dateien
Die Lücke direkt nach den Directories sorgt dafür, daß GEMDOS auch nach vielen Löschoperationen immer noch schnell freie Cluster finden wird.
Da es nicht möglich ist, zunächst die Festplatte zu leeren, um anschließend die Files in einer besseren Reihenfolge zu speichern, wird die Festplatte durch eine Folge von Clustervertauschungen reorganisiert: Zum Reorganisieren einer Datei (oder eines Directories) werden ihre Cluster nacheinander durchlaufen. Für jeden Cluster wird seine Zielposition (DestCluster) bestimmt. Ist dieser Cluster frei, kann der Clusterinhalt direkt nach DestCluster geschrieben werden. Ansonsten wird der Inhalt des Clusters mit DestCluster vertauscht.
Beispiel: Die erste zu verschiebende Datei befindet sich auf den Clustern 15,300,500,501,502,503 und 1052. Sie soll (s.o.) zu den Clustern 1050, 1051 bis 1056 verschoben werden. Dabei sind die Cluster 1052 bis 1054 belegt. Dazu sind folgende Operationen erforderlich:
Schreibe Cluster 15 nach 1050
Schreibe Cluster 300 nach 1051
Tausche Cluster 500 mit 1052
Tausche Cluster 501 mit 1053
Tausche Cluster 502 mit 1054
Schreibe Cluster 503 nach 1055
Schreibe Cluster 500 nach 1056 (!)
Bis auf die letzte Zeile sollten Ihnen diese Operationen klar sein. Jedoch wurde in der letzten Zeile nicht, wie erwartet, Cluster 1052 nach 1056 geschrieben. Das liegt daran, daß bereits im 3. Schritt der Inhalt von Cluster 1052 nach Cluster 500 verschoben wurde.
Dieser einfache Algorithmus ist der Kern des gesamten Programmes. Die Größe des Programmes resultiert daraus, daß es bei der Verschiebung einer Datei erforderlich ist, Verweise auf den jeweiligen Cluster zu aktualisieren:
Neben dieser Buchführungsarbeit werden alle Operationen am Bildschirm protokolliert und Lese- oder Schreibfehler intelligent abgefangen, um Datenverluste zu vermeiden.
Das Hauptprogramm (Listing aufschlagen!) läßt sich in die folgenden Schritte aufteilen: Zunächst wird BIOS gebeten, nähere Details über die Festplattenpartition preiszugeben (BIOS(7) Routine, genannt BIOS. GetBPB).
Hier wird nun überprüft, ob HD-Pack mit diesen Informationen arbeiten kann: Während auf einer Floppy jeder FAT-Eintrag 1.5 Bytes benötigt, sind auf einer Festplatte 2 Bytes erforderlich. um die große Anzahl an Clustern indizieren zu können. Desweiteren wird die Größe von Sektoren, Cluster und Platte in globalen Variablen aufbewahrt.
Nun werden die beiden File Allocation Tables (FAT 1 und 2) gelesen und auf Gleichheit getestet. Nur wenn Ihre Festplatte schweren Schaden genommen hat, werden diese nicht übereinstimmen, so daß Sie vor dem Start von HDPACK.PRG mit dem von ATARI gelieferten Festplatten-Hilfsprogramm HDX.PRG und der Option MARKBAD retten müssen, was noch zu retten ist.
Während des Vertauschungsprozesses werden zwei Informationen für jeden Cluster benötigt: Wo stand dieser Cluster vor dem Beginn des Optimierens? Wohin ist der ursprüngliche Inhalt des Clusters verschoben worden? Diese Daten werden in den Arrays NewPos und OldPos ständig aktualisiert. Dementsprechend werden diese beiden Arrays vor dem Optimieren initialisiert.
Die Prozedur SortDirectory stellt Unterverzeichniseinträge in Verzeichnissen an den Anfang, so daß diese später von GEMDOS schneller gefunden werden können. Diese minimale Optimierung kann bei Bedarf ersatzlos gestrichen werden, da sie nicht Vorbedingung für die nachfolgenden Verschiebungen ist.
Mit MakeNewFAT wird eine FAT erzeugt, in der alle Cluster als frei markiert sind. Wie später auch bei MoveFile werden defekte Sektoren, die in HDX.PRG mit der Option MARKBAD als unbrauchbar gekennzeichnet wurden, ausgelassen. Anschließend werden mit rekursiven Aufrufen von CompactDirectory die (geschachtelten) Inhaltsverzeichnisse ab dem ersten verfügbaren Cluster auf Platte (FirstCluster) gespeichert. Dabei wird gleichzeitig die Variable DestCluster erhöht. Nach den Inhaltsverzeichnissen wird eine maximal große Lücke von Num-berFree Clustern gelassen. Nach dieser Lücke folgen nun die Dateien, die mit CompactFiles (ebenfalls rekursiv) dicht an dicht gespeichert werden. Am Ende dieser Operation hat DestCluster den letzten adressierbaren Cluster auf der Platte erreicht. Während NewFAT bereits durch MoveFile aktualisiert wird, müssen die Verweise im Directory auf den Beginn jeder Datei und jedes Unterverzeichnisses explizit mit UpdateLinks (rekursiv) nachgetragen werden.
Schließlich wird NewFAT in zweifacher Ausfertigung zurück auf Platte geschrieben.
Weitere Details finden sich im (hoffentlich) ausreichend dokumentierten Listing, wobei Sie insbesondere auf die Kommentare bei den Variablen und Konstanten achten sollten.
Für Pascal- und C-Programmierer, die ein wenig Verständnis von Modula-2 haben, ist eine Umsetzung in ihre Lieblingssprache mit den obigen Hinweisen und dem Listing sicher nicht schwierig. Für BASIC-Pro-grammierer und absolute Assembler-Fanatiker könnte die Rekursion in CompactDirectory, CompactFiles sowie UpdateLinks problematisch sein. Hier hilft die Standardmethode, lokale Variablen (hier also i, x, r, StartCl) auf einem Stack zu speichern.
Untypischerweise werden in HD-Pack fast keine Importe aus anderen Modulen vorgenommen, so daß nur die Operationen BIOS(4) (BIOS.RW Abs) und BIOS(7) (BIOS.GetBPB) und der Typ BIOS.BPBPtr (s. ca. Zeile 75 des Listings) extern deklariert werden müssen.
Florian Matthes
Referenzen
Das Betriebssystem des Atari ST, c't 6/86
Das Betriebssystem des Atari ST, c’t 10/86 S. 122
FAT. Cluster und halbe Bytes, mc 9/86 S. 46
Das ST Betriebssystem (2), ST 5/86 S. 49
Das ST Betriebssystem (4), ST 7/86 S. 30
Floppy Spielereien (1), ST 6/87 S. 20
Floppy-Spielereien (2), ST 8/87 S. 99
MODULE HDPack;
(* Optimiere Festplattenbelegung:
- Sortiere Verzeichnisse so, daß Unterverzeichnisse vor den Files stehen
- Speichere alle (Unter-)Verzeichnisse ab dem 'Anfang' der Festplatte
- Speichere alle Datenfiles ab dem 'Ende' der Festplatte
Florian Matthes 18.10.1987 (TDI-Modula Version 3.0)
*)
FROM SYSTEM IMPORT ADR, ADDRESS, TSIZE;
FROM InOut IMPORT WriteCard, WriteString, WriteLn, Write, Read;
IMPORT BIOS;
CONST (* Spezielle Einträge in der FAT:
FreeCluster = 0; (* dieser Cluster ist unbelegt *)
FirstCluster = 2; (* Offset zur Umwandlung Cluster-Sektor *)
BadCluster = 0FFF7H; (* dieser Cluster ist defekt *)
(* z.B. Plattenfehler (nicht verwenden) *)
EOFCluster = 0FFF8H; (* Cluster mit diesem oder einem höheren *)
(* Index sind der letzte Cluster eines *)
(* Files *)
MaxPhysCluster= 03FFFH; (* maximale Clustergröße für HDPack *)
(* ==> FAT ist kleiner als 64K *)
MaxCluster = 0FFFFH; (* theoretisches Maximum und auch *)
(* von ATARI-BIOS tatsächlich erwartetes *)
(* EOFCluster *)
MaxDirectoryBlocks =64; (* beschränkt Sektorgröße in HDPack auf *)
(* 64*TSIZE(DirectoryRecord)= 64K *)
(* der Inhalt des ersten Buchstabens eines Filenamens in einem *)
(* Directory kennzeichnet dessen Typ: *)
FreeName = OH; (* Eintrag noch nie benutzt *)
UnusedName = 0E5H; (* Eintrag nicht mehr benutzt *)
SpecialName = 02EH; (* = ORD('.') reservierte Namen: *)
(* '.' zeigt auf lfd. Directory *)
(* '..' zeigt auf VaterDirectory *)
TYPE ClusterIndex = [0..MaxCluster];
ClusterIndex8086 = CARDINAL;
(* Wie bei 80xxx Prozessoren: H und L-Byte vertauscht *)
attributset = SET OF (readonlyattr,
hiddenattr,
systemattr,
volumeattr,
subdirattr,
(* dieses File ist ein Directory *)
archivattr);
DirectoryRecord = RECORD
Name : ARRAY[0..10] OF CHAR;
(* Filename und Extension *)
Attr : attributset;
(* Typ des Files *)
res : ARRAY[0..13] OF CHAR;
(* reserviert *)
StartCluster: ClusterIndex;
(* 1. Cluster des Files *)
Size : LONG CARD;
(* Filegröße in Bytes *)
END;
DirectoryArray = ARRAY [0..MaxDirectoryBlocks-1] OF DirectoryRecord;
(* Struktur eines Sektors in einem Directory *)
VAR (* Die 'File Allocation Table' enthält folgende Informationen:
Index 0 Format Version
1 unbenutzt
2.. MaxPhysCluster Belegung des jeweiligen Clusters
OrgFAT : FAT vor dem Optimieren
NewFAT : FAT nach dem Optimieren
*)
OrgFAT, NewFAT : ARRAY [0..MaxPhysCluster] OF ClusterIndex8086;
NewPos, OldPos : ARRAY [0..MaxPhysCluster] OF ClusterIndex;
(* NewPos[i] liefert die momentane Position des ehemals i-ten
Cluster OldPos[i] liefert die ehemalige Position des
momentan i-ten Cluster *)
BPBPtr : BIOS.BPBPtr;
(* Zeiger auf BIOS Parameterblock für dev. Die Deklaration in BIOS ist:
POINTER TO RECORD
recsiz, clsiz, clsizb,
rdlen, fsiz,
fatrec, datrec, numcl,
bflags: CARDINAL;
END;
*)
dev : CARDINAL;
(* Gerätenummer 0 = 'A:', 1 ='B:' *)
(* aus BPBPtr^ abgeleitete Größen: *)
SectorsPerCluster : CARDINAL;
FirstDataSector : CARDINAL;
DirectoryRecordsPerSector: CARDINAL;
FirstDirectorySector : CARDINAL;
PROCEDURE Abort(x: ARRAY OF CHAR; nochanges: BOOLEAN);
(* Programmabbruch mit Fehlermeldung *)
BEGIN
WriteLn;
WriteString(x);
WriteLn;
IF nochanges THEN
WriteString('Keine Änderungen auf Platte durchgeführ');
ELSE
WriteString('Leider sind wahrscheinlich einige Files zerstört worden.');
END;
WriteLn;
WriteString('weiter mit einer beliebigen Taste...'); WriteLn;
Read(CH);
HALT; (* Beende Programmausführung mit Fehlermeldung *)
END Abort;
PROCEDURE SwapLH(x:CARDINAL): CARDINAL;
(* Tausche Low und High-Byte (Umwandlung zwischen 80xx - 68000 Format) *)
BEGIN
RETURN 256 * (x MOD 256) + (x DIV 256)
END SwapLH;
PROCEDURE ClusterToSector(x: ClusterIndex): CARDINAL;
(* Berechne den ersten Sektor, in dem der Cluster x beginnt *)
BEGIN
RETURN (x-FirstCluster) *
SectorsPerCluster + FirstDataSector; END ClusterToSector;
PROCEDURE SectorToCluster(x: CARDINAL): ClusterIndex;
(* Berechne den Cluster, in dem sich der Sektor x befindet *)
BEGIN
RETURN (x-FirstDataSector) DIV
SectorsPerCluster + FirstCluster; END SectorToCluster;
PROCEDURE Next(x:ClusterIndex):ClusterIndex; (* liefere den Nachfolger des Clusters gemäß FAT
*)
BEGIN
RETURN SwapLH(OrgFAT[x]);
END Next;
PROCEDURE ReadAbs(buf : ADDRESS;
(* in diesen Puffer *) Count : CARDINAL;
(* Anzahl der Sektoren *)
recno : CARDINAL;
(* Index erster Sektor *)
VAR errorcode: CARDINAL);
(* BIOS-Erfolgsmeldung *)
BEGIN
errorcode:= CARDINAL(BIOS.RWAbs(BIOS.Read, buf,count,recno,dev));
END ReadAbs;
PROCEDURE WriteAbs(buf : ADDRESS;
(* in diesen Puffer *) count : CARDINAL;
(* Anzahl der Sektoren *)
recno : CARDINAL;
(* Index erster Sektor *)
VAR errorcode: CARDINAL);
(* BIOS-Erfolgsmeldung *)
BEGIN
errorcode:CARDINAL(BIOS.RWAbs(BIOS.Write, buf,count,recno,dev));
END WriteAbs;
PROCEDURE LocateDirectoryRecord(Cluster,
Index : CARDINAL;
VAR Sector, Offset : CARDINAL)
: BOOLEAN;
(* Lokalisiere indizierten Directory-Eintrag auf der Platte. Beachte
bereits stattgefundene Tauschoperationen.
Das Funktionsergebnis ist FALSE, falls dieser Eintrag nicht existiert.
Eingabe:
Cluster: 1.Cluster des zu bearbeitenden Directories (Position
vor dem Optimieren). Ist Cluster = 0, so handelt es sich um das Wurzelverzeichnis.
Index : Index des gewünschten Eintrages (ab 0 gezählt) 9
Ausgabe:
Sector : Sektor, in dem sich der Eintrag auf Platte befindet
Offset : Index des Eintrages in diesem Sector (ab 0 gezählt)
Beispiel :
LocateDirectoryRecord(1234, 15, sec, offs)
bestimmt den Sektor, in dem der 16. Filenamen des Unterverzeichnisses steht, das im Cluster 1234 beginnt.
Innerhalb dieses Sektors (sec) steht der Filenamen an der Position offs.
*)
BEGIN
IF Cluster = 0 THEN
(* Suche im Wurzelverzeichnis *)
Sector:= Index DIV DirectoryRecordsPerSector;
IF Sector+1>BPBPtr^.rdlen THEN
(* nicht über das Ende hinausgehen *)
RETURN FALSE
END;
INC(Sector, FirstDirectorySector);
ELSE
LOOP
(* durchlaufe das Directory sektorweise *)
IF Cluster>= EOFCluster THEN (* nicht über das Ende hinausgehen *)
RETURN FALSE END;
IF Index<DirectoryRecordsPerSector* SectorsPerCluster THEN EXIT;
END;
Cluster:= Next(Cluster);
DEC (Index, DirectoryRecordsPerSector* SectorsPerCluster);
END;
Sector:= ClusterToSector(NewPos[Cluster]) + Index DIV DirectoryRecordsPerSector;
END;
Offset:=Index MOD DirectoryRecordsPerSector;
RETURN TRUE;
END LocateDirectoryRecord;
PROCEDURE GetDirectoryRecord(Cluster: CARDINAL;
Index : CARDINAL;
VAR r : DirectoryRecord): BOOLEAN;
(* Hole Eintrag r mit diesem Index (0...).
Ergebnis = FALSE, falls dieser Eintrag nicht existiert.
*)
VAR Sector, Offset : CARDINAL;
DirectorySector: DirectoryArray;
errorcode : CARDINAL;
BEGIN
IF LocateDirectoryRecord(Cluster, Index, Sector, Offset) THEN
ReadAbs(ADR(DirectorySector),1,Sector, errorcode);
r := DirectorySector[Offset];
RETURN (errorcode=0) AND (ORD(r.Name[0])# FreeName);
ELSE
RETURN FALSE;
END;
END GetDirectoryRecord;
PROCEDURE PutDirectoryRecord(Cluster: CARDINAL;
Index : CARDINAL; r :
DirectoryRecord): BOOLEAN;
(* Schreibe Eintrag mit diesem Index (0...). Ergebnis = FALSE,
falls dieser Eintrag nicht existiert.
*)
VAR Sector, Offset : CARDINAL;
DirectorySector: DirectoryArray;
errorcode : CARDINAL;
BEGIN
IF LocateDirectoryRecord(Cluster, Index, Sector, Offset) THEN
ReadAbs(ADR(DirectorySector),1,Sector, errorcode);
IF errorcode#0 THEN RETURN FALSE;
END;
DirectorySector[Offset]:= r;
WriteAbs(ADR(DirectorySector),1,Sector, errorcode);
RETURN errorcode=0;
ELSE
RETURN FALSE;
END;
END PutDirectoryRecord;
PROCEDURE SortDirectory(StartCl: CARDINAL);
(* Sortiere (Unter-)Verzeichnisse so, daß Unterverzeichnisnamen
vor den 'normalen' Filenamen auftreten: Dabei ist kein Update
der FAT nötig, da alle Vertauschungen innerhalb
des Files stattfinden.
Sortieralgorithmus: Direktes Einfügen
*)
VAR r,r2 : DirectoryRecord;
i,j,x,y: CARDINAL;
BEGIN
(* Zunächst rekursiv alle Söhne sortieren:*)
i := 0;
WHILE GetDirectoryRecord(StartCl,i,r) DO
WITH r DO
x: = ORD(Name[0]);
IF (x <> UnusedName) AND (x <> Special Name) AND
(subdirattr IN Attr) THEN
SortDirectory(SwapLH(StartCluster));
END;
END;
INC(i);
END; (* WHILE *)
(* jetzt eigenes Directory sortieren: *)
i := 0;
WHILE GetDirectoryRecord(StartCl,i,r) DO
WITH r DO
x: = ORD(Name[0]);
IF (x <> UnusedName) AND (x <> Special Name) AND
NOT(subdirattr IN Attr) THEN
(* i zeigt auf den ersten Eintrag im Directory, der
keinen Unterverzeichnisnamen enthält *)
j:= i+1;
LOOP
IF NOT GetDirectoryRecord(StartCl,j,r2) THEN RETURN;
(* Sortierung beendet, da kein
Unterverzeichniseintrag mehr gefunden wurde. *)
END;
y:= ORD(r2.Name[0]);
IF (y <> UnusedName) AND (y <>SpecialName)
AND (subdirattr IN r2.Attr) THEN
(* j zeigt auf den ersten
Unterverzeichnisnamen nach i *)
WriteString('Tausche Directory-Einträge: ');
WriteString(Name) ; WriteString('mit ');
WriteString(r2.Name) ; WriteLn;
IF NOT PutDirectoryRecord (StartCl, j, r) THEN
WriteString(r.Name);
WriteString(" kann nicht geschrieben werden ");
WriteLn; RETURN;
ELSIF NOT PutDirectoryRecord (StartCl, i, r2) THEN
WriteString (r2.Name);
WriteString(" kann nicht geschrieben werden ");
WriteLn; RETURN;
END;
EXIT;
END;
INC(j);
END;
END; (* IF *)
END; (* WITH *)
INC(i);
END; (* WHILE *)
END SortDirectory;
PROCEDURE CountFree(): CARDINAL;
(* zähle die unbelegten Cluster auf der Platte *)
VAR i,Free: CARDINAL;
BEGIN
Free:= 0;
FOR i:= FirstCluster TO BPBPtr^.numc1-1 DO
IF OrgFAT[i]=FreeCluster THEN INC(Free);
END;
END;
RETURN Free;
END CountFree;
(* Die folgenden Variablen werden global von der Prozedur MoveFile verändert: *)
VAR DestCluster : ClusterIndex;
(* Ziel für nächsten Cluster *)
FirstFreeDestCluster: ClusterIndex;
(* Ziel für ersten Datencluster *)
PROCEDURE MoveFile(Cluster: CARDINAL);
(* Tausche alle Cluster des Files mit den Clustern ab DestCluster,
Als Seiteneffekt wird DestCluster erhöht und Cluster in NewFAT verkettet.
*)
TYPE Operation = (wr,rd);
VAR SourceCluster: ClusterIndex;
(* tatsächliche Position für Cluster *)
OldDest : ClusterIndex;
(* ehemalige Position für DestCluster*)
Predecessor : ClusterIndex;
(* zuletzt geschriebener Cluster *)
Current : ClusterIndex;
(* ehemalige Position lfd. Cluster *)
A, B : DirectoryArray;
(* Puffer für je einen Cluster *)
PROCEDURE OK(Op : Operation;
VAR X: DirectoryArray;
CI: ClusterIndex): BOOLEAN;
VAR err: CARDINAL;
BEGIN
IF Op = rd THEN
ReadAbs(ADR(X) , SectorsPerCluster,
ClusterToSector(CI) , err);
IF err#0 THEN
WriteString('Warnung: Fehler beim Lesen von Cluster ');
WriteCard(CI, 1) ;
WriteString(' (Cluster nicht verschoben) ');
END;
ELSE
WriteAbs(ADR(X), SectorsPerCluster, ClusterToSector(CI) , err);
IF err#0 THEN
WriteString('Warnung: Fehler beim Schreiben von Cluster ');
WriteCard(CI, 1) ;
WriteString(' (Cluster nicht verschoben) ');
END;
END;
RETURN err=0;
END OK;
BEGIN
Predecessor:= 0;
WHILE Cluster<EOFCluster DO
LOOP
(* Lasse defekte Cluster als Ziel aus: *)
WHILE SwapLH(OrgFAT[DestCluster])= BadCluster DO
INC(DestCluster)
END;
IF DestCluster<BPBPtr^.numc1 THEN EXIT
END;
(* sollte eigentlich nicht passieren: *)
WriteLn;
WriteString('Warnung: Zu wenige freie Cluster ');
WriteLn;
IF FirstFreeDestCluster = 0 THEN
(* vermeide Endlosschleife: *)
Abort('Fataler Fehler: Keine freien Cluster mehr gefunden', FALSE);
ELSE
DestCluster:= FirstFreeDestCluster; FirstFreeDestCluster:= 0;
END;
END;
(* Tausche jetzt NewPos[Cluster] mit DestCluster: *)
SourceCluster:= NewPos[Cluster];
OldDest:= OldPos[DestCluster];
Current:= SourceCluster;
(* vorläufig, wird überschrieben, falls Block-Austausch erfolgreich *)
IF OrgFAT[OldDest]=FreeCluster THEN
(* Ziel-Cluster ist frei, kann direkt überschrieben werden *)
WriteCard(SourceCluster,6);
WriteString('->');
IF OK(rd,A,SourceCluster) AND OK(wr,A, DestCluster) THEN
NewPos[Cluster]:= DestCluster;
NewPos[OldDest]:= SourceCluster;
OldPos[SourceCluster]:= OldDest;
OldPos[DestCluster]:= Cluster;
Current:= DestCluster;
END;
ELSIF SourceCluster#DestCluster THEN
WriteCard(SourceCluster, 6) ;
WriteString('<>');
IF OK(rd,A,SourceCluster) THEN
IF OK(rd,B,DestCluster) THEN
IF OK(wr,B,SourceCluster) THEN
IF OK(wr,A,DestCluster) THEN
NewPos[Cluster]:=DestCluster;
NewPos[OldDest]:=SourceCluster;
OldPos[SourceCluster]:=OldDest;
OldPos[DestCluster]:= Cluster;
Current:= DestCluster;
ELSE
(* mache vorherigen Schreibvorgang rückgängig; *)
IF NOT OK(wr,A,SourceCluster) AND
NOT OK(wr,A,SourceCluster) THEN (* 2-mal *)
WriteLn;
WriteString('Fataler Fehler: Inhalt des Clusters ');
WriteCard(SourceCluster,4);
WriteString(' zerstört'); WriteLn;
END;
END;
END;
END;
END;
END; (* IF unused *)
(* Verkette mit Vorgänger *)
IF Predecessor#0 THEN
IF NewFAT[Predecessor]#FreeCluster
THEN
WriteLn; WriteString('Warnung: Cluster doppelt belegt ');
WriteCard(Predecessor, 4) ; WriteLn;
END;
NewFAT[Predecessor]:= SwapLH(Current);
END;
Predecessor:= Current;
Cluster:= Next(Cluster);
INC(DestCluster);
END; (* WHILE *)
IF Predecessor#0 THEN
IF NewFAT[Predecessor]#FreeCluster
THEN
WriteLn; WriteString('Warnung: Cluster doppelt belegt ');
WriteCard(Predecessor, 4) ; WriteLn;
END;
NewFAT[Predecessor]:=SwapLH(MaxCluster);
END;
END MoveFile;
PROCEDURE CompactDirectory (StartCl: CARDINAL);
(* Schreibe Subdirectories adjazent ab Cluster 2, aktualisiere NewFAT *)
VAR i, x: CARDINAL;
r : DirectoryRecord;
BEGIN
(* Nur falls nicht Wurzelverzeichnis ist Verschieben möglich: *)
IF StartCl#0 THEN
MoveFile(StartCl);
END;
(* Verschiebe jetzt die geschachtelten Verzeichnisse: *)
i:= 0;
WHILE GetDirectoryRecord(StartCl,i, r) DO
WITH r DO
x:= ORD(Name[0]) ;
IF (x <> UnusedName) AND (x <> SpecialName) AND
(* nicht und ! *)
(subdirattr IN Attr) THEN
WriteLn; WriteString(Name);
Write(':');WriteLn;
CompactDirectory(SwapLH (StartCluster));
END;
END;
INC(i);
END; (* WHILE *)
END CompactDirectory;
PROCEDURE CompactFiles (StartCl: CARDINAL);
(* schreibe Datenfiles adjazent bis zum Plattenende, aktualisiere NewFAT *)
VAR i, x: CARDINAL;
r : DirectoryRecord;
BEGIN
i:= 0;
WHILE GetDirectoryRecord(StartCl,i,r) DO
WITH r DO
x:= ORD(Name[0]);
IF (x <> UnusedName) AND (x <> Special Name) THEN
IF subdirattr IN Attr THEN
(* rekursiv die Söhne bearbeiten *)
CompactFiles(SwapLH(StartCluster) );
ELSE
WriteLn; WriteString(Name);
Write(':'); WriteLn;
IF Size#0 THEN
MoveFile(SwapLH(StartCluster));
END;
END;
END;
END;
INC(i);
END; (* WHILE *)
END CompactFiles;
PROCEDURE UpdateLinks (StartCl: CARDINAL);
(* ersetzte Eintrag StartCluster für jedes File und
Unterverzeichnis durch seinen neuen Wert. *)
VAR i,x, Start: CARDINAL;
r: DirectoryRecord;
BEGIN
i:= 0;
WHILE GetDirectoryRecord(StartCl,i,r) DO
WITH r DO
x: = ORD(Name[0]);
Start:= SwapLH(StartCluster);
IF (x <> UnusedName) THEN
(* auch für '.' und '..' ! *)
IF (subdirattr IN Attr) AND (xoSpecialName) THEN
UpdateLinks(Start);
END;
WriteLn; WriteString(Name);
Write(':');
IF (Start>=FirstCluster) AND (Start<=MaxPhysCluster) THEN
StartCluster:=SwapLH(NewPos[Start]);
WriteCard(SwapLH(StartCluster),4);
IF NOT PutDirectoryRecord(StartCl, i,r) THEN
WriteLn;
WriteString('Fehler: StartCluster für File ');
WriteString(Name);
WriteString(' konnte nicht auf den Wert ');
WriteCard(SwapLH(StartCluster),4);
WriteString(' aktualisiert werden!'); WriteLn;
END;
END;
END;
END;
INC (i);
END; (* WHILE *)
END UpdateLinks;
PROCEDURE MakeNewFAT;
(* Erzeuge leere NewFAT, in der fehlerhafte Cluster bereits
markiert sind *)
BEGIN
NewFAT[0]:= OrgFAT[0];
NewFAT[1]:= OrgFAT[1];
FOR i:= FirstCluster TO BPBPtr^.numcl-1 DO
IF SwapLH(OrgFAT[i])=BadCluster THEN
NewFAT[i]:= SwapLH(BadCluster);
ELSE
NewFAT[i]:= FreeCluster;
END;
END;
END MakeNewFAT;
VAR i : CARDINAL;
errorcode : CARDINAL;
CH : CHAR;
NumberFree: CARDINAL;
BEGIN (* Hauptprogramm *)
Write(CHR(27)); Write('v'); Write(CHR(27));
Write( 'E');
WriteString('HDPack:'); WriteLn;
WriteString('----'); WriteLn; WriteLn;
WriteString('Version 1.2 18.10.1987 Florian Matthes'); WriteLn;
WriteLn;
REPEAT
CH:='0’; WriteLn;
WriteString("Buchstabe des logischen Laufwerkes (z.B. 'C') ==>");
Read(CH); CH:= CAP(CH); Write(CH);
WriteLn;
UNTIL (CH>='A') AND (CH<='Z');
dev: = ORD(CH)-ORD("A");
WriteLn;
WriteString('Bitte bestätigen Sie die Optimierung für Laufwerk ');
Write(CHR(dev+ORD('A'))); WriteString(': ') ;
WriteLn;
WriteString("durch die Eingabe des Buchstabens 'P'!"); WriteLn;
WriteLn; WriteString ('W A R N U N G') ;
WriteLn;
WriteString("Unterbrechen Sie keinesfalls den Programmablauf nach der");
WriteLn;
WriteString("Eingabe von 'P' , da dies sicher zum Verlust von Dateien und ") ;
WriteLn;
WriteString('Directories führen würde!');
WriteLn; WriteLn;
WriteString('==>') ; Read(CH); Write(CH) ;
WriteLn;
IF CAP(CH)#'P' THEN
Abort('OK: Programmabbruch durch den Benutzer', TRUE);
END;
BPBPtr:= BIOS.GetBPB(dev);
IF ADDRESS(BPBPtr) = ADDRESS(0) THEN
Abort('Parameter Block nicht gefunden (Laufwerksbuchstabe prüfen)!', TRUE);
END;
(* bestimme die geräteabhängingen Parameter und speichere sie global: *)
WITH BPBPtr^ DO
SectorsPerCluster := clsiz;
FirstDataSector := datrec;
DirectoryRecordsPerSector:= recsiz DIV TSIZE(DirectoryRecord);
FirstDirectorySector := BPBPtr^.fatrec+BPBPtr^.fsiz;
IF numcl>MaxCluster THEN
Abort('Platte besitzt zu viele (>16384) Cluster!', TRUE);
ELSIF numcl<4096 THEN
Abort('FAT ist nicht wortweise organisiert. HDPack läuft nicht für Floppies',TRUE);
ELSIF DirectoryRecordsPerSector> MaxDirectoryBlocks THEN
Abort('Sectorgröße auf dem Laufwerk zu groß (>65355 Bytes)', TRUE);
END;
END;
WriteString('Parameter Block gelesen...'); WriteLn;
ReadAbs(ADR(OrgFAT), (* hole FAT *)
BPBPtr^.fsiz, (* Anzahl Sektoren = Laenge FAT *)
1, errorcode);(* 1. Sektor = 1. Sektor FAT *)
IF errorcode#0 THEN
WriteString('Errorcode =' ) ;
WriteCard(errorcode, 4); WriteLn;
Abort ('Fehler beim Lesen von FAT 1.',TRUE);
END;
WriteString('FAT 1 gelesen...'); WriteLn;
ReadAbs(ADR(OldPos), (* hole 2.FAT
BPBPtr^.fsiz, (* Anzahl Sektoren = Laenge FAT *)
BPBPtr^.fatrec, (* 1. Sektor = 1. Sektor FAT *)
errorcode);
IF errorcode#0 THEN
WriteString('Errorcode );
WriteCard(errorcode, 4); WriteLn;
Abort ('Fehler beim Lesen von FAT 2.',TRUE);
END;
WriteString('FAT 2 gelesen...'); WriteLn;
errorcode:= 0;
FOR i: = 0 TO BPBPtr^.numcl-1 DO
IF OrgFAT[i] # OldPos[i] THEN
INC (errorcode);
WriteCard(i,6); WriteCard(OrgFAT[i], 6);
WriteCard(OldPos[i],6);
WriteLn;
END;
END;
IF errorcode=0 THEN
WriteString ('Gleichheit von FAT 1 und FAT 2 überprüft...'); WriteLn;
ELSE
Abort('FAT1 unterscheidet sich von FAT2 in den obigen Clustern', TRUE);
END;
FOR i:= 0 TO BPBPtr^ .numcl-1 DO
NewPos[i]:= i; OldPos[i] := i;
END;
NumberFree:= CountFree();
WriteCard(NumberFree,1);
WriteString(' Cluster noch unbelegt...'); WriteLn;
WriteString('Sortiere Directories...'); WriteLn;
SortDirectory(0);
WriteLn; WriteString ('Erstelle leere FAT...') ;
MakeNewFAT;
WriteLn;
DestCluster:= FirstCluster;
FirstFreeDestCluster:= 0; (* noch nicht bestimmt *)
CompactDirectory(0);
WriteLn;
FirstFreeDestCluster:= DestCluster;
(* erstes Cluster nach den Directories *)
DestCluster:= FirstFreeDestCluster + NumberFree;
CompactFiles(0);
WriteLn;
WriteString('Aktualisiere Anfangscluster...'); WriteLn;
UpdateLinks(0);
WriteLn;
WriteAbs(ADR(NewFAT) , (* Schreibe FAT *)
BPBPtr^.fsiz, (* Anzahl Sektoren = Laenge FAT *)
1, errorcode); (* 1. Sektor = 1. Sektor FAT *)
IF errorcode#0 THEN
WriteString('Fehler beim Schreiben von FAT 1: Code =');
WriteCard(errorcode, 4) ; WriteLn;
END;
WriteString('FAT 1 geschrieben...'); WriteLn;
WriteAbs(ADR(NewFAT), (* schreibe 2.FAT *)
BPBPtr^.fsiz, (* Anzahl Sektoren = Laenge FAT *)
BPBPtr^.fatrec, (* 1. Sektor = 1. Sektor FAT *)
errorcode);
IF errorcode#0 THEN
WriteString('Fehler beim Schreiben von FAT 2: Code =');
WriteCard(errorcode, 4); WriteLn;
END;
WriteString('FAT 2 geschrieben...');WriteLn;
WriteString('HDPACK erfolgreich beendet.'); WriteLn; WriteLn;
WriteString('Bitte drücken Sie <RESET> um das System erneut zu booten.!' ) ;
WriteLn;
WriteString('(dies ist zur Initialisierung des DESKTOP erforderlich) ');
WriteLn;
LOOP END; (* Endlosschleife *)
END HDPack.