#pragma once #include #include #include #include "Structs.h" // https://www.pjrc.com/tech/8051/ide/fat32.html namespace FAT32 { template class FS { static constexpr const char* NAME = "FAT32"; BlockDev& dev; AbsOffset offset; FSDesc desc; Precomputed tmp; bool valid = false; #include "FreeClusterIterator.h" FreeClusterIterator fci; public: #include "File.h" #include "File2.h" #include "DirIterator.h" /** ctor with the absolute offset addr (in bytes) */ FS(BlockDev& dev, AbsOffset offset) : dev(dev), offset(offset), fci(*this) { init(); } void setup(uint32_t totalSize) { uint8_t buf[512] = {0}; buf[0x1FE] = 0x55; buf[0x1FF] = 0xAA; FSHeader* header = (FSHeader*) buf; header->bytesPerSector = 512; header->numberOfFATs = 2; header->numReservedSectors = 32; header->rootDirFirstCluster = 2; header->mediaDescriptor = 0xF8; // hard disk header->sectorsInPartition = totalSize / 512; header->sectorsPerCluster = 8; header->sectorsPerFAT = 64; // 8 MB dev.write(offset, 512, (const uint8_t*)header); init(); // mark the root-dir cluster as used setNextCluster(2, END_OF_CLUSTERS); } /** is the detected FS valid? */ bool isValid() const { return valid; } /** get an iterator for the root directory */ DirIterator getRoot() { return DirIterator(*this, desc.rootDirFirstCluster); } /** open the given file for reading*/ File open(const DirEntryAt& dea) { return File(*this, dea.getSize(), dea.getFirstCluster()); } /** open the given file for reading */ File2 open2(const DirEntryAt& dea) { return File2(*this, dea); } /** get or create a file with the given name */ File2 getOrCreateFile(const char* name) { DirEntryAt dea = getDirEntry(name, true); // new file -> allocate the first cluster if (dea.getFirstCluster() == 0) { ClusterNr firstCluster = allocFreeCluster(0); dea.setFirstCluster(firstCluster); write(dea); } return File2(*this, dea); } private: void init() { Log::addInfo(NAME, "init @ %d", offset); uint8_t buf[512]; dev.read(offset, 512, buf); desc.bytesPerSector = getU16(&buf[0x0B]); desc.sectorsPerCluster = getU8(&buf[0x0D]); desc.numReservedSectors = getU16(&buf[0x0E]); desc.numberOfFATs = getU8(&buf[0x10]); desc.sectorsPerFAT = getU32(&buf[0x24]); desc.rootDirFirstCluster = getU32(&buf[0x2C]); // basic sanity check based on constants valid = (desc.bytesPerSector == 512) && (desc.numberOfFATs == 2) && (getU16(&buf[0x1FE]) == 0xAA55); tmp.bytesPerCluster = desc.sectorsPerCluster * desc.bytesPerSector; tmp.startOfFAT = offset + (desc.numReservedSectors * desc.bytesPerSector); tmp.startOfFirstDataCluster = offset + (desc.numReservedSectors * desc.bytesPerSector) + (desc.numberOfFATs * desc.sectorsPerFAT * desc.bytesPerSector); tmp.startOfFirstRootDirCluster = clusterToAbsPos(desc.rootDirFirstCluster); tmp.dirEntriesPerSector = desc.bytesPerSector / sizeof(DirEntry); Log::addInfo(NAME, "Bytes/Sector: %d, Sector/Cluster: %d, FATs: %d, RootDir: %d", desc.bytesPerSector, desc.sectorsPerCluster, desc.numberOfFATs, desc.rootDirFirstCluster); } /** get (or create, if needed) a DirEntry with the given name */ DirEntryAt getDirEntry(const char* name, bool createIfNeeded) { // TODO: support for sub folders) // start at the root folder ClusterNr dirStartCluster = 2; { // search for a matching existing entry DirIterator di(*this, dirStartCluster); while(true) { DirEntryAt dea = di.nextUsable(); if (!dea.isValid()) {break;} if (dea.getName() == name) {return dea;} } } // no matching entry found if (!createIfNeeded) {return DirEntryAt::invalid();} { // allocate a new DirEntry for the file DirIterator di(*this, dirStartCluster); DirEntryAt dea = di.nextFree(); dea.setName(name); dea.setSize(0); dea.setFirstCluster(0); return dea; } } /** determine the ClusterNr following the given ClusterNr */ ClusterNr getNextCluster(const ClusterNr clusterNr) { const AbsPos pos = tmp.startOfFAT + (clusterNr * sizeof(ClusterNr)); ClusterNr next = 0; dev.read(pos, sizeof(ClusterNr), reinterpret_cast(&next)); Log::addInfo(NAME, "getNextCluster(%d) -> %d", clusterNr, next); return next; } /** set the ClusterNr following clusterNr */ void setNextCluster(const ClusterNr clusterNr, const ClusterNr next) { const AbsPos pos = tmp.startOfFAT + (clusterNr * sizeof(ClusterNr)); dev.write(pos, sizeof(ClusterNr), reinterpret_cast(&next)); Log::addInfo(NAME, "setNextCluster(%d) -> %d", clusterNr, next); } /** convert ClusterNr into an absolute position on disk */ AbsPos clusterToAbsPos(ClusterNr clusterNr) { return tmp.startOfFirstDataCluster + ((clusterNr - 2) * desc.sectorsPerCluster * desc.bytesPerSector); } /** allocate a new empty cluster, and register it. if prevNr == 0, it has NO previous cluster (first in line) */ ClusterNr allocFreeCluster(ClusterNr prevNr = 0) { ClusterNr newCluster = fci.next(); // get/find a free cluster if (prevNr > 0) { setNextCluster(prevNr, newCluster); // follows the previous cluster (if any) } setNextCluster(newCluster, END_OF_CLUSTERS);// no clusters follow the new cluster return newCluster; } /** write the given DirEntry back to disk */ void write(const DirEntryAt& dea) { dev.write(dea.posOnDisk, sizeof(DirEntry), (const uint8_t*)&dea.entry); } }; }