198 lines
5.5 KiB
C++
198 lines
5.5 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <vector>
|
|
|
|
#include "Structs.h"
|
|
|
|
// https://www.pjrc.com/tech/8051/ide/fat32.html
|
|
namespace FAT32 {
|
|
|
|
template <typename BlockDev> 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<uint8_t*>(&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<const uint8_t*>(&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);
|
|
}
|
|
|
|
};
|
|
}
|