nufatfs
Version:
A new async-friendly library for accessing FAT16 and FAT32 filesystems
135 lines (134 loc) • 5.91 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClusterAllocator = void 0;
const cluster_chain_1 = require("./cluster-chain");
class ClusterAllocator {
constructor(fat) {
this.fat = fat;
this.freelist = [];
// Freemap: true if free, false if taken
this.freemap = [];
}
static async create(fat) {
const c = new ClusterAllocator(fat);
await c.init();
return c;
}
async init() {
const fatEntriesCount = this.fat.maxDataCluster + 1;
this.freemap = Array(fatEntriesCount).fill(true);
// Clusters 0 and 1 are always taken.
this.freemap[0] = false;
this.freemap[1] = false;
// COMMENTED OUT: It appears I might have entirely misunderstood FAT's idea of file deletion.
// Iterate over the files, mark free when iterating over the files
// const consumeChain = (chain: Chain<ClusterChainLink>) => chain.links.forEach(e => this.freemap[e.index] = false);
// const consumeDirectory = async (cluster: number, isRoot = false) => {
// let thisEntry: FatFSDirectoryEntry[];
// if(isRoot) {
// thisEntry = await this.fat.getRootDirectoryData();
// }else {
// const chain = this.fat.constructClusterChain(cluster, false);
// // Add the whole raw entry as a non-free region
// consumeChain(chain);
// thisEntry = this.fat.consumeAllDirectoryEntries(await chain.readAll());
// }
// for(let subentry of thisEntry) {
// const initialCluster = subentry._firstCluster;
// if(initialCluster === 0) {
// // Empty file
// continue;
// }
// const isDirectory = subentry.attribs & FatFSDirectoryEntryAttributes.Directory;
// if(isDirectory && !(["..", "."].includes(subentry._filenameStr.trim()))) {
// await consumeDirectory(initialCluster);
// } else if (!(subentry.attribs & FORBIDDEN_ATTRIBUTES_FOR_FILE)) {
// // Is a file.
// // Construct a chain, then mark it as taken if it's not deleted
// if(subentry.filename[0] !== FAT_MARKER_DELETED){
// const chain = this.fat.getClusterChainFromFAT(initialCluster);
// chain.forEach(e => this.freemap[e] = false);
// }
// }
// }
// };
// await consumeDirectory(-1, true)
for (let i = 2; i <= this.fat.maxDataCluster; i++) {
this.freemap[i] = this.fat.readFATClusterEntry(i) == 0;
}
this.convertFreemapToFreelist();
}
convertFreemapToFreelist() {
this.freelist = [];
let nextFree = 0;
while ((nextFree = this.freemap.indexOf(true, nextFree)) !== -1) {
let length = 0;
const startCluster = nextFree;
while (this.freemap[nextFree++])
++length;
this.freelist.push({ length, startCluster });
}
}
addChainToFreelist(chain) {
chain.links.forEach(e => this.freemap[e.index] = true); // Mark as free
this.convertFreemapToFreelist();
}
addClusterListToFreelist(list) {
list.forEach(e => this.freemap[e] = true);
this.convertFreemapToFreelist();
}
allocate(lastLink, size) {
// console.log(`[NUFATFS]: Trying to allocate ${size} bytes after ${lastLink?.index || '<unspecified>'}`);
if (!this.freelist.length || size === 0)
return [];
// Try to find an area that's big enough to house the whole `size`
let sizeAsClusters = Math.ceil(size / this.fat.clusterSizeInBytes);
const allFitting = this.freelist.filter(e => e.length >= sizeAsClusters);
const findClosest = (list) => {
if (lastLink) {
list.sort((a, b) => Math.abs(a.startCluster - lastLink.index) - Math.abs(b.startCluster - lastLink.index));
}
};
findClosest(allFitting);
let chainToModify;
if (allFitting.length) {
// There exists such a chain
chainToModify = allFitting[0];
}
else {
let clone = this.freelist.slice();
findClosest(clone);
chainToModify = clone[0];
}
if (!chainToModify)
return [];
sizeAsClusters = Math.min(sizeAsClusters, chainToModify.length);
chainToModify.length -= sizeAsClusters;
const startIndex = chainToModify.startCluster;
chainToModify.startCluster += sizeAsClusters;
if (chainToModify.length === 0) {
// Delete from freelist
this.freelist.splice(this.freelist.indexOf(chainToModify), 1);
}
let links = [];
let remainingSize = size;
for (let i = startIndex; i < startIndex + sizeAsClusters; i++) {
const link = new cluster_chain_1.ClusterChainLink(this.fat, i, this.fat.clusterSizeInBytes);
links.push(link);
this.freemap[i] = false;
remainingSize -= link.length;
}
// console.log(`[NUFATFS]: Allocated chain: ${links.map(e => e.index).join(', ')}`);
for (let i = 1; i < links.length; i++) {
// Update the main FAT
this.fat.writeFATClusterEntry(links[i - 1].index, links[i].index);
}
this.fat.writeFATClusterEntry(links[links.length - 1].index, this.fat.endOfChain[this.fat.endOfChain.length - 1]);
if (lastLink) {
// Merge chains if possible.
this.fat.writeFATClusterEntry(lastLink.index, links[0].index);
}
return links;
}
}
exports.ClusterAllocator = ClusterAllocator;