MODULE FATScavenger;
IMPORT
SYSTEM, KernelLog, Streams, Files, FATVolumes, Disks, FATFiles, UTF8Strings,
Strings, PartitionsLib, Clock;
CONST
Trace = FALSE;
Details = FALSE;
OK = Disks.Ok;
LongName = 15;
EntryFree = 0E5X;
EntryFreeLast = 0X;
FREE = FATVolumes.FREE;
EOC = FATVolumes.EOC;
BAD = FATVolumes.BAD;
FAT12* = 0; FAT16* = 1; FAT32* = 2;
fat32CleanShutdown = {27};
fat32IOError = {26};
fat16CleanShutdown = {15};
fat16IOError = {14};
SectorSize = 512;
BufferSize = 512;
BitmapSize = 65536;
BS = PartitionsLib.BS;
BsJmpBoot = 0;
BsOEMName = 3;
BpbBytsPerSec = 11;
BpbSecPerClus = 13;
BpbRsvdSecCnt = 14;
BpbNumFATs = 16;
BpbRootEntCnt = 17;
BpbTotSec16 = 19;
BpbMedia = 21;
BpbFATSz16 = 22;
BpbSecPerTrk = 24;
BpbNumHeads = 26;
BpbHiddSec = 28;
BpbTotSec32 = 32;
BsDrvNum = 36;
BsReserved1 = 37;
BsBootSig = 38;
BsVolID = 39;
BsVolLab = 43;
BsFilSysType = 54;
BpbFATSz32 = 36;
BpbExtFlags = 40;
BpbFSVer = 42;
BpbRootClus = 44;
BpbFSInfo = 48;
BpbBkBootSec = 50;
BpbReserved = 52;
Bs32DrvNum = 64;
Bs32Reserved1 = 65;
Bs32BootSig = 66;
Bs32VolID = 67;
Bs32VolLab = 71;
Bs32FilSysType = 82;
FsiLeadSig = 0;
FsiReserved1 = 4;
FsiStrucSig = 484;
FsiFreeCount = 488;
FsiNxtFree = 492;
FsiReserved2 = 496;
FsiTrailSig = 508;
TYPE
Block = PartitionsLib.Block;
String = PartitionsLib.String;
Node = POINTER TO RECORD
cluster, offset : LONGINT;
parent, first : LONGINT;
next : Node;
END;
STACK = OBJECT
VAR
head : Node;
PROCEDURE PushCluster(cluster : Cluster);
VAR
temp : Node;
BEGIN
ASSERT(cluster#NIL);
NEW(temp);
temp.cluster:=cluster.cluster; temp.offset:=cluster.GetPos();
temp.first:=cluster.first; temp.parent:=cluster.parent;
temp.next:=head.next; head.next:=temp;
END PushCluster;
PROCEDURE Push(node : Node);
BEGIN
ASSERT(node#NIL);
node.next:=head.next; head.next:=node;
END Push;
PROCEDURE ReplaceTop(cluster : Cluster);
BEGIN
ASSERT((cluster#NIL) & (~Empty()));
head.next.cluster:=cluster.cluster;
head.next.offset:=cluster.GetPos();
END ReplaceTop;
PROCEDURE RemoveTop; BEGIN ASSERT(~Empty()); head.next:=head.next.next; END RemoveTop;
PROCEDURE GetTop():Node; BEGIN ASSERT(~Empty()); RETURN head.next; END GetTop;
PROCEDURE Empty():BOOLEAN; BEGIN RETURN (head.next=NIL); END Empty;
PROCEDURE &Init*;
BEGIN
NEW(head); head.next:=NIL;
END Init;
END STACK;
TYPE
LongEntryList = OBJECT
VAR
head, current : LongEntry;
PROCEDURE Insert(entry: LongEntry);
BEGIN
entry.next:=head.next; head.next:=entry;
END Insert;
PROCEDURE GetNext():LongEntry;
VAR
result : LongEntry;
BEGIN
ASSERT((head.next#NIL) & (current#NIL));
result:=current; current:=current.next;
RETURN result;
END GetNext;
PROCEDURE SetCurrent; BEGIN current:=head.next; END SetCurrent;
PROCEDURE HasNext():BOOLEAN; BEGIN RETURN (current#NIL); END HasNext;
PROCEDURE Clear; BEGIN head.next:=NIL; current:=NIL; END Clear;
PROCEDURE &Init*;
BEGIN
NEW(head); head.next:=NIL; current:=NIL;
END Init;
END LongEntryList;
TYPE
Entry = OBJECT
VAR
rawEntry, correctedEntry : ARRAY 32 OF CHAR;
cluster : Files.Address;
offset : LONGINT;
PROCEDURE ParseRawEntry;
BEGIN
HALT(99);
END ParseRawEntry;
PROCEDURE Print;
BEGIN
HALT(99);
END Print;
END Entry;
ShortEntry = OBJECT(Entry)
VAR
shortName: ARRAY 12 OF CHAR;
attr : SET;
crtTime, crtDate : LONGINT;
lstAccDate : LONGINT;
firstCluster : LONGINT;
wrtTime, wrtDate : LONGINT;
fileSize : LONGINT;
directory : BOOLEAN;
PROCEDURE ParseRawEntry;
VAR i : LONGINT;
BEGIN
IF rawEntry[0]=05X THEN
shortName[0]:=0E5X
ELSE
shortName[0]:=rawEntry[0];
END;
FOR i:=1 TO 10 DO shortName[i]:=rawEntry[i]; END;
shortName[11]:=0X;
attr:=SYSTEM.VAL(SET, LONG(ORD(rawEntry[11])));
IF (FATFiles.faDirectory IN attr) THEN directory:=TRUE; ELSE directory:=FALSE; END;
crtTime:=FATFiles.TimeFAT2Oberon(FATVolumes.GetUnsignedInteger(rawEntry,14),ORD(rawEntry[13]));
crtDate:=FATFiles.DateFAT2Oberon(FATVolumes.GetUnsignedInteger(rawEntry,16));
lstAccDate:=FATFiles.DateFAT2Oberon(FATVolumes.GetUnsignedInteger(rawEntry, 18));
firstCluster:=FATVolumes.GetUnsignedInteger(rawEntry, 26);
IF fsType2=FAT32 THEN firstCluster:=firstCluster+10000H*FATVolumes.GetUnsignedInteger(rawEntry,20); END;
wrtTime:=FATFiles.TimeFAT2Oberon(FATVolumes.GetUnsignedInteger(rawEntry,22),0);
wrtDate:=FATFiles.DateFAT2Oberon(FATVolumes.GetUnsignedInteger(rawEntry,24));
fileSize:=FATVolumes.GetLongint(rawEntry,28);
COPY(rawEntry, correctedEntry);
END ParseRawEntry;
PROCEDURE GetChecksum(): LONGINT;
VAR
checksum, i : LONGINT;
BEGIN
checksum:=0;
FOR i:=0 TO 10 DO
IF ODD(checksum) THEN checksum := 80H + checksum DIV 2 ELSE checksum := checksum DIV 2 END;
checksum := (checksum + ORD(shortName[i])) MOD 100H
END;
RETURN checksum;
END GetChecksum;
PROCEDURE Print;
BEGIN
KernelLog.String(shortName);
IF FATFiles.faReadOnly IN attr THEN KernelLog.String(" R"); ELSE KernelLog.String(" r"); END;
IF FATFiles.faHidden IN attr THEN KernelLog.String("H"); ELSE KernelLog.String("h"); END;
IF FATFiles.faSystem IN attr THEN KernelLog.String("S"); ELSE KernelLog.String("s"); END;
IF FATFiles.faArchive IN attr THEN KernelLog.String("A"); ELSE KernelLog.String("a"); END;
IF FATFiles.faDirectory IN attr THEN KernelLog.String("D"); ELSE KernelLog.String("d"); END;
IF FATFiles.faVolumeID IN attr THEN KernelLog.String("V "); ELSE KernelLog.String("v "); END;
KernelLog.String("1st: "); KernelLog.Int(firstCluster,10);
KernelLog.String(" size: "); KernelLog.Int(fileSize,10);
KernelLog.Ln;
END Print;
END ShortEntry;
LongEntry = OBJECT(Entry)
VAR
order : INTEGER;
name: ARRAY 13 OF LONGINT;
type : INTEGER;
chksum : LONGINT;
FstClusLO : LONGINT;
next : LongEntry;
last : BOOLEAN;
PROCEDURE ParseRawEntry;
VAR
i,j : INTEGER;
BEGIN
IF FATVolumes.AND(40H, ORD(rawEntry[0])) = 40H THEN
last:=TRUE;
order:=ORD(rawEntry[0]) MOD 40H;
ELSE
order:=ORD(rawEntry[0]);
END;
chksum:=ORD(rawEntry[13]);
j:=0;
FOR i:=0 TO 4 DO name[j]:=FATVolumes.GetUnsignedInteger(rawEntry,1+2*i); INC(j); END;
FOR i:=0 TO 5 DO name[j]:=FATVolumes.GetUnsignedInteger(rawEntry,14+2*i); INC(j); END;
FOR i:=0 TO 1 DO name[j]:=FATVolumes.GetUnsignedInteger(rawEntry,28+2*i); INC(j); END;
type:=ORD(rawEntry[12]);
FstClusLO:=FATVolumes.GetUnsignedInteger(rawEntry,26);
COPY(rawEntry, correctedEntry);
END ParseRawEntry;
PROCEDURE Print;
VAR longname : ARRAY 256 OF CHAR;
BEGIN
KernelLog.String("Long: "); KernelLog.String("order: "); KernelLog.Int(order,3); KernelLog.String(" ");
UTF8Strings.UnicodetoUTF8(name, longname); KernelLog.String(longname);
KernelLog.Ln;
END Print;
PROCEDURE &Init*;
BEGIN
last:=FALSE; next:=NIL;
END Init;
END LongEntry;
TYPE
Bitmap = POINTER TO ARRAY OF SET;
ClusterBitmap = OBJECT
VAR
maxClusters : LONGINT;
bitmaps : POINTER TO ARRAY OF Bitmap;
bitmapsPos, bmPos, bmOffset: LONGINT;
PROCEDURE &Init*(MaxClusters : LONGINT);
VAR
size : LONGINT;
BEGIN
maxClusters:=MaxClusters;
size:=maxClusters DIV BitmapSize*MAX(SET);
IF maxClusters MOD BitmapSize*MAX(SET)>0 THEN INC(size); END;
NEW(bitmaps, size);
ASSERT(bitmaps#NIL);
END Init;
PROCEDURE CalcAddress(pos: LONGINT);
VAR
bitmapSize : LONGINT;
BEGIN
ASSERT((pos<=maxClusters+1) & (pos>1));
bitmapSize:=BitmapSize*(MAX(SET)+1);
bitmapsPos:=pos DIV bitmapSize;
bmPos := ( pos MOD bitmapSize ) DIV (MAX(SET)+1);
bmOffset := ( pos MOD bitmapSize ) MOD (MAX(SET)+1);
ASSERT((bmOffset <= MAX(SET)) & (bmPos<BitmapSize));
END CalcAddress;
PROCEDURE SetBit(pos : LONGINT; VAR collision: BOOLEAN);
VAR
bitmap : Bitmap;
BEGIN
ASSERT((pos<=maxClusters+1) & (pos>1));
CalcAddress(pos);
IF bitmaps[bitmapsPos]=NIL THEN
NEW(bitmap, BitmapSize);
ASSERT(bitmap#NIL);
bitmaps[bitmapsPos]:=bitmap;
END;
bitmap:=bitmaps[bitmapsPos];
IF bmOffset IN bitmap[bmPos] THEN
collision:=TRUE;
ELSE
INCL(bitmap[bmPos], bmOffset); collision:=FALSE;
END;
END SetBit;
PROCEDURE IsSet(pos : LONGINT):BOOLEAN;
BEGIN
CalcAddress(pos);
RETURN bmOffset IN bitmaps[bitmapsPos][bmPos];
END IsSet;
END ClusterBitmap;
Cluster = OBJECT
VAR
cluster : LONGINT;
parent, first : LONGINT;
clusterSize : LONGINT;
currentEntry, maxEntries: LONGINT;
data : POINTER TO ARRAY OF CHAR;
next : LONGINT;
PROCEDURE &Init*(csize :LONGINT);
BEGIN
clusterSize:=csize; maxEntries:=clusterSize DIV 32;
NEW(data, clusterSize);
next:=0;
END Init;
PROCEDURE SetPos(pos: LONGINT);
BEGIN
ASSERT((pos<=maxEntries) & (pos >= 0));
currentEntry:=pos;
END SetPos;
PROCEDURE GetPos(): LONGINT;
BEGIN
ASSERT(currentEntry<=maxEntries);
RETURN currentEntry;
END GetPos;
PROCEDURE GetNext():Entry;
VAR
result : Entry;
shortEntry : ShortEntry;
longEntry : LongEntry;
i ,j : LONGINT;
type: LONGINT;
BEGIN
ASSERT(currentEntry<=maxEntries-1);
type:=FATVolumes.AND(3FH, ORD(data[currentEntry*32+11]));
IF (data[currentEntry*32]#EntryFree) & (data[currentEntry*32]#EntryFreeLast) THEN
IF (type=LongName) THEN
NEW(longEntry); result:=longEntry;
ELSE
NEW(shortEntry); result:=shortEntry;
END;
j:=0;
FOR i:=currentEntry*32 TO currentEntry*32+31 DO result.rawEntry[j]:=data[i];INC(j); END;
result.offset:=currentEntry;
result.ParseRawEntry;
ELSE
result:=NIL;
END;
currentEntry:=currentEntry+1;
RETURN result;
END GetNext;
PROCEDURE HasNext():BOOLEAN;
BEGIN
RETURN (currentEntry <= maxEntries - 1);
END HasNext;
END Cluster;
TYPE
PathName = POINTER TO RECORD
name : POINTER TO ARRAY OF CHAR;
next : PathName;
END;
Path = OBJECT
VAR
head : PathName;
prefix : Files.Prefix;
PROCEDURE &Init*(CONST prefix : Files.Prefix);
BEGIN
NEW(head); head.next:=NIL;
SELF.prefix := prefix;
END Init;
PROCEDURE Append(CONST dirname : ARRAY OF CHAR);
VAR temp, new : PathName; i : INTEGER;
BEGIN
NEW(new); NEW(new.name,LEN(dirname));
i:=0;
WHILE i<LEN(dirname) DO
IF dirname[i]#" " THEN new.name[i] :=dirname[i];END;
INC(i);
END;
temp:=head; WHILE temp.next#NIL DO temp:=temp.next; END;
temp.next:=new;
END Append;
PROCEDURE RemoveLast;
VAR temp : PathName;
BEGIN
ASSERT(head.next#NIL);
temp:=head; WHILE(temp.next.next#NIL) DO temp:=temp.next; END;
temp.next:=NIL;
END RemoveLast;
PROCEDURE Get(VAR result: ARRAY OF CHAR);
VAR
temp : PathName;
writer : Streams.StringWriter;
BEGIN
NEW(writer, 2048);
temp := head;
writer.String(prefix);
writer.String(":");
WHILE (temp.next#NIL) DO
temp := temp.next;
writer.String(temp.name^);
writer.String(Files.PathDelimiter);
END;
writer.Get(result);
END Get;
PROCEDURE Print;
VAR temp : PathName;
BEGIN
ASSERT(head.next#NIL);
KernelLog.String(prefix); KernelLog.String(":");
temp:=head;
WHILE(temp.next#NIL) DO
temp:=temp.next;
KernelLog.String(temp.name^); KernelLog.String(Files.PathDelimiter);
END;
KernelLog.Ln;
END Print;
END Path;
LostCluster = POINTER TO RECORD
cluster, link : LONGINT;
next : LostCluster;
chain : LostCluster;
terminated, crosslink : BOOLEAN;
END;
ClusterList = OBJECT
VAR
head, tail, previous, current : LostCluster;
size : LONGINT;
currentDeleted : BOOLEAN;
PROCEDURE &Init*;
BEGIN
IF(head=NIL) THEN NEW(head); END;
head.next:=NIL; tail:=NIL;
previous:=head; current:=head;
size:=0; currentDeleted:=FALSE;
END Init;
PROCEDURE Insert(lost : LostCluster);
BEGIN
IF head.next=NIL THEN
lost.next:=NIL;
head.next:=lost; tail:=lost;
ELSE
lost.next:=head.next;
head.next:=lost;
END;
INC(size);
END Insert;
PROCEDURE Append(lost : LostCluster);
BEGIN
lost.next:=NIL;
IF head.next=NIL THEN
head.next:=lost; tail:=lost;
ELSE
tail.next:=lost; tail:=lost;
END;
INC(size);
END Append;
PROCEDURE SetCurrent;
BEGIN
current:=head; previous:=head;
END SetCurrent;
PROCEDURE GetNext(): LostCluster;
BEGIN
ASSERT((head.next#NIL) & (current#NIL));
IF currentDeleted THEN
currentDeleted:=FALSE;
current:=previous.next;
ELSE
previous:=current;
current:=current.next;
END;
RETURN current;
END GetNext;
PROCEDURE HasNext():BOOLEAN;
BEGIN
RETURN current.next#NIL;
END HasNext;
PROCEDURE RemoveCurrent;
BEGIN
ASSERT((current#NIL) & (previous#NIL) & ~Empty() & (current#head));
IF current=tail THEN tail:=previous; END;
previous.next:=current.next;
currentDeleted:=TRUE;
DEC(size);
END RemoveCurrent;
PROCEDURE Empty():BOOLEAN;
BEGIN
RETURN head.next=NIL;
END Empty;
PROCEDURE Print;
VAR
temp : LostCluster;
BEGIN
temp:=head;
KernelLog.String("*** lost cluster list ***"); KernelLog.Ln;
WHILE temp.next#NIL DO
temp:=temp.next;
KernelLog.Int(temp.cluster,10); KernelLog.Int(temp.link,10); KernelLog.Ln;
END;
END Print;
END ClusterList;
TYPE
FATScavenger*= OBJECT(PartitionsLib.Operation);
VAR
doSurfaceScan, doCompareFATs, doLostClusters, doWrite : BOOLEAN;
filesScanned-, directoriesScanned- : LONGINT;
longEntriesScanned-, shortEntriesScanned-, emptyEntriesScanned- : LONGINT;
freeClusters-, badClusters-, lostClusters-, lostClusterChains- : LONGINT;
nbrFreeFragments : LONGINT;
errorsFound: LONGINT;
ioError : BOOLEAN;
curOp, maxOp : LONGINT;
vol : FATVolumes.Volume; dev : Disks.Device;
path : Path;
cluster, baseCluster : Cluster;
processStack : STACK;
longList : LongEntryList;
lostList, lostErrorList, fileList, xlinkedList : ClusterList;
fsType : LONGINT;
FAT1216rootDir : Cluster;
clusterBitmap : ClusterBitmap;
fsinfo : ARRAY FATVolumes.BS OF CHAR;
fsinfoAddress : LONGINT;
fsInfoLoaded : BOOLEAN;
deleted : LONGINT;
PROCEDURE SetParameters*(doSurfaceScan, doCompareFATs, doLostClusters, doWrite : BOOLEAN);
BEGIN
SELF.doSurfaceScan := doSurfaceScan; SELF.doCompareFATs := doCompareFATs; SELF.doLostClusters := doLostClusters;
SELF.doWrite := doWrite;
IF doWrite THEN locktype := PartitionsLib.WriterLock ELSE locktype := PartitionsLib.ReaderLock; END;
curOp := 0; maxOp := 1;
IF doSurfaceScan THEN INC(maxOp); END; IF doCompareFATs THEN INC(maxOp); END;
IF doLostClusters THEN INC(maxOp); END;
END SetParameters;
PROCEDURE ValidParameters*() : BOOLEAN;
BEGIN
dev := disk.device;
IF dev = NIL THEN ReportError("Could not access device"); RETURN FALSE; END;
IF ~PartitionsLib.IsFatType(disk.table[partition].type) & ~disk.isDiskette THEN
ReportError("Scavenger only supports FAT formatted partitions"); RETURN FALSE;
END;
RETURN TRUE;
END ValidParameters;
PROCEDURE DoOperation*;
VAR
string, str : String;
collision : BOOLEAN;
vdf, clnShutdown, ioError : SET;
bpb : Block;
res : LONGINT;
BEGIN
IF Trace THEN KernelLog.String("FATScavenger started on "); END;
SetStatus(state.status, "Scavenger starting...", 0, 0, 0, FALSE);
dev.Transfer(Disks.Read, disk.table[partition].start, 1, bpb, 0, res);
IF res # Disks.Ok THEN
PartitionsLib.GetErrorMsg("Could not load boot sectors", res, string); ReportError(string);
RETURN;
END;
vol := GetVolume(dev, partition, bpb);
IF vol = NIL THEN
ReportError("Could not get FAT volume");
RETURN;
ELSE
info.String("FAT volume type: ");
IF vol IS FATVolumes.FAT12Volume THEN
fsType := FAT12; info.String("FAT12");
ELSIF vol IS FATVolumes.FAT16Volume THEN
fsType := FAT16; info.String("FAT16");
ELSIF vol IS FATVolumes.FAT32Volume THEN
fsType := FAT32; info.String("FAT32");
ELSE
ReportError("Only FAT12, FAT16 and FAT32 volumes are supported");
info.String("No FAT (Error)");
RETURN;
END;
info.Ln;
END;
fsType2 := fsType;
NEW(clusterBitmap, vol.maxClusters); NEW(cluster, vol.clusterSize);
NEW(path, "");
IF (fsType = FAT16) OR (fsType = FAT32) THEN
vol.unsafe := TRUE;
vdf := SYSTEM.VAL(SET, vol.ReadFATEntry(1));
vol.unsafe := FALSE;
IF fsType = FAT16 THEN
clnShutdown := fat16CleanShutdown; ioError := fat16IOError;
ELSE
clnShutdown := fat32CleanShutdown; ioError := fat32IOError;
END;
IF vdf * ioError # ioError THEN
info.String("Device reports I/O error"); info.Ln;
IF ~doSurfaceScan THEN
ReportError("Device reports I/O errors. A surface scan is recommended.");
END;
END;
IF vdf * clnShutdown # clnShutdown THEN
info.String("The volume has not been properly unmounted last time"); info.Ln;
END;
IF doWrite THEN
IF vdf * (clnShutdown + ioError) # (clnShutdown + ioError) THEN
vdf := vdf + clnShutdown + ioError;
vol.unsafe := TRUE;
vol.WriteFATEntry(1, SYSTEM.VAL(LONGINT, vdf), res);
vol.unsafe := FALSE;
IF res # Disks.Ok THEN
PartitionsLib.GetErrorMsg("Could not write back volume dirty flags ", res, string); ReportError (string);
ELSE
info.String("Corrected: Cleared volume dirty flag"); info.Ln;
END;
END;
END;
END;
IF alive & doSurfaceScan THEN SurfaceScan; END;
IF alive & doCompareFATs THEN CompareFATs; END;
IF alive THEN
CASE fsType OF
FAT12..FAT16: BEGIN
NEW(FAT1216rootDir,vol(FATVolumes.FAT1216Volume).numRootSectors*FATVolumes.BS);
ASSERT(FAT1216rootDir#NIL);
dev.Transfer(Disks.Read, vol.start + vol(FATVolumes.FAT1216Volume).firstRootSector,
vol(FATVolumes.FAT1216Volume).numRootSectors, FAT1216rootDir.data^,0,res);
IF res # Disks.Ok THEN
ReportTransferError("Critical: Couldn't load FAT1216 root directoy",
Disks.Read, vol.start + vol(FATVolumes.FAT1216Volume).firstRootSector, res);
alive := FALSE;
END;
baseCluster := cluster;
cluster := FAT1216rootDir;
cluster.next := EOC;
cluster.first := 1; cluster.parent := 1; cluster.cluster := 1;
END;
| FAT32: BEGIN
cluster.cluster := vol(FATVolumes.FAT32Volume).rootCluster;
vol(FATVolumes.FAT32Volume).ReadCluster(cluster.cluster, cluster.data^, res);
IF res # Disks.Ok THEN
PartitionsLib.GetErrorMsg("Critical: Couldn't load FAT32 root directory, res:", res, string); ReportError(string);
alive := FALSE;
END;
cluster.first := cluster.cluster;
clusterBitmap.SetBit(cluster.cluster, collision);
END;
END;
cluster.SetPos(0); processStack.PushCluster(cluster);
path.Append(Files.PathDelimiter);
IF alive THEN ProcessHead; END;
END;
IF alive & doLostClusters THEN
NEW(lostList); NEW(lostErrorList);
TraverseFAT;
IF ~lostList.Empty() & alive THEN
CheckLostClusters;
string := "Found "; Strings.IntToStr(lostClusters, str); Strings.Append(string, str);
Strings.Append(string, " lost cluster in "); Strings.IntToStr(lostClusterChains, str);
Strings.Append(string, str); Strings.Append(string, " chains");
IF doWrite & alive THEN
DeleteLostClusters;
Strings.Append(string, "(corrected)");
ReportError(string);
ELSE
Strings.Append(string, "(not correctly since writes not allowed by user)");
ReportError(string);
END;
END;
END;
IF alive THEN
result.String("Scavenger on "); result.String(diskpartString); result.String(" finished ");
IF state.status * PartitionsLib.StatusError = {} THEN
result.String("without errors");
ELSE
result.String("width "); result.Int(state.errorCount, 0); result.String(" errors");
END;
BuildInfo;
END;
IF Trace THEN IF ~alive THEN KernelLog.String("Scanner aborted."); ELSE KernelLog.String("Scanner finished."); KernelLog.Ln; END; END;
END DoOperation;
PROCEDURE &Init*(disk : PartitionsLib.Disk; partition : LONGINT; out : Streams.Writer);
BEGIN
Init^(disk, partition, out);
name := "FATScavenger"; desc := "Check FAT file system on partition"; locktype := PartitionsLib.WriterLock;
NEW(longList); NEW(processStack); NEW(xlinkedList);
END Init;
PROCEDURE BuildInfo;
BEGIN
info.String("Volume Information: "); info.String(diskpartString); info.Ln;
info.String("Clusters: "); info.Int(vol.maxClusters, 3); info.String(" ClusterSize: "); info.Int(vol.clusterSize, 3); info.String("B");
info.Ln;
info.String("Files: "); info.Int(filesScanned, 3); info.String(" Directories: "); info.Int(directoriesScanned, 3);
info.Ln;
info.String("Short Entries: "); info.Int(shortEntriesScanned, 3);
info.String(" Long Entries: "); info.Int(longEntriesScanned, 3);
info.String(" Empty Entries: "); info.Int(emptyEntriesScanned, 3);
info.Ln;
IF errorsFound > 0 THEN
info.Int(lostClusters, 4); info.String(" lost clusters found in "); info.Int(lostClusterChains, 3); info.String(" chains."); info.Ln;
END;
END BuildInfo;
PROCEDURE GetFSInfo(VAR freeCount, nextFree : LONGINT) : BOOLEAN;
VAR
bootsector : ARRAY FATVolumes.BS OF CHAR;
res : LONGINT;
BEGIN
dev.Transfer(Disks.Read, vol.start, 1, bootsector, 0, res);
IF res # OK THEN
ReportTransferError("Could not load FSinfo block (", Disks.Read, vol.start + vol.startFAT, res);
RETURN FALSE;
END;
IF (bootsector[42] # 0X) OR (bootsector[43] # 0X) THEN
ReportError("Couldn't not load FSInfo block (Wrong FAT32 FS verion)");
RETURN FALSE;
END;
fsinfoAddress := FATVolumes.GetUnsignedInteger(bootsector, 48);
dev.Transfer(Disks.Read, vol.start + fsinfoAddress, 1, fsinfo, 0, res);
IF res # OK THEN
ReportTransferError("Couldn't load FSinfo block (", Disks.Read, vol.start + fsinfoAddress, res);
RETURN FALSE;
END;
IF (FATVolumes.GetLongint(fsinfo, 0) = 041615252H) &
(FATVolumes.GetLongint(fsinfo, 508) = 0AA550000H) &
(FATVolumes.GetLongint(fsinfo, 484) = 061417272H)
THEN
fsInfoLoaded := TRUE;
freeCount := FATVolumes.GetLongint(fsinfo, 488);
nextFree := FATVolumes.GetLongint(fsinfo, 492);
ELSE
ReportError("Signature of FSinfo block if wrong");
RETURN FALSE;
END;
RETURN TRUE;
END GetFSInfo;
PROCEDURE SetFSInfo(freeCount, nextFree : LONGINT);
VAR res : LONGINT;
BEGIN
IF fsInfoLoaded THEN
FATVolumes.PutLongint(fsinfo, 488, freeCount);
FATVolumes.PutLongint(fsinfo, 492, nextFree);
IF doWrite THEN
dev.Transfer(Disks.Write, vol.start + fsinfoAddress, 1, fsinfo, 0, res);
IF res # OK THEN
ReportTransferError("Could not store FSinfo block (", Disks.Write, vol.start+ fsinfoAddress, res);
END;
END;
ELSE ReportError("FSinfo block is not loaded");
END;
END SetFSInfo;
PROCEDURE CompareFATs;
CONST UpdateRate = 3;
VAR
buffer : POINTER TO ARRAY OF CHAR;
buffersize, reads : LONGINT;
operation : String;
i, j, k, res : LONGINT;
BEGIN
IF Trace THEN KernelLog.String("Comparing FATs... "); END;
INC(curOp); operation := GetString(curOp, maxOp, "", "Comparing FAT structures");
SetStatus(state.status, operation, 0, 0, vol.fatSize, TRUE);
buffersize := BufferSize * FATVolumes.BS;
NEW(buffer, buffersize * vol.numFATs);
ASSERT(buffer # NIL);
reads := vol.fatSize DIV BufferSize;
k := 0;
WHILE k < reads DO
INC(k);
IF (k = reads) & ((vol.fatSize MOD BufferSize) # 0) THEN
buffersize := (vol.fatSize MOD BufferSize) * FATVolumes.BS;
END;
FOR i := 0 TO vol.numFATs-1 DO
dev.Transfer(Disks.Read, vol.start + vol.startFAT+ i*vol.fatSize, BufferSize, buffer^, i*buffersize, res);
IF res # OK THEN
ReportTransferError("CompareFATs: IO error (", Disks.Read, vol.start + vol.startFAT+ i*vol.fatSize, res);
END;
END;
FOR i := 0 TO buffersize-1 DO
FOR j := 1 TO vol.numFATs-1 DO
IF buffer[i + j*buffersize] # buffer[i] THEN
ReportError("CompareFATs: SERIOUS ERROR: File allocation tables are not equal");
alive := FALSE;
END;
END;
END;
IF (k MOD UpdateRate = 0) OR (k >= reads) THEN
SetCurrentProgress(k * BufferSize);
END;
IF ~alive THEN IF Trace THEN KernelLog.String("aborted."); KernelLog.Ln; END; RETURN; END;
END;
IF alive THEN info.String("Comparing FATs succeeded."); info.Ln; END;
IF Trace & alive THEN KernelLog.String("done."); KernelLog.Ln;END;
END CompareFATs;
PROCEDURE TraverseFAT;
CONST
UpdateRate = 10000;
VAR
collision, lastFree : BOOLEAN;
lost : LostCluster;
cluster, link, firstFree : LONGINT;
operation, string : String;
BEGIN
IF Trace THEN KernelLog.String("Building up cluster bitmap... "); END;
ASSERT((clusterBitmap # NIL) & (lostList # NIL));
INC(curOp); operation := GetString(curOp, maxOp, "", "Analyzing FAT: ");
string := operation; Strings.Append(string, GetString(0, vol.maxClusters+1, "Cluster", ""));
SetStatus(state.status, string, 0, 0, vol.maxClusters, TRUE);
lastFree := FALSE; firstFree := -1;
FOR cluster:=2 TO vol.maxClusters+1 DO
link := vol.ReadFATEntry(cluster);
CASE link OF
|FREE: BEGIN
IF firstFree=-1 THEN firstFree:=cluster; END;
INC(freeClusters);
IF ~lastFree THEN INC(nbrFreeFragments); END;
lastFree:=TRUE;
END;
|BAD: BEGIN
lastFree:=FALSE;
INC(badClusters);
clusterBitmap.SetBit(cluster, collision);
IF collision THEN
INC(errorsFound);
collision := FALSE;
info.String("Cannot fix: File contains bad cluster."); info.Ln;
KernelLog.String("error: bad cluster in file"); KernelLog.Ln;
END;
END;
ELSE
lastFree := FALSE;
IF ~clusterBitmap.IsSet(cluster) THEN
INC(errorsFound);
NEW(lost); lost.cluster:=cluster; lost.link:=link;
IF (link # EOC) & clusterBitmap.IsSet(link) THEN
lostErrorList.Append(lost);
ELSE
lostList.Append(lost);
END;
INC(lostClusters);
END;
END;
IF (cluster MOD UpdateRate = 0) OR (cluster = vol.maxClusters+1) THEN
string := operation; Strings.Append(string, GetString(cluster, vol.maxClusters+1, "Cluster", ""));
SetStatus(state.status, string, 0, cluster - 1, state.max, TRUE);
END;
IF ~alive THEN IF Trace THEN KernelLog.String("aborted."); END; RETURN; END;
END;
IF Trace THEN KernelLog.String("done."); KernelLog.Ln; END;
END TraverseFAT;
PROCEDURE SurfaceScan;
CONST
UpdateRate = 99;
VAR
data : POINTER TO ARRAY OF CHAR;
newBadClusters, cluster, link : LONGINT;
operation, string, temp : String;
address : Files.Address;
res : LONGINT;
BEGIN
NEW(data,vol.clusterSize);
IF Trace THEN KernelLog.String("Surface scan started..."); END;
INC(curOp); operation := GetString(curOp, maxOp, "", "Surface scan: ");
string := operation; Strings.Append(string, GetString(0, vol.maxClusters, "Cluster", ""));
SetStatus(state.status, string, 0, 0, vol.maxClusters, TRUE);
newBadClusters := 0;
FOR cluster := 2 TO vol.maxClusters+1 DO
link := vol.ReadFATEntry(cluster);
address := vol.startData + (cluster * vol.sectorsPC);
dev.Transfer(Disks.Read, address, vol.sectorsPC, data^, 0, res);
IF res#OK THEN
string := "Cluster "; Strings.IntToStr(cluster, temp); Strings.Append(string, temp); Strings.Append(string, " is bad");
PartitionsLib.GetErrorMsg(", res: ", res, temp); Strings.Append(string, temp);
ReportError(string);
IF link#BAD THEN
IF link=FREE THEN INC(deleted); END;
INC(newBadClusters);
IF doWrite THEN vol.WriteFATEntry(cluster, BAD, res);
IF res # OK THEN
string := "Failed to mark cluster "; Strings.Append(string, temp); Strings.Append(string, " as bad");
PartitionsLib.GetErrorMsg(", res: ", res, temp); Strings.Append(string, temp);
ReportError(string);
ELSE
string := "Cluster"; Strings.Append(string, temp); Strings.Append(string, " marked as bad");
info.String(string); info.Ln;
END;
END;
END;
END;
IF (cluster MOD UpdateRate = 0) OR (cluster = vol.maxClusters+1) THEN
string := operation; Strings.Append(string, GetString(cluster, vol.maxClusters+1, "Cluster", ""));
SetStatus(state.status, string, 0, cluster, vol.maxClusters, TRUE);
END;
IF ~alive THEN IF Trace THEN KernelLog.String("aborted."); KernelLog.Ln; END; RETURN; END;
END;
info.String("Surface scan: "); info.Int(newBadClusters, 0); info.String(" bad sectors found."); info.Ln;
IF Trace THEN KernelLog.Int(newBadClusters,6); KernelLog.String(" new bad sectors found...");KernelLog.String("done."); KernelLog.Ln; END;
END SurfaceScan;
PROCEDURE DeleteLostClusters;
VAR
temp, temp2 : LostCluster;
string, error : String;
counter, res : LONGINT;
BEGIN
IF Trace THEN KernelLog.String("Deleting lost clusters... "); END;
ASSERT((lostErrorList#NIL) & (fileList#NIL));
counter := 0;
lostErrorList.SetCurrent;
WHILE lostErrorList.HasNext() DO
temp := lostErrorList.GetNext();
IF doWrite THEN vol.WriteFATEntry(temp.cluster, FREE, res); INC(counter); END;
IF res # OK THEN
string := ""; PartitionsLib.GetErrorMsg("Critical: Could not delete lost clusters (", res, error); Strings.Append(string, error); Strings.Append(string, ")");
ReportError(string);
RETURN;
END;
END;
fileList.SetCurrent;
WHILE fileList.HasNext() DO
temp := fileList.GetNext();
IF doWrite THEN vol.WriteFATEntry(temp.cluster, FREE, res); INC(counter); END;
IF res # OK THEN
string := ""; PartitionsLib.GetErrorMsg("Critical2: Could not delete lost clusters (", res, error); Strings.Append(string, error); Strings.Append(string, ")");
ReportError(string);
RETURN;
END;
temp2 := temp.chain;
WHILE temp2 # NIL DO
IF doWrite THEN vol.WriteFATEntry(temp2.cluster, FREE, res); INC(counter); END;
IF res # OK THEN
string := ""; PartitionsLib.GetErrorMsg("Critical3: Could not delete lost clusters (", res, error); Strings.Append(string, error); Strings.Append(string, ")");
ReportError(string);
RETURN;
END;
temp2 := temp2.next;
END;
END;
IF vol IS FATVolumes.FAT32Volume THEN INC(deleted); END;
info.Int(counter, 0); info.String(" lost clusters deleted"); info.Ln;
IF Trace THEN KernelLog.String("Deleted "); KernelLog.Int(counter, 0); KernelLog.String("clusters, done."); KernelLog.Ln; END;
END DeleteLostClusters;
PROCEDURE CheckLostClusters;
VAR
tempList : ClusterList;
tempCluster, tempLink : LONGINT;
lost, temp : LostCluster;
found : BOOLEAN;
xlink, collision, terminated: BOOLEAN;
BEGIN
IF Trace THEN KernelLog.String("Processing lost cluster list... "); END;
ASSERT(lostList#NIL);
NEW(fileList);
NEW(tempList);
WHILE ~lostList.Empty() DO
found:=TRUE;
WHILE found=TRUE DO
lostList.SetCurrent;
found:=FALSE;
WHILE(lostList.HasNext()) DO
lost:=lostList.GetNext();
IF tempList.Empty() THEN
lostList.RemoveCurrent; lost.next:=NIL;
tempList.Insert(lost);
tempCluster:=lost.cluster; tempLink:=lost.link;
found:=TRUE;
END;
IF lost.cluster=tempLink THEN
lostList.RemoveCurrent; lost.next:=NIL;
tempList.Append(lost);
tempLink:=lost.link;
found:=TRUE;
ELSIF lost.link=tempCluster THEN
lostList.RemoveCurrent; lost.next:=NIL;
tempList.Insert(lost);
tempCluster:=lost.cluster;
found:=TRUE;
END;
END;
END;
IF found=FALSE THEN
tempList.SetCurrent;
ASSERT(~tempList.Empty());
lost:=tempList.GetNext();
lost.chain:=lost.next; lost.next:=NIL;
fileList.Append(lost);
fileList.SetCurrent;
lost:=fileList.GetNext();
tempList.Init;
END;
END;
fileList.SetCurrent;
WHILE fileList.HasNext() DO
lost:=fileList.GetNext();
clusterBitmap.SetBit(lost.cluster,collision); xlink:=collision; terminated:=(lost.chain=NIL) & (lost.link=EOC);
temp:=lost.chain;
WHILE(temp#NIL) DO
clusterBitmap.SetBit(temp.cluster, collision);
IF collision THEN xlink:=TRUE; END;
IF (temp.next=NIL) & (temp.link=EOC) THEN terminated:=TRUE; END;
temp:=temp.next;
END;
lost.terminated:=terminated;
lost.crosslink:=xlink;
END;
lostClusterChains := fileList.size;
IF Trace THEN KernelLog.String(" done."); KernelLog.Ln; END;
END CheckLostClusters;
PROCEDURE CheckLongEntries(shortEntry : ShortEntry);
VAR
chksum : LONGINT;
temp : LongEntry;
order : INTEGER;
longName : ARRAY 256 OF CHAR;
unicode : ARRAY 256 OF LONGINT;
lastFound : BOOLEAN;
padding : BOOLEAN; firstPadding : INTEGER;
i : INTEGER;
BEGIN
IF Details THEN KernelLog.String("CheckLongEntries of "); KernelLog.String(shortEntry.shortName); KernelLog.String("..."); END;
chksum := shortEntry.GetChecksum(); order := 0; lastFound := FALSE;
longList.SetCurrent();
WHILE (longList.HasNext()) & (order<20) & (~lastFound) DO
INC(order);
temp:=longList.GetNext();
IF temp.order#order THEN
KernelLog.String("Cannot fix: Long entry order mismatch"); KernelLog.Ln;
info.String("Cannot fix: Long entry order mismatch."); info.Ln;
END;
IF temp.chksum#chksum THEN
KernelLog.String("Cannot fix: Long entry chksum mismatch"); KernelLog.Ln;
info.String("Cannot fix: Long enty chksum mismatch."); info.Ln;
END;
FOR i:=0 TO 12 DO unicode[(order-1)*13+i]:=temp.name[i]; END;
IF temp.last THEN lastFound:=TRUE; unicode[order*13]:=0; END;
END;
IF (order#0) & (~lastFound) THEN
KernelLog.String("Cannot fix: Last entry of long entry sequence not found."); KernelLog.Ln;
info.String("Cannot fix: Last entry of long entry sequence not found."); info.Ln;
ELSE
UTF8Strings.UnicodetoUTF8(unicode,longName);
padding:=FALSE; firstPadding:=0;
FOR i:=0 TO order*13-1 DO
IF (padding=TRUE) & (unicode[i]#0FFFFH) THEN
KernelLog.String("Cannot fix: Incorrect padding in long entry."); KernelLog.Ln;
info.String("Cannot fix: Incorrect padding in long entry."); info.Ln;
END;
IF (unicode[i]=0) & (padding=FALSE) THEN padding:=TRUE; firstPadding:=i;END;
END;
IF firstPadding=0 THEN firstPadding:=order*13-1; END;
FOR i:=0 TO firstPadding-1 DO
IF ~ValidLongChar(longName[i]) THEN
KernelLog.String("Cannot fix: Invalid char in long name"); KernelLog.Ln;
info.String("Cannot fix: Invalid char in long name."); info.Ln;
END;
END;
END;
IF longList.HasNext() THEN
KernelLog.String("Cannot fix: Remaing long entries are orphans"); KernelLog.Ln;
info.String("Cannot fix: Remaining long entries are orphans."); info.Ln;
END;
longList.Clear();
IF Details THEN KernelLog.String(" done."); KernelLog.Ln; END;
END CheckLongEntries;
PROCEDURE CheckDotDot(shortEntry : ShortEntry);
VAR
rootDir : LONGINT;
BEGIN
ASSERT(shortEntry.shortName[0]=".");
IF shortEntry.shortName=". " THEN
IF shortEntry.firstCluster#cluster.first THEN
KernelLog.String("Cannot fix: dot entry points wrong."); KernelLog.Ln;
info.String("Cannot fix: Dot entry points wrong."); info.Ln;
END;
ELSIF shortEntry.shortName=".. " THEN
IF shortEntry.firstCluster=0 THEN
CASE fsType OF
FAT12..FAT16 : rootDir:=1;
|FAT32 : rootDir:=vol(FATVolumes.FAT32Volume).rootCluster;
END;
IF cluster.parent#rootDir THEN
KernelLog.String("Warning: dot dot points to root but should not");
info.String("Cannot fix: Dot dot point to root but should not."); info.Ln;
END;
ELSIF shortEntry.firstCluster#cluster.parent THEN
KernelLog.String("ERROR: dot dot entry points wrong:"); KernelLog.Int(shortEntry.firstCluster,10);
KernelLog.String(" parent is :"); KernelLog.Int(cluster.parent,10); KernelLog.Ln;
info.String("Cannot fix: Dot dot entry wrong."); info.Ln;
END;
ELSE
KernelLog.String("Cannot fix: Invalid shortEntry (starts with .)"); KernelLog.Ln;
info.String("Cannot fix: Invalid short entry (name starts with . )"); info.Ln;
END;
END CheckDotDot;
PROCEDURE ProcessShortEntry(shortEntry: ShortEntry);
VAR
counter : SHORTINT;
clusterCount : LONGINT;
link, oldlink : LONGINT;
fragments : LONGINT;
collision : BOOLEAN;
xlinked : LostCluster;
BEGIN
IF Details THEN shortEntry.Print; END;
FOR counter := 0 TO 10 DO
IF ~ValidShortChar(shortEntry.shortName[counter]) THEN
KernelLog.String("Invalid short name: "); KernelLog.String(shortEntry.shortName); KernelLog.Ln;
info.String("Invalid short name: "); info.String(shortEntry.shortName); info.Ln;
END;
END;
IF FATFiles.faVolumeID IN shortEntry.attr THEN
DEC(filesScanned);
ELSE
CheckLongEntries(shortEntry);
fragments:=1; collision:=FALSE;
clusterCount:=1; link:=shortEntry.firstCluster;
WHILE (link>1) & ((clusterCount-1)*vol.clusterSize<=shortEntry.fileSize) DO
oldlink:=link;
link:=vol.ReadFATEntry(link);
INC(clusterCount);
IF link#oldlink+1 THEN INC(fragments); END;
clusterBitmap.SetBit(oldlink, collision); ASSERT(collision=FALSE);
IF collision THEN
collision:=FALSE; KernelLog.String("Warning: PSE:crosslink detected!"); KernelLog.Ln;
NEW(xlinked); xlinked.cluster:=oldlink; xlinked.link:=link;
xlinkedList.Append(xlinked);
END;
END;
IF (shortEntry.fileSize>0) & (link#EOC) THEN
KernelLog.String("Cannot fix: Wrong file size");
info.String("Cannot fix: Wrong file size: "); info.String(shortEntry.shortName); info.Ln;
END;
END;
END ProcessShortEntry;
PROCEDURE ProcessHead*;
VAR
temp: Node;
operation, string, tempStr : String;
entry : Entry; shortEntry : ShortEntry;
dirFragments, dirClusters : LONGINT;
collision : BOOLEAN;
res : LONGINT;
BEGIN
IF Trace THEN KernelLog.String("Scanning FAT directory structure... "); END;
INC(curOp); operation := GetString(curOp, maxOp, "", "Scanning FAT directory: ");
string := operation; path.Get(tempStr); Strings.Append(string, tempStr);
SetStatus(state.status, string, 0, 0, 0, FALSE);
dirFragments:=1; dirClusters:=1;
WHILE(~processStack.Empty()) DO
IF ~alive THEN IF Trace THEN KernelLog.String("aborted."); KernelLog.Ln; END; RETURN; END;
temp:=processStack.GetTop();
IF (fsType<=FAT16) & (temp.cluster=1) & (temp.parent=1) & (temp.first=1) THEN
cluster.cluster:=1;
cluster:=FAT1216rootDir;
END;
IF cluster.cluster # temp.cluster THEN
IF Details THEN
KernelLog.String("(re)load cluster: "); KernelLog.Int(temp.cluster,8); KernelLog.String(" offset: "); KernelLog.Int(temp.offset,4); KernelLog.Ln;
END;
ASSERT(temp.cluster>1);
vol.ReadCluster(temp.cluster, cluster.data^, res);
IF res#OK THEN
string := "ProcessHead: Could not read cluster "; Strings.IntToStr(temp.cluster, tempStr); Strings.Append(string, tempStr);
PartitionsLib.GetErrorMsg(" (res: ", res, tempStr); Strings.Append(string, tempStr);
ReportError(string);
KernelLog.String(" load failed!!");
END;
cluster.cluster:=temp.cluster;
cluster.first:=temp.first;
cluster.parent:=temp.parent;
cluster.SetPos(temp.offset);
END;
IF (cluster.HasNext()) THEN
entry:=cluster.GetNext();
IF entry#NIL THEN
IF (entry IS LongEntry) THEN
INC(longEntriesScanned);
longList.Insert(entry(LongEntry));
ELSE
INC(shortEntriesScanned);
shortEntry:=entry(ShortEntry);
IF (shortEntry.directory=FALSE) THEN
ProcessShortEntry(shortEntry);
INC(filesScanned);
ELSE
IF (shortEntry.shortName[0]=".") THEN
CheckDotDot(shortEntry);
ELSE
path.Append(shortEntry.shortName);
string := operation; path.Get(tempStr); Strings.Append(string, tempStr);
SetStatus(state.status, string, 0, 0, 0, FALSE);
CheckLongEntries(shortEntry);
IF Details THEN
KernelLog.String("open directory "); KernelLog.String(entry(ShortEntry).shortName); KernelLog.Ln; path.Print;
END;
INC(dirClusters); INC(dirFragments); INC(directoriesScanned);
processStack.ReplaceTop(cluster);
NEW(temp); temp.cluster:=entry(ShortEntry).firstCluster; temp.offset:=0;
temp.parent:=cluster.first; temp.first:=temp.cluster;
IF cluster=FAT1216rootDir THEN
cluster:=baseCluster;
cluster.cluster:=1;
END;
clusterBitmap.SetBit(temp.cluster, collision);
processStack.Push(temp);
END;
END;
END;
ELSE
INC(emptyEntriesScanned);
END;
ELSE
ASSERT(cluster.currentEntry=cluster.maxEntries);
processStack.RemoveTop;
IF cluster # FAT1216rootDir THEN cluster.next := vol.ReadFATEntry(cluster.cluster); END;
IF (cluster.next # EOC) & (cluster.next # FREE) & (cluster.next # BAD) THEN
INC(dirClusters);
IF cluster.next # cluster.cluster+1 THEN INC(dirFragments);END;
NEW(temp);
temp.cluster:=cluster.next; temp.offset:=0;
temp.first:=cluster.first; temp.parent:=cluster.parent;
processStack.Push(temp);
clusterBitmap.SetBit(temp.cluster,collision);
ELSE
path.RemoveLast;
END;
END;
END;
IF Trace THEN KernelLog.String(" done."); KernelLog.Ln; END;
END ProcessHead;
PROCEDURE WriteEntry(entry: Entry);
VAR
temp : Cluster;
temp2 : Entry;
test : BOOLEAN;
address : Files.Address;
offset, res : LONGINT;
BEGIN
ASSERT((entry # NIL) & (vol # NIL) & (dev # NIL));
test:=TRUE;
IF cluster.cluster # entry.cluster THEN
IF (vol IS FATVolumes.FAT1216Volume) & (entry.cluster=1) THEN
ASSERT(FAT1216rootDir#NIL); cluster:=FAT1216rootDir;
ELSE
vol.ReadCluster(entry.cluster, temp.data^, res);
END;
END;
temp.SetPos(entry.offset);
ASSERT(cluster.HasNext());
temp2:=cluster.GetNext();
ASSERT(Equals(temp2.rawEntry, entry.rawEntry));
IF (fsType <= FAT16) & (entry.cluster = 1) THEN
address := vol.startData + vol(FATVolumes.FAT1216Volume).firstRootSector + (entry.offset DIV SectorSize);
offset := entry.offset MOD SectorSize;
ELSE
address := vol.startData+ (entry.cluster*vol.sectorsPC) + (entry.offset DIV SectorSize);
offset := entry.offset MOD SectorSize;
END;
ASSERT( (address>vol.endFAT) & (address<(vol.maxClusters+1)*vol.sectorsPC));
IF doWrite THEN vol.WriteCluster(entry.cluster, cluster.data^, res); END;
END WriteEntry;
PROCEDURE GetString(cur, max : LONGINT; CONST unit, status : ARRAY OF CHAR) : String;
VAR string : String; temp : ARRAY 16 OF CHAR;
BEGIN
string := "";
IF unit#"" THEN Strings.Append(string, unit); Strings.Append(string, " "); END;
Strings.IntToStr(cur, temp); Strings.Append(string, temp); Strings.Append(string, " of ");
Strings.IntToStr(max, temp); Strings.Append(string, temp);
IF status#"" THEN Strings.Append(string, ": "); Strings.Append(string, status); END;
RETURN string;
END GetString;
PROCEDURE ReportTransferError(name : ARRAY OF CHAR; op, adr, res : LONGINT);
VAR temp: ARRAY 256 OF CHAR;
BEGIN
Strings.Append(name, " (");
PartitionsLib.GetTransferError(dev, op, adr, res, temp); Strings.Append(name, temp);
Strings.Append(name, ")");
ioError := TRUE; ReportError(name);
END ReportTransferError;
PROCEDURE ValidShortChar(ch : CHAR): BOOLEAN;
BEGIN
RETURN ((ch>=020X) & (ch#022X) & (ch#02AX) & (ch#02BX) & (ch#02CX) & (ch#02EX) & (ch#02FX) & (ch#03AX) &
(ch#03BX) & (ch#03CX) & (ch#03DX) & (ch#03EX) & (ch#03FX) & (ch#05BX) & (ch#05CX) & (ch#05DX) & (ch#07CX));
END ValidShortChar;
PROCEDURE ValidLongChar(ch: CHAR): BOOLEAN;
BEGIN
RETURN (ch >= 20X) & (ch # "\") & (ch # "/") & (ch # ":") & (ch # "*") & (ch # "?") & (ch # '"') & (ch # "<") & (ch # ">") & (ch # "|");
END ValidLongChar;
PROCEDURE Equals(CONST op1, op2 : ARRAY OF CHAR): BOOLEAN;
VAR i : LONGINT;
BEGIN
ASSERT( (LEN(op1)#0) & (LEN(op2)#0) );
IF LEN(op1)#LEN(op2) THEN
RETURN FALSE;
ELSE
i := 0;
WHILE i < LEN(op1) DO
IF op1[i]#op2[i] THEN RETURN FALSE; END;
END;
RETURN TRUE;
END;
END Equals;
END FATScavenger;
TYPE
FormatPartition* = OBJECT(PartitionsLib.Operation);
VAR
quickFormat : BOOLEAN; volumeName : Strings.String;
fs : LONGINT;
oemName : ARRAY 9 OF CHAR;
rsvdSecCnt1216, rsvdSecCnt32 : LONGINT;
numFATs : LONGINT;
rootEntCnt : LONGINT;
fatsize : LONGINT;
volLab : ARRAY 12 OF CHAR;
rootCluster32 : LONGINT;
fsinfo : LONGINT;
backupBoot : LONGINT;
PROCEDURE SetParameters*(volumeName : Strings.String; quickFormat : BOOLEAN);
BEGIN
SELF.volumeName := volumeName; SELF.quickFormat := quickFormat;
END SetParameters;
PROCEDURE ValidParameters*() : BOOLEAN;
BEGIN
IF disk.device.blockSize # BS THEN ReportError("Blocksize not supported"); RETURN FALSE END;
IF ~PartitionsLib.IsFatType(disk.table[partition].type) & ~disk.isDiskette THEN
ReportError("Partition type is not FAT"); RETURN FALSE;
END;
RETURN TRUE;
END ValidParameters;
PROCEDURE DoOperation*;
VAR
vol : FATVolumes.Volume;
block : Block;
null : POINTER TO ARRAY OF CHAR;
rootDirSectors, firstDataSector : LONGINT;
freeCount, firstFree, media: LONGINT;
spc, type : LONGINT;
i, res : LONGINT;
temp: ARRAY 256 OF CHAR;
BEGIN
type := disk.table[partition].type;
IF (type = 1) OR disk.isDiskette THEN fs := FAT12; info.String("Formating FAT12 volume");
ELSIF (type = 4) OR (type = 6) OR (type = 0EH) THEN fs := FAT16; info.String("Formating FAT16 volume");
ELSE fs := FAT32; info.String("Formating FAT32 volume");
END;
SetStatus(state.status, "Formating...", 0, 0, 0, FALSE);
spc := GetSectorPerCluster(disk.table[partition].size);
info.String(" (clusterSize: "); info.Int(spc * disk.device.blockSize, 0); info.String("B)"); info.Ln;
IF spc # -1 THEN
fatsize := GetFatSize(spc);
block := BuildBPB(spc);
ASSERT((disk.table[partition].start#0) OR (disk.isDiskette));
disk.device.Transfer(Disks.Write, disk.table[partition].start, 1, block, 0, res);
IF res # Disks.Ok THEN
PartitionsLib.GetErrorMsg("Format failed: Could not write boot sector", res, temp); ReportError(temp);
ELSE
IF fs = FAT32 THEN
rootDirSectors := 0;
firstDataSector := rsvdSecCnt32 + (numFATs * fatsize) + rootDirSectors;
ELSE
rootDirSectors := ((rootEntCnt * 32) + (disk.device.blockSize -1)) DIV disk.device.blockSize;
firstDataSector := rsvdSecCnt1216 + (numFATs * fatsize) + rootDirSectors;
END;
vol := GetVolume(disk.device, partition, block);
IF vol#NIL THEN
IF quickFormat THEN
ClearSectors(2, firstDataSector -1);
IF fs = FAT32 THEN
NEW(null, spc*disk.device.blockSize);
FOR i := 0 TO LEN(null)-1 DO null[i] := 0X; END;
vol.WriteCluster(rootCluster32, null^, res);
IF res # Disks.Ok THEN
PartitionsLib.GetErrorMsg("Could not clear root cluster", res, temp); ReportError(temp);
END;
END;
ELSE
ClearSectors(2, disk.table[partition].size - 1);
END;
vol.unsafe := TRUE;
IF Disks.Removable IN disk.device.flags THEN media := 0FFFFFF0H; ELSE media := 0FFFFFF8H; END;
vol.WriteFATEntry(0, media, res);
IF res # Disks.Ok THEN PartitionsLib.GetErrorMsg("Could not set media byte", res, temp); ReportError(temp); END;
vol.unsafe := TRUE;
vol.WriteFATEntry(1, LONGINT(0FFFFFFFFH), res);
IF res # Disks.Ok THEN PartitionsLib.GetErrorMsg("Could not set EOC mark", res, temp); ReportError(temp); END;
vol.unsafe := FALSE;
IF fs = FAT32 THEN
vol.unsafe := TRUE;
vol.WriteFATEntry(rootCluster32, 0FFFFFFFH, res);
vol.unsafe := FALSE;
IF res # Disks.Ok THEN PartitionsLib.GetErrorMsg("Could not set FAT entry of root cluster", res, temp); ReportError(temp); END;
IF freeCount + 3 >= 0FFFFFF7H THEN
vol.WriteFATEntry(0FFFFFF7H, 0FFFFFF7H, res);
IF res # Disks.Ok THEN PartitionsLib.GetErrorMsg("Could not set EOC mark", res, temp); ReportError(temp); END;
END;
END;
ELSE ReportError("Could not get volume object");
END;
IF fs = FAT12 THEN
ELSIF fs = FAT16 THEN
ELSIF fs = FAT32 THEN
END;
IF fs = FAT32 THEN
disk.device.Transfer(Disks.Write, disk.table[partition].start + backupBoot, 1, block, 0, res);
IF res = Disks.Ok THEN
freeCount := ((disk.table[partition].size - (rsvdSecCnt32 + (numFATs*fatsize))) DIV spc) + 1;
freeCount := freeCount - 3;
IF rootCluster32 = 2 THEN firstFree := 3; ELSE firstFree := 2; END;
block := BuildFSInfo(freeCount, firstFree);
disk.device.Transfer(Disks.Write, disk.table[partition].start + fsinfo, 1, block, 0, res);
IF res # Disks.Ok THEN
PartitionsLib.GetErrorMsg("Could not write FSInfo sector", res, temp); ReportError(temp);
END;
ELSE PartitionsLib.GetErrorMsg("Could not write backup boot sector", res, temp); ReportError(temp);
END;
END;
result.String("Formatted "); result.String(diskpartString); result.String(" as ");
IF fs = FAT12 THEN result.String("FAT12 ");
ELSIF fs = FAT16 THEN result.String("FAT16 ");
ELSIF fs = FAT32 THEN result.String("FAT32 ");
END;
IF state.status * PartitionsLib.StatusError = {} THEN
result.String("without errors");
ELSE
result.String("with "); result.Int(state.errorCount, 0); result.String(" errors");
END;
END;
END;
END DoOperation;
PROCEDURE ClearSectors(from, to : LONGINT);
CONST BufSize = 1024;
VAR
buf : POINTER TO ARRAY OF CHAR;
ofs, num : LONGINT;
res : LONGINT;
temp: ARRAY 256 OF CHAR;
BEGIN
ASSERT(from < to);
ASSERT((disk.table[partition].start#0) OR (disk.isDiskette));
NEW(buf, BufSize*BS);
num := (to - from + 1) DIV BufSize;
ofs := from;
SetStatus(state.status, "Formating...", from, from, to - from + 1, TRUE);
WHILE (num > 0) DO
ASSERT(ofs + BufSize - 1<= disk.table[partition].size );
disk.device.Transfer(Disks.Write, disk.table[partition].start + ofs, BufSize, buf^, 0, res);
IF res # Disks.Ok THEN
PartitionsLib.GetTransferError(disk.device, Disks.Write, disk.table[partition].start + ofs, res, temp); ReportError(temp);
END;
SetCurrentProgress(from + ofs);
DEC(num); INC(ofs, BufSize);
END;
num := (to - from) MOD BufSize;
WHILE (num > 0) DO
ASSERT(ofs <= disk.table[partition].size);
disk.device.Transfer(Disks.Write, disk.table[partition].start + ofs, 1, buf^, 0, res);
IF res # Disks.Ok THEN
PartitionsLib.GetTransferError(disk.device, Disks.Write, disk.table[partition].start + ofs, res, temp); ReportError(temp);
END;
SetCurrentProgress(from + ofs);
DEC(num); INC(ofs);
END;
END ClearSectors;
PROCEDURE BuildBPB(secPerClus : LONGINT) : Block;
VAR
b : Block;
temp : ARRAY 9 OF CHAR;
i, t, d: LONGINT;
BEGIN
ASSERT(disk.device.blockSize = BS);
b[BsJmpBoot] := 0E9X; b[BsJmpBoot+1] := 0X; b[BsJmpBoot+2] := 0X;
FOR i := 0 TO 7 DO b[BsOEMName + i] := oemName[i]; END;
PartitionsLib.Put2(b, BpbBytsPerSec, disk.device.blockSize);
b[BpbSecPerClus] := CHR(secPerClus);
IF (fs = FAT12) OR (fs = FAT16) THEN b[BpbRsvdSecCnt] := CHR(rsvdSecCnt1216);
ELSE b[BpbRsvdSecCnt] := CHR(rsvdSecCnt32);
END;
b[BpbNumFATs] := CHR(numFATs);
IF (fs = FAT12) OR (fs = FAT16) THEN PartitionsLib.Put2(b, BpbRootEntCnt, rootEntCnt);
ELSE PartitionsLib.Put2(b, BpbRootEntCnt, 0);
END;
IF ((fs = FAT12) OR ((fs = FAT16) & (disk.table[partition].size < 10000H))) THEN PartitionsLib.Put2(b, BpbTotSec16, disk.table[partition].size);
ELSIF (fs = FAT32) THEN PartitionsLib.Put2(b, BpbTotSec16, 0);
END;
IF Disks.Removable IN disk.device.flags THEN b[BpbMedia] := CHR(0F0H); ELSE b[BpbMedia] := CHR(0F8H); END;
IF (fs = FAT12) OR (fs = FAT16) THEN PartitionsLib.Put2(b, BpbFATSz16, fatsize);
ELSE PartitionsLib.Put2(b, BpbFATSz16, 0);
END;
PartitionsLib.Put2(b, BpbSecPerTrk, disk.geo.spt);
PartitionsLib.Put2(b, BpbNumHeads, disk.geo.hds);
IF ((disk.device.table=NIL) OR (LEN(disk.device.table)=1)) THEN PartitionsLib.Put4(b, BpbHiddSec, 0); ELSE PartitionsLib.Put4(b, BpbHiddSec, 63) END;
IF ((fs = FAT12) OR ((fs = FAT16) & (disk.table[partition].size < 10000H))) THEN PartitionsLib.Put4(b, BpbTotSec32, 0);
ELSE PartitionsLib.Put4(b, BpbTotSec32, disk.table[partition].size);
END;
IF (fs = FAT12) OR (fs = FAT16) THEN
b[BsDrvNum] := PartitionsLib.GetDriveNum(disk.device);
b[BsReserved1] := 0X;
b[BsBootSig] := CHR(29H);
Clock.Get(t,d); PartitionsLib.Put4(b, BsVolID, i);
FOR i := 0 TO 10 DO b[BsVolLab + i] := volLab[i]; END;
IF fs = FAT12 THEN temp := "FAT12 "; ELSIF fs = FAT16 THEN temp := "FAT16 "; END;
FOR i := 0 TO 7 DO b[BsFilSysType + i] := temp[i]; END;
ELSE
PartitionsLib.Put4(b, BpbFATSz32, fatsize);
PartitionsLib.Put2(b, BpbExtFlags, 0);
PartitionsLib.Put2(b, BpbFSVer, 0);
PartitionsLib.Put4(b, BpbRootClus, rootCluster32);
PartitionsLib.Put2(b, BpbFSInfo, fsinfo);
PartitionsLib.Put2(b, BpbBkBootSec, backupBoot);
FOR i := 0 TO 11 DO b[BpbReserved] := 0X; END;
b[Bs32DrvNum] := PartitionsLib.GetDriveNum(disk.device);
b[Bs32Reserved1] := 0X;
b[Bs32BootSig] := CHR(29H);
Clock.Get(t,d); PartitionsLib.Put4(b, Bs32VolID, i);
FOR i := 0 TO 10 DO b[Bs32VolLab + i] := volLab[i]; END;
IF fs = FAT32 THEN temp := "FAT32 "; END; FOR i := 0 TO 7 DO b[BsFilSysType + i] := temp[i]; END;
END;
b[510] := 055X; b[511] := 0AAX;
RETURN b;
END BuildBPB;
PROCEDURE BuildFSInfo(freecount, nextfree : LONGINT) : Block;
VAR b : Block; i : LONGINT;
BEGIN
PartitionsLib.Put4(b, FsiLeadSig, 41615252H);
FOR i := 0 TO 479 DO b[FsiReserved1] := 0X; END;
PartitionsLib.Put4(b, FsiStrucSig, 61417272H);
PartitionsLib.Put4(b, FsiFreeCount, freecount);
PartitionsLib.Put4(b, FsiNxtFree, nextfree);
FOR i := 0 TO 11 DO b[FsiReserved2] := 0X; END;
PartitionsLib.Put4(b, FsiTrailSig, LONGINT(0AA550000H));
RETURN b;
END BuildFSInfo;
PROCEDURE GetFatSize(sectorPerCluster : LONGINT) : LONGINT;
VAR
rootDirSectors, rootEntCnt, bytsPerSec, rsvdSecCnt : LONGINT;
tmpVal1, tmpVal2 : LONGINT;
BEGIN
IF disk.isDiskette THEN
RETURN 9;
ELSE
IF fs = FAT32 THEN rootEntCnt := 0; rsvdSecCnt := rsvdSecCnt32; ELSE rootEntCnt := SELF.rootEntCnt; rsvdSecCnt := rsvdSecCnt1216; END;
bytsPerSec := disk.device.blockSize;
rootDirSectors := ((rootEntCnt * 32) + (bytsPerSec -1)) DIV bytsPerSec;
tmpVal1 := disk.table[partition].size - (rsvdSecCnt + rootDirSectors);
tmpVal2 := (256 * sectorPerCluster) + numFATs;
IF fs = FAT32 THEN tmpVal2 := tmpVal2 DIV 2; END;
RETURN (tmpVal1 + (tmpVal2 - 1)) DIV tmpVal2;
END;
END GetFatSize;
PROCEDURE GetSectorPerCluster(disksize : LONGINT) : LONGINT;
VAR spc : LONGINT;
BEGIN
ASSERT((disk.device.blockSize = 512) & (rsvdSecCnt1216 = 1) & (numFATs = 2));
ASSERT((fs = FAT12) OR (rootEntCnt = 512));
IF fs = FAT12 THEN
spc := 1;
ELSIF fs = FAT16 THEN
IF disksize <= 8400 THEN spc := -1; ReportError("FAT16 volumes must be bigger than 4,1MB");
ELSIF disksize <= 32680 THEN spc := 2;
ELSIF disksize <= 262144 THEN spc := 4;
ELSIF disksize <= 524288 THEN spc := 8;
ELSIF disksize <= 1048576 THEN spc := 16;
ELSIF disksize <= 2097152 THEN spc := 32;
ELSIF disksize <= 4194304 THEN spc := 64;
ELSE spc := -1; ReportError("FAT16 volumes can't be bigger than 2GB");
END;
ELSIF fs = FAT32 THEN
IF disksize <= 66600 THEN spc := -1; ReportError("FAT32 volumes must be bigger than 32,5MB");
ELSIF disksize <= 532480 THEN spc := 1;
ELSIF disksize <= 16777216 THEN spc := 8;
ELSIF disksize <= 33554432 THEN spc := 16;
ELSIF disksize <= 67108864 THEN spc := 32;
ELSE spc := 64;
END;
ELSE
HALT(301);
END;
RETURN spc;
END GetSectorPerCluster;
PROCEDURE ValidClusterSize(clusterSize : LONGINT):BOOLEAN;
BEGIN
RETURN ((clusterSize=512) OR (clusterSize=1024) OR (clusterSize=2048) OR (clusterSize=4096) OR
(clusterSize=8192) OR (clusterSize=16384) OR (clusterSize=32768));
END ValidClusterSize;
PROCEDURE &Init*(disk : PartitionsLib.Disk; partition : LONGINT; out : Streams.Writer);
BEGIN
Init^(disk, partition, out);
name := "FormatFAT"; desc := "Format partition"; locktype := PartitionsLib.WriterLock;
oemName := "MSWIN4.1";
rsvdSecCnt1216 := 1;
rsvdSecCnt32 := 32;
IF disk.isDiskette THEN rootEntCnt := 224; ELSE rootEntCnt := 512; END;
numFATs := 2;
volLab := "NO NAME ";
rootCluster32 := 2;
fsinfo := 1;
backupBoot := 6;
END Init;
END FormatPartition;
VAR
fsType2 : LONGINT;
PROCEDURE GetVolume(dev : Disks.Device; partIdx : LONGINT; bpb : Block) : FATVolumes.Volume;
CONST CacheSize = 65563;
VAR
vol : FATVolumes.Volume; vol12: FATVolumes.FAT12Volume; vol16: FATVolumes.FAT16Volume; vol32: FATVolumes.FAT32Volume;
fatSize, numSectors, numClusters, reserved, numFATs, rootEntryCount, sectPC, fat : LONGINT;
BEGIN
IF (LEN(bpb) = 512) & (bpb[510] = 055X) & (bpb[511] = 0AAX) THEN
fatSize := FATVolumes.GetUnsignedInteger(bpb, BpbFATSz16);
IF (fatSize = 0) THEN fatSize := FATVolumes.GetLongint(bpb, BpbFATSz32) END;
numSectors := FATVolumes.GetUnsignedInteger(bpb, BpbTotSec16);
IF (numSectors = 0) THEN numSectors := FATVolumes.GetLongint(bpb, BpbTotSec32) END;
reserved := FATVolumes.GetUnsignedInteger(bpb, BpbRsvdSecCnt);
numFATs := ORD(bpb[BpbNumFATs]);
rootEntryCount := FATVolumes.GetUnsignedInteger(bpb, BpbRootEntCnt);
sectPC := ORD(bpb[BpbSecPerClus]);
numClusters := (numSectors - (reserved + (numFATs * fatSize) + (rootEntryCount * 32 + BS - 1) DIV BS)) DIV sectPC;
IF (numClusters < 4085) THEN NEW(vol12); vol := vol12; fat := 12
ELSIF (numClusters < 65525) THEN NEW(vol16); vol := vol16; fat := 16
ELSE NEW(vol32); vol := vol32; fat := 32
END;
IF ~vol.InitLowLevel(bpb, numClusters, dev, dev.table[partIdx].start, dev.table[partIdx].size, BS) THEN
vol := NIL;
ELSE
vol.SetCache(FATVolumes.Data, CacheSize, FALSE);
EXCL(vol.flags, Files.ReadOnly);
END;
END;
RETURN vol;
END GetVolume;
END FATScavenger.