{ ************************************************ * Asuro * Unit: Drivers/storage/fat32 * Description: fat32 file system driver * ************************************************ * Author: Aaron Hance * Contributors: ************************************************ } { Todo in the future, optimise by prvoiding batch read/write commands } unit FAT32; interface uses console, storagemanagement, util, terminal, lmemorymanager, strings, lists, tracer, serial, rtc; type TBootRecord = bitpacked record jmp2boot : ubit24; OEMName : array[0..7] of char; sectorSize : uint16; spc : uint8; rsvSectors : uint16; numFats : uint8; numDirEnt : uint16; numSectors : uint16; mediaDescp : uint8; sectorsPerFat : uint16; sectorsPerTrack : uint16; heads : uint16; hiddenSectors : uint32; manySectors : uint32; FATSize : uint32; flags : uint16; signature : uint8; FATVersion : uint16; rootCluster : uint32; FSInfoCluster : uint16; backupCluster : uint16; reserved0 : array[0..11] of uint8; driveNumber : uint8; reserved1 : uint8; bsignature : uint8;// = $28; volumeID : uint32; volumeLabel : array[0..10] of uint8; identString : array[0..7] of char;// = 'FAT32 '; end; PBootRecord = ^TBootRecord; TDirectory = packed record fileName : array[0..7] of char; fileExtension : array[0..2] of char; attributes : uint8; reserved0 : uint8; timeFine : uint8; time : uint16; date : uint16; accessTime : uint16; clusterHigh : uint16; modifiedTime : uint16; modifiedDate : uint16; clusterLow : uint16; byteSize : uint32; end; PDirectory = ^TDirectory; TFilesystemInfo = record leadSignature : uint32; reserved0 : array[0..479] of uint8; structSignature : uint32; freeSectors : uint32; nextFreeSector : uint32; reserved1 : array[0..11] of uint8; trailSignature : uint32; end; TFatVolumeInfo = record sectorsPerCluster : uint8; // must be power of 2 and mult by sectorsize to max 32k end; PFatVolumeInfo = ^TFatVolumeInfo; var filesystem : TFilesystem; procedure init; procedure create_volume(disk : PStorage_Device; sectors : uint32; start : uint32; config : puint32); procedure detect_volumes(disk : PStorage_Device); //function writeDirectory(volume : PStorage_volume; directory : pchar; attributes : uint32) : uint8; // need to handle parent table cluster overflow, need to take attributes //function readDirectory(volume : PStorage_volume; directory : pchar; listPtr : PLinkedListBase) : uint8; //returns: 0 = success, 1 = dir not exsist, 2 = not directory, 3 = error implementation procedure STOS(str : PChar); var i : uint32; begin for i:=0 to StringSize(str)-1 do begin serial.send(COM1, uint8(str[i]), 100); end; serial.send(COM1, 13, 100); end; function load(ptr : void) : boolean; begin console.outputln('DUMMY DRIVER', 'LOADED.') end; function cleanString(str : pchar; status : puint32) : byteArray8; var i : uint32; ii: uint32; begin //cleanString:= pchar(kalloc(sizeof(10))); for i:=0 to 7 do begin if str[i] = char(0) then begin for ii:=i to 7 do begin cleanString[ii]:= ' '; end; break; end else begin // if (str[i] = '/') or (str[i] = ',') then begin // status:= 3; // end; cleanString[i]:= str[i]; end; end; end; function readBootRecord(volume : PStorage_volume) : PBootRecord; // need write functions for boot record! var buffer : puint32; begin buffer:= puint32(kalloc(512)); memset(uint32(buffer), 0, 512); volume^.device^.readcallback(volume^.device, volume^.sectorStart + 1, 1, buffer); readBootRecord:= PBootRecord(buffer); end; //TODO fat starts after reserved secotrs function readFat(volume : PStorage_volume; cluster : uint32; bootRecord : PBootRecord) : uint32; var buffer : puint32; fatEntriesPerSector : uint32; sectorLocation : uint32; dataStart : uint32; begin push_trace('fat32.readFat'); buffer:= puint32(kalloc(bootRecord^.sectorsize)); memset(uint32(buffer), 0, bootRecord^.sectorsize); fatEntriesPerSector:= bootRecord^.sectorsize div 4; sectorLocation:= cluster div fatEntriesPerSector; dataStart:= (volume^.sectorStart + 1 + bootRecord^.rsvSectors); volume^.device^.readcallback(volume^.device, datastart + sectorLocation, 1, buffer); readFat:= buffer[cluster - (sectorLocation * fatEntriesPerSector)]; kfree(buffer); end; procedure writeFat(volume : PStorage_volume; cluster : uint32; value : uint32; bootRecord : PBootRecord); var buffer : puint32; fatEntriesPerSector : uint32; sectorLocation : uint32; dataStart : uint32; begin push_trace('fat32.WriteFat'); buffer:= puint32(kalloc(bootRecord^.sectorsize)); memset(uint32(buffer), 0, bootRecord^.sectorsize); fatEntriesPerSector:= bootRecord^.sectorsize div 4; sectorLocation:= cluster div fatEntriesPerSector; dataStart:= (volume^.sectorStart + 1 + bootRecord^.rsvSectors); volume^.device^.readcallback(volume^.device, dataStart + sectorLocation, 1, buffer); buffer[cluster - (sectorLocation * fatEntriesPerSector)]:= value; volume^.device^.writecallback(volume^.device, dataStart + sectorLocation, 1, buffer); kfree(buffer); end; function getFatChain(volume : PStorage_volume; cluster : uint32; bootRecord : PBootRecord) : PLinkedListBase; var currentCluster : uint32; currentClusterValue : uint32; clusters : PLinkedListBase; dirElm : puint32; begin push_trace('fat32.getFatChain'); clusters:= LL_New(sizeof(uint32)); currentCluster:= cluster; currentClusterValue:= cluster; while true do begin currentClusterValue:= readFat(volume, currentClusterValue, bootRecord); if currentClusterValue = $FFFFFFF7 then begin break; end else if currentClusterValue = $FFFFFFF8 then begin dirElm:= LL_add(clusters); dirElm^:= currentCluster; break; end else if currentClusterValue = 0 then begin break; end else begin dirElm:= LL_add(clusters); dirElm^:= currentCluster; end; currentCluster+=1; end; getFatChain:= clusters; end; //TODO improve with FSINFO function findFreeClusters(volume : PStorage_volume; amount : uint32; bootRecord : PBootRecord) : PLinkedListBase; var i : uint32 = 2; currentClusterValue : uint32; currentAmount : uint32 = 0; clusters : PLinkedListBase; dirElm : puint32; begin push_trace('fat32.findFreeClusters'); clusters := LL_New(8); while true do begin if currentAmount = amount then break; currentClusterValue:= readFat(volume, i, bootRecord); if currentClusterValue = 0 then begin dirElm:= LL_add(clusters); dirElm^:= i; currentAmount+=1; end; i+=1; end; findFreeClusters:= clusters; end; //TODO add optional attributes flag to refine what i return function getDirEntries(volume : PStorage_volume; cluster : uint32; bootRecord : PBootRecord) : PLinkedListBase; var buffer : puint32; bufferI : puint32; clusters : PLinkedListBase; directories : PLinkedListBase; i : uint32 = 0; datastart : uint32; sectorLocation : uint32; dirElm : puint32; begin push_trace('fat32.getDirEntries'); directories:= LL_New(sizeof(TDirectory)); clusters:= PLinkedListBase(getFatChain(volume, cluster, bootRecord)); buffer:= puint32(kalloc( (bootRecord^.sectorSize * bootRecord^.spc) * LL_size(clusters) )); memset(uint32(buffer), 0, (bootRecord^.sectorSize * bootRecord^.spc) * LL_size(clusters) ); dataStart:= volume^.sectorStart + 1 + bootRecord^.rsvSectors + bootRecord^.FATSize; for i:=0 to LL_size(clusters) - 1 do begin sectorLocation:= bootRecord^.spc * (i + cluster); bufferI:= @buffer[i * (bootRecord^.spc * bootRecord^.sectorSize)]; volume^.device^.readcallback(volume^.device, datastart + sectorLocation, bootRecord^.spc, bufferI); //datastart + spc(i + cluster) end; i:=0; while true do begin if PDirectory(buffer)[i].fileName[0] = char(0) then break; dirElm:= LL_Add(directories); PDirectory(dirElm)^:= PDirectory(buffer)[i]; i+=1; end; getDirEntries:= directories; //get last . LL_Free(clusters); kfree(buffer); end; function compareByteArray8(str1 : byteArray8; str2 : byteArray8) : boolean; var i : uint32; begin push_trace('fat32.compareArray'); compareByteArray8:= true; for i:=0 to 7 do begin if str1[i] <> str2[i] then begin compareByteArray8:= false; break; end; end; end; function fat2GenericEntries(list : PLinkedListBase) : PLinkedListBase; var i : uint32; entry : PDirectory_Entry; dir : PDirectory; dirElm: puint32; begin push_trace('fat32.fat2GenericEntries'); puint32(entry) := kalloc(sizeof(TDirectory_Entry)); fat2GenericEntries:= LL_New(sizeof(TDirectory_Entry)); if LL_size(list) > 0 then begin //TODO for i:= 0 to LL_Size(list) - 1 do begin dir := PDirectory(LL_get(list, i)); entry^.fileName:= pchar(dir^.fileName); entry^.extension:= pchar(dir^.fileExtension); if dir^.attributes = $10 then begin entry^.entryType:= TDirectory_Entry_Type.directoryEntry; end else begin //TODO add mount type entry^.entryType:= TDirectory_Entry_Type.fileEntry; end; //add to list dirElm:= LL_add(fat2GenericEntries); PDirectory_Entry(dirElm)^:= entry^; end; end; kfree(puint32(list)); kfree(puint32(entry)); end; //need to find out why having multiple dir stings isn't working, maybe the ls command? did I fix this? function readDirectory(volume : PStorage_volume; directory : pchar; statusOut : puint32) : PLinkedListBase; //statusout: 0 = success, 1 = dir not exsist, 2 = not directory, 3 = invalid name, 4= already exists var bootRecord : PBootRecord; directoryStrings : PLinkedListBase; directories : PLinkedListBase; cluster : uint32; i : uint32; ii : uint32 = 0; dirEntry : PDirectory; status : puint32; begin status:= puint32(kalloc(sizeof(uint32))); push_trace('fat32.readDirectory'); status^:= 0; bootRecord:= readBootRecord(volume); directoryStrings:= LL_fromString(directory, '/'); directories:= getDirEntries(volume, bootRecord^.rootCluster, bootRecord); if LL_size(directoryStrings) > 0 then begin for i:=0 to (LL_Size(directoryStrings) ) do begin /// maybe -1 will work ii:=0; while true do begin if ii > LL_Size(directories) - 1 then begin status^:= 1; break; end; dirEntry:= PDirectory(LL_Get(directories, ii)); if compareByteArray8( dirEntry^.fileName, cleanString( pchar(puint32(LL_Get(directoryStrings, i))^), status)) then begin cluster:= uint32(dirEntry^.clusterLow); cluster:= uint32(cluster) or uint32(dirEntry^.clusterHigh shl 16); break; end; ii+=1; end; if status^ <> 0 then break; LL_Free(directories); directories:= getDirEntries(volume, cluster, bootRecord); if i = LL_Size(directoryStrings) - 1 then break; end; end else begin while true do begin if ii > LL_Size(directories) - 1 then break; dirEntry:= PDirectory(LL_Get(directories, ii)); ii+=1; end; end; readDirectory:= directories; statusOut^:= status^; LL_Free(directoryStrings); kfree(puint32(bootRecord)); end; function readDirectoryGen(volume : PStorage_volume; directory : pchar; status : puint32) : PLinkedListBase; //returns: 0 = success, 1 = dir not exsist, 2 = not directory, 3 = invalid name, 4= already exists begin readDirectoryGen:= fat2GenericEntries(readDirectory(volume, directory, status)); end; //need to allow for setting file extension function writeDirectory(volume : PStorage_volume; directory : pchar; dirName : pchar; attributes : uint32; statusOut : puint32; fileExtension : pchar) : uint32; // need to handle parent table cluster overflow, need to take attributes var directories : PLinkedListBase; parentDirectory : PDirectory; parentCluster : uint32; clusters : PLinkedListBase; cluster : uint32; bootRecord : PBootRecord; buffer : puint32; bufferPointer : PDirectory; dataStart : uint32; EntriesPerSector : uint32; sectorLocation : uint32; dataOffset : uint32; i : uint32; thisArray : byteArray8 = ('.',' ',' ',' ',' ',' ',' ',' '); parentArray : byteArray8 = ('.','.',' ',' ',' ',' ',' ',' '); status : puint32; begin push_trace('fat32.writeDirectory'); status:= puint32(kalloc(sizeof(uint32))); status^:= 0; directories:= readDirectory(volume, directory, status); if(LL_size(directories) > 1) then begin for i:=0 to LL_Size(directories) - 1 do begin if compareByteArray8( pchar(PDirectory(LL_get(directories, i))^.fileName), cleanString( dirName , status)) then begin status^:= 4; end; end; end; bootRecord:= readBootRecord(volume); datastart:= volume^.sectorStart + 1 + bootRecord^.FATSize + bootRecord^.rsvSectors; if status^ = 0 then begin parentDirectory:= PDirectory(LL_Get(directories, 0)); parentCluster:= uint32(parentDirectory^.clusterlow) or uint32(parentDirectory^.clusterhigh shl 16); //TODO check if this cluster is full, if so allocate new cluster clusters:= findFreeClusters(volume, 1, bootRecord); cluster:= uint32(LL_Get(clusters, 0)^); LL_Free(clusters); buffer:= puint32(kalloc(bootRecord^.sectorSize)); if attributes = $10 then begin // if directory memset(uint32(buffer), 0, bootRecord^.sectorSize); bufferPointer:= @PDirectory(buffer)[0]; bufferPointer^.fileName:= thisArray; //TODO implement time bufferPointer^.attributes:= attributes; bufferPointer^.clusterLow:= cluster; bufferPointer^.clusterHigh:= uint16((cluster shr 16) and $0000FFFF); bufferPointer:= @PDirectory(buffer)[1]; bufferPointer^.fileName:= parentArray; //TODO implement time bufferPointer^.attributes:= attributes; bufferPointer^.clusterLow:= parentCluster; bufferPointer^.clusterHigh:= uint16((parentCluster shr 16) and $0000FFFF); //write to disk volume^.device^.writecallback(volume^.device, dataStart + (cluster * bootRecord^.spc), 1, buffer); //write fat writeFat(volume, cluster, $FFFFFFF8, bootRecord); end; memset(uint32(buffer), 0, bootRecord^.sectorSize); //calculate write cluster using directories and parentCluster sectorLocation:= LL_size(directories) * sizeof(TDirectory) div bootRecord^.sectorSize; sectorLocation:= sectorLocation + (parentCluster * bootRecord^.spc); //dataOffset:= datastart + ( (LL_size(directories) * sizeof(PDirectory)) - (sizeUsed * bootRecord^.sectorSize)); volume^.device^.readcallback(volume^.device, dataStart + sectorLocation, 1, buffer); //construct my dir entry bufferPointer:= @PDirectory(buffer)[LL_size(directories)]; bufferPointer^.fileName:= cleanString(dirName, status); bufferPointer^.attributes:= attributes; //if attributes = 0 then bufferPointer^.fileExtension:= fileExtension; bufferPointer^.clusterLow:= cluster; bufferPointer^.clusterHigh:= uint16((cluster shr 16) and $0000FFFF); writeDirectory:= cluster; //write to disk volume^.device^.writecallback(volume^.device, dataStart + sectorLocation, 1, buffer); kfree(buffer); end; statusOut^:= status^; kfree(puint32(bootRecord)); //LL_Free(directories); // page fault on free, possible memory leak now? push_trace('writedirectory.end'); end; procedure writeDirectoryGen(volume : PStorage_volume; directory : pchar; dirName : pchar; attributes : uint32; statusOut : puint32); // need to handle parent table cluster overflow, need to take attributes begin writeDirectory(volume, directory, dirName, attributes, statusOut, ''); end; procedure writeFile(volume : PStorage_volume; directory : pchar; entry : PDirectory_Entry; byteCount : uint32; buffer : puint32; statusOut : puint32); var bootRecord : PBootRecord; directories : PLinkedListBase; clusters : PLinkedListBase; startCluster: uint32; device : PStorage_Device; dir : PDirectory; exists : boolean = false; sectorCount : uint32; clusterCount : uint32; clusterDifference : uint32; dataStart : uint32; iterations : uint32; bufferPointer : puint32; i : uint32; status : puint32; begin push_trace('fat32.writefile'); status:= kalloc(4); bootRecord:= readBootRecord(volume); device:= volume^.device; directories:= readDirectory(volume, directory, statusOut); sectorCount:= (byteCount div bootRecord^.sectorSize) + 1; datastart:= volume^.sectorStart + 1 + bootRecord^.FATSize + bootRecord^.rsvSectors; for i:=0 to LL_size(directories) - 1 do begin dir:= PDirectory(LL_get(directories, i)); if (dir^.fileName = entry^.fileName) and (dir^.fileExtension = entry^.extension) then begin exists:= true; break; end; end; push_trace('writefile.1'); if exists then begin startCluster:= uint32(dir^.clusterlow) or uint32(dir^.clusterhigh shl 16); clusters:= getFatChain(volume, startCluster, bootRecord); //check no clusters and check if needs to be more or less, add/remove clusters clusterCount := LL_size(clusters); if (clusterCount * bootRecord^.spc) > sectorCount then begin //shrink clusterDifference:= clusterCount - (sectorCount div bootRecord^.spc); for i:= (clusterCount - clusterDifference) + 1 to clusterCount do begin //free unused clusters writeFat(volume, startCluster + i, 0, bootRecord); end; writeFat(volume, startCluster + (clusterCount - clusterDifference), $FFFFFFF8, bootRecord); // add new cluster terminator //change last entry to terminator. end else if (clusterCount * bootRecord^.spc) < sectorCount then begin //expand clusterDifference:= (sectorCount div bootRecord^.spc) - clusterCount; LL_Free(clusters); clusters:= findFreeClusters(volume, clusterDifference, bootRecord); for i:= clusterCount to clusterCount + clusterDifference - 1 do begin writeFat(volume, startCluster + i, startCluster + i + 1, bootRecord); end; writeFat(volume, startcluster + clusterCount + clusterDifference, $FFFFFFF8, bootRecord); end else begin //nothing clusterDifference:= 0; end; end else begin push_trace('writefile.1.2.1'); startCluster:= writeDirectory(volume, directory, entry^.fileName, 0, status, entry^.extension); clusterDifference:= (byteCount div bootRecord^.sectorsize) - 1; push_trace('writefile.1.2.2'); for i:= startcluster to startCluster + clusterDifference - 1 do begin writeFat(volume, i, i + 1, bootRecord); end; push_trace('writefile.1.2.3'); writeFat(volume, startcluster + clusterDifference, $FFFFFFF8, bootRecord); //setup fat chain end; push_trace('writefile.2'); iterations:= (bytecount div bootRecord^.sectorSize) div 4; for i:=0 to byteCount div bootRecord^.sectorSize do begin bufferPointer:= @buffer[i * uint32(bootRecord^.sectorsize * 4)]; volume^.device^.writecallback(volume^.device, dataStart + startCluster, 4, bufferPointer); end; kfree(puint32(bootRecord)); end; procedure create_volume(disk : PStorage_Device; sectors : uint32; start : uint32; config : puint32); var buffer : puint32; zeroBuffer : puint32; bootRecord : PBootRecord; dataStart : uint32; fatStart : uint32; FATSize : uint32; i : uint32 = 0; asuroArray : byteArray8 = ('A','S','U','R','O',' ','V','1'); fatArray : byteArray8 = ('F','A','T','3','2',' ',' ',' '); thisArray : byteArray8 = ('.',' ',' ',' ',' ',' ',' ',' '); parentArray : byteArray8 = ('.','.',' ',' ',' ',' ',' ',' '); asuroFileArray : byteArray8 = ('A','S','U','R','O',' ',' ',' '); mountFileArray : byteArray8 = ('M','O','U','N','T',' ',' ',' '); programFileArray : byteArray8 = ('P','R','O','G','R','A','M','S'); rootCluster : uint32 = 1; begin push_trace('fat32.create_volume()'); // zeroBuffer:= puint32(kalloc( disk^.sectorSize * 4 )); // memset(uint32(zeroBuffer), 0, disk^.sectorSize * 4); // while true do begin // if i > sectors then break; // disk^.writecallback(disk, 1 + i, 1, zeroBuffer); // i+=1; // end; // kfree(zeroBuffer); //fat32 structure (* BootRecord *) (* reserved sectors *) (* File Allocation Table *) (* Data Area *) buffer:= puint32(kalloc(sizeof(TBootRecord))); memset(uint32(buffer), 0, sizeof(TBootRecord)); bootRecord:= PBootRecord(buffer); FATSize:= ((sectors div config^) * 4) div disk^.sectorsize; bootRecord^.jmp2boot := $0; //TODO impliment boot jump bootRecord^.OEMName := asuroArray; bootRecord^.sectorSize := disk^.sectorsize; bootRecord^.spc := config^; bootRecord^.rsvSectors := 32; //32 is standard bootRecord^.numFats := 1; bootRecord^.mediaDescp := $F8; bootRecord^.hiddenSectors := start; bootRecord^.manySectors := sectors; bootRecord^.FATSize := FATSize; bootRecord^.rootCluster := rootCluster; bootRecord^.FSInfoCluster := 0; bootRecord^.driveNumber := $80; bootRecord^.volumeID := 62; //+ puint32(@rtc.getDateTime())^; bootRecord^.bsignature := $29; bootRecord^.identString := fatArray; puint32(buffer)[127]:= $55AA; disk^.writecallback(disk, start + 1, 1, buffer); fatStart:= start + 1 + bootRecord^.rsvSectors; dataStart:= fatStart + bootRecord^.FATSize; zeroBuffer:= puint32(kalloc( disk^.sectorSize * 4 )); memset(uint32(zeroBuffer), 0, disk^.sectorSize * 4); while true do begin if i > FATSize DIV 4 then break; disk^.writecallback(disk, fatStart + i, 4, zeroBuffer); i+=4; end; kfree(buffer); kfree(zeroBuffer); buffer:= puint32(kalloc(disk^.sectorSize)); memset(uint32(buffer), 0, disk^.sectorSize); puint32(buffer)[0]:= $FFFFFFF8; //fsinfo puint32(buffer)[1]:= $FFFFFFF8; //root cluster disk^.writecallback(disk, fatStart, 1, buffer); kfree(buffer); buffer:= puint32(kalloc(disk^.sectorsize)); memset(uint32(buffer), 0, disk^.sectorsize); PDirectory(buffer)[0].fileName := thisArray; PDirectory(buffer)[0].attributes := $08; PDirectory(buffer)[0].clusterLow := 1; PDirectory(buffer)[1].fileName := parentArray; PDirectory(buffer)[1].attributes := $10; PDirectory(buffer)[1].clusterLow := 1; disk^.writecallback(disk, dataStart + (config^ * rootCluster), 1, buffer); kfree(buffer); end; procedure detect_volumes(disk : PStorage_Device); var buffer : puint32; i : uint8; volume : PStorage_volume; dir : PDirectory; dirs : PLinkedListBase; begin push_trace('fat32.detectVolumes()'); console.writeintln(2); redrawWindows(); volume:= PStorage_volume(kalloc(sizeof(TStorage_Volume))); //check first address for MBR //if found then add volume and use info to see if there is another volume buffer := puint32(kalloc(512)); memset(uint32(buffer), 0, 512); disk^.readcallback(disk, 2, 1, buffer); console.writeintln(3); redrawWindows(); if (puint32(buffer)[127] = $55AA) and (PBootRecord(buffer)^.bsignature = $29) then begin //TODO partition table console.writestringln('FAT32: volume found!'); volume^.device:= disk; volume^.sectorStart:= 1; volume^.sectorSize:= PBootRecord(buffer)^.sectorSize; volume^.freeSectors:= 1000000; //TODO implement get free sectors need FSINFO implemented first volume^.filesystem := @filesystem; storagemanagement.register_volume(disk, volume); end; kfree(buffer); end; procedure init(); begin push_trace('fat32.init()'); filesystem.sName:= 'FAT32'; filesystem.readDirCallback:= @readDirectoryGen; filesystem.createDirCallback:= @writeDirectoryGen; filesystem.createcallback:= @create_volume; filesystem.detectcallback:= @detect_volumes; filesystem.writecallback:= @writeFile; storagemanagement.register_filesystem(@filesystem); end; end.